Hi, this patch adds IPv6 support to umb(4).
It will try to obtain a IPv6 address if the kernel is compiled with INET6. Currently there is no option to disable IPv6 on such a kernel (other than manually calling "ifconfig umb0 -inet6"). Nor is there a IPv6-only mode which refrains from obtaining an IPv4 address from the kernel. To get an IPv6 address, your provider has to offer one. But more importantly the firmware of your umb(4) device has to have IPv6 support. I stumbled across two older Sierra Wireless modules (EM8805 and MC3805) that refused to provide an IPv6 address. Have fun, Gerhard Index: sbin/ifconfig/ifconfig.c =================================================================== RCS file: /cvs/src/sbin/ifconfig/ifconfig.c,v retrieving revision 1.417 diff -u -p -u -p -r1.417 ifconfig.c --- sbin/ifconfig/ifconfig.c 27 Dec 2019 14:34:46 -0000 1.417 +++ sbin/ifconfig/ifconfig.c 23 Jan 2020 09:24:38 -0000 @@ -5666,6 +5666,7 @@ umb_status(void) char apn[UMB_APN_MAXLEN+1]; char pn[UMB_PHONENR_MAXLEN+1]; int i, n; + char astr[INET6_ADDRSTRLEN]; memset((char *)&mi, 0, sizeof(mi)); ifr.ifr_data = (caddr_t)&mi; @@ -5830,7 +5831,15 @@ umb_status(void) for (i = 0, n = 0; i < UMB_MAX_DNSSRV; i++) { if (mi.ipv4dns[i].s_addr == INADDR_ANY) break; - printf("%s %s", n++ ? "" : "\tdns", inet_ntoa(mi.ipv4dns[i])); + printf("%s %s", n++ ? "" : "\tdns", + inet_ntop(AF_INET, &mi.ipv4dns[i], astr, sizeof (astr))); + } + for (i = 0; i < UMB_MAX_DNSSRV; i++) { + if (memcmp(&mi.ipv6dns[i], &in6addr_any, + sizeof (mi.ipv6dns[i])) == 0) + break; + printf("%s %s", n++ ? "" : "\tdns", + inet_ntop(AF_INET6, &mi.ipv6dns[i], astr, sizeof (astr))); } if (n) printf("\n"); Index: share/man/man4/umb.4 =================================================================== RCS file: /cvs/src/share/man/man4/umb.4,v retrieving revision 1.9 diff -u -p -u -p -r1.9 umb.4 --- share/man/man4/umb.4 23 Nov 2017 20:47:26 -0000 1.9 +++ share/man/man4/umb.4 28 Jan 2020 09:04:20 -0000 @@ -40,6 +40,11 @@ will remain in this state until the MBIM In case the device is connected to an "always-on" USB port, it may be possible to connect to a provider without entering the PIN again even if the system was rebooted. +.Pp +If the kernel has been compiled with INET6, the driver will try to +obtain an IPv6 address from the provider. To succeed with the IPv6 +configuration, both the ISP and the MBIM device have to offer IPv6 +support. .Sh HARDWARE The following devices should work: .Pp @@ -64,10 +69,6 @@ The following devices should work: .%U http://www.usb.org/developers/docs/devclass_docs/MBIM10Errata1_073013.zip .Re .Sh CAVEATS -The -.Nm -driver does not support IPv6. -.Pp Devices which fail to provide a conforming MBIM implementation will probably be attached as some other driver, such as .Xr umsm 4 . Index: sys/dev/usb/if_umb.c =================================================================== RCS file: /cvs/src/sys/dev/usb/if_umb.c,v retrieving revision 1.31 diff -u -p -u -p -r1.31 if_umb.c --- sys/dev/usb/if_umb.c 26 Nov 2019 23:04:28 -0000 1.31 +++ sys/dev/usb/if_umb.c 28 Jan 2020 09:08:16 -0000 @@ -43,6 +43,14 @@ #include <netinet/in_var.h> #include <netinet/ip.h> +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_ifattach.h> +#include <netinet6/nd6.h> +#endif + #include <machine/bus.h> #include <dev/usb/usb.h> @@ -158,7 +166,11 @@ int umb_decode_connect_info(struct umb void umb_clear_addr(struct umb_softc *); int umb_add_inet_config(struct umb_softc *, struct in_addr, u_int, struct in_addr); -void umb_send_inet_proposal(struct umb_softc *); +#ifdef INET6 +int umb_add_inet6_config(struct umb_softc *, struct in6_addr *, + u_int, struct in6_addr *); +#endif +void umb_send_inet_proposal(struct umb_softc *, int); int umb_decode_ip_configuration(struct umb_softc *, void *, int); void umb_rx(struct umb_softc *); void umb_rxeof(struct usbd_xfer *, void *, usbd_status); @@ -800,8 +812,8 @@ umb_input(struct ifnet *ifp, struct mbuf #endif /* INET6 */ default: ifp->if_ierrors++; - DPRINTFN(4, "%s: dropping packet with bad IP version (%d)\n", - __func__, ipv); + DPRINTFN(4, "%s: dropping packet with bad IP version (af %d)\n", + __func__, af); m_freem(m); return 1; } @@ -902,7 +914,10 @@ umb_rtrequest(struct ifnet *ifp, int req struct umb_softc *sc = ifp->if_softc; if (req == RTM_PROPOSAL) { - umb_send_inet_proposal(sc); + umb_send_inet_proposal(sc, AF_INET); +#ifdef INET6 + umb_send_inet_proposal(sc, AF_INET6); +#endif return; } @@ -1596,11 +1611,6 @@ umb_decode_connect_info(struct umb_softc if (ifp->if_flags & IFF_DEBUG) log(LOG_INFO, "%s: connection %s\n", DEVNAM(sc), umb_activation(act)); - if ((ifp->if_flags & IFF_DEBUG) && - letoh32(ci->iptype) != MBIM_CONTEXT_IPTYPE_DEFAULT && - letoh32(ci->iptype) != MBIM_CONTEXT_IPTYPE_IPV4) - log(LOG_DEBUG, "%s: got iptype %d connection\n", - DEVNAM(sc), letoh32(ci->iptype)); sc->sc_info.activation = act; sc->sc_info.nwerror = letoh32(ci->nwerror); @@ -1621,9 +1631,16 @@ umb_clear_addr(struct umb_softc *sc) struct ifnet *ifp = GET_IFP(sc); memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns)); - umb_send_inet_proposal(sc); + memset(sc->sc_info.ipv6dns, 0, sizeof (sc->sc_info.ipv6dns)); + umb_send_inet_proposal(sc, AF_INET); +#ifdef INET6 + umb_send_inet_proposal(sc, AF_INET6); +#endif NET_LOCK(); in_ifdetach(ifp); +#ifdef INET6 + in6_ifdetach(ifp); +#endif NET_UNLOCK(); } @@ -1698,29 +1715,125 @@ umb_add_inet_config(struct umb_softc *sc sockaddr_ntop(sintosa(&ifra.ifra_dstaddr), str[2], sizeof(str[2]))); } - return rv; + return 0; } +#ifdef INET6 +int +umb_add_inet6_config(struct umb_softc *sc, struct in6_addr *ip, u_int prefixlen, + struct in6_addr *gw) +{ + struct ifnet *ifp = GET_IFP(sc); + struct in6_aliasreq ifra; + struct sockaddr_in6 *sin6, default_sin6; + struct rt_addrinfo info; + struct rtentry *rt; + int rv; + + memset(&ifra, 0, sizeof (ifra)); + sin6 = &ifra.ifra_addr; + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof (*sin6); + memcpy(&sin6->sin6_addr, ip, sizeof (sin6->sin6_addr)); + + sin6 = &ifra.ifra_dstaddr; + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof (*sin6); + memcpy(&sin6->sin6_addr, gw, sizeof (sin6->sin6_addr)); + + /* XXX: in6_update_ifa() accepts only 128 bits for P2P interfaces. */ + prefixlen = 128; + + sin6 = &ifra.ifra_prefixmask; + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof (*sin6); + in6_prefixlen2mask(&sin6->sin6_addr, prefixlen); + + ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + + rv = in6_ioctl(SIOCAIFADDR_IN6, (caddr_t)&ifra, ifp, 1); + if (rv != 0) { + printf("%s: unable to set IPv6 address, error %d\n", + DEVNAM(ifp->if_softc), rv); + return rv; + } + + memset(&default_sin6, 0, sizeof(default_sin6)); + default_sin6.sin6_family = AF_INET6; + default_sin6.sin6_len = sizeof (default_sin6); + + memset(&info, 0, sizeof(info)); + info.rti_flags = RTF_GATEWAY /* maybe | RTF_STATIC */; + info.rti_ifa = ifa_ifwithaddr(sin6tosa(&ifra.ifra_addr), + ifp->if_rdomain); + info.rti_info[RTAX_DST] = sin6tosa(&default_sin6); + info.rti_info[RTAX_NETMASK] = sin6tosa(&default_sin6); + info.rti_info[RTAX_GATEWAY] = sin6tosa(&ifra.ifra_dstaddr); + + NET_LOCK(); + rv = rtrequest(RTM_ADD, &info, 0, &rt, ifp->if_rdomain); + NET_UNLOCK(); + if (rv) { + printf("%s: unable to set IPv6 default route, " + "error %d\n", DEVNAM(ifp->if_softc), rv); + rtm_miss(RTM_MISS, &info, 0, RTP_NONE, 0, rv, + ifp->if_rdomain); + } else { + /* Inform listeners of the new route */ + rtm_send(rt, RTM_ADD, rv, ifp->if_rdomain); + rtfree(rt); + } + + if (ifp->if_flags & IFF_DEBUG) { + char str[3][INET6_ADDRSTRLEN]; + log(LOG_INFO, "%s: IPv6 addr %s, mask %s, gateway %s\n", + DEVNAM(ifp->if_softc), + sockaddr_ntop(sin6tosa(&ifra.ifra_addr), str[0], + sizeof(str[0])), + sockaddr_ntop(sin6tosa(&ifra.ifra_prefixmask), str[1], + sizeof(str[1])), + sockaddr_ntop(sin6tosa(&ifra.ifra_dstaddr), str[2], + sizeof(str[2]))); + } + return 0; +} +#endif + void -umb_send_inet_proposal(struct umb_softc *sc) +umb_send_inet_proposal(struct umb_softc *sc, int af) { struct ifnet *ifp = GET_IFP(sc); struct sockaddr_rtdns rtdns; struct rt_addrinfo info; int i, flag = 0; + size_t sz = 0; memset(&rtdns, 0, sizeof(rtdns)); memset(&info, 0, sizeof(info)); for (i = 0; i < UMB_MAX_DNSSRV; i++) { - if (sc->sc_info.ipv4dns[i].s_addr == INADDR_ANY) - break; - memcpy(rtdns.sr_dns + i * sizeof(struct in_addr), - &sc->sc_info.ipv4dns[i], sizeof(struct in_addr)); - flag = RTF_UP; + if (af == AF_INET) { + sz = sizeof (sc->sc_info.ipv4dns[i]); + if (sc->sc_info.ipv4dns[i].s_addr == INADDR_ANY) + break; + memcpy(rtdns.sr_dns + i * sz, &sc->sc_info.ipv4dns[i], + sz); + flag = RTF_UP; +#ifdef INET6 + } if (af == AF_INET6) { + sz = sizeof (sc->sc_info.ipv6dns[i]); + if (IN6_ARE_ADDR_EQUAL(&sc->sc_info.ipv6dns[i], + &in6addr_any)) + break; + memcpy(rtdns.sr_dns + i * sz, &sc->sc_info.ipv6dns[i], + sz); + flag = RTF_UP; +#endif + } } - rtdns.sr_family = AF_INET; - rtdns.sr_len = 2 + i * sizeof(struct in_addr); + rtdns.sr_family = af; + rtdns.sr_len = 2 + i * sz; info.rti_info[RTAX_DNS] = srtdnstosa(&rtdns); rtm_proposal(ifp, &info, flag, RTP_PROPOSAL_UMB); @@ -1732,7 +1845,7 @@ umb_decode_ip_configuration(struct umb_s struct mbim_cid_ip_configuration_info *ic = data; struct ifnet *ifp = GET_IFP(sc); int s; - uint32_t avail; + uint32_t avail_v4; uint32_t val; int n, i; int off; @@ -1740,6 +1853,12 @@ umb_decode_ip_configuration(struct umb_s struct in_addr addr, gw; int state = -1; int rv; + int hasmtu = 0; +#ifdef INET6 + uint32_t avail_v6; + struct mbim_cid_ipv6_element ipv6elem; + struct in6_addr addr6, gw6; +#endif if (len < sizeof (*ic)) return 0; @@ -1750,17 +1869,20 @@ umb_decode_ip_configuration(struct umb_s } s = splnet(); + memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns)); + memset(sc->sc_info.ipv6dns, 0, sizeof (sc->sc_info.ipv6dns)); + /* * IPv4 configuation */ - avail = letoh32(ic->ipv4_available); - if ((avail & (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) == + avail_v4 = letoh32(ic->ipv4_available); + if ((avail_v4 & (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) == (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) { n = letoh32(ic->ipv4_naddr); off = letoh32(ic->ipv4_addroffs); if (n == 0 || off + sizeof (ipv4elem) > len) - goto done; + goto tryv6; if (n != 1 && ifp->if_flags & IFF_DEBUG) log(LOG_INFO, "%s: more than one IPv4 addr: %d\n", DEVNAM(ifp->if_softc), n); @@ -1780,12 +1902,12 @@ umb_decode_ip_configuration(struct umb_s } memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns)); - if (avail & MBIM_IPCONF_HAS_DNSINFO) { + if (avail_v4 & MBIM_IPCONF_HAS_DNSINFO) { n = letoh32(ic->ipv4_ndnssrv); off = letoh32(ic->ipv4_dnssrvoffs); i = 0; while (n-- > 0) { - if (off + sizeof (uint32_t) > len) + if (off + sizeof (addr) > len) break; memcpy(&addr, data + off, sizeof(addr)); if (i < UMB_MAX_DNSSRV) @@ -1798,29 +1920,93 @@ umb_decode_ip_configuration(struct umb_s &addr, str, sizeof(str))); } } - umb_send_inet_proposal(sc); + umb_send_inet_proposal(sc, AF_INET); } - - if ((avail & MBIM_IPCONF_HAS_MTUINFO)) { + if ((avail_v4 & MBIM_IPCONF_HAS_MTUINFO)) { val = letoh32(ic->ipv4_mtu); if (ifp->if_hardmtu != val && val <= sc->sc_maxpktlen) { + hasmtu = 1; ifp->if_hardmtu = val; if (ifp->if_mtu > val) ifp->if_mtu = val; - if (ifp->if_flags & IFF_DEBUG) - log(LOG_INFO, "%s: MTU %d\n", DEVNAM(sc), val); } } - avail = letoh32(ic->ipv6_available); - if ((ifp->if_flags & IFF_DEBUG) && avail & MBIM_IPCONF_HAS_ADDRINFO) { - /* XXX FIXME: IPv6 configuation missing */ - log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc)); +tryv6:; +#ifdef INET6 + /* + * IPv6 configuation + */ + avail_v6 = letoh32(ic->ipv6_available); + if (avail_v6 == 0) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: ISP or WWAN module offers no IPv6 " + "support\n", DEVNAM(ifp->if_softc)); + goto done; + } + + if ((avail_v4 & (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) == + (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) { + n = letoh32(ic->ipv6_naddr); + off = letoh32(ic->ipv6_addroffs); + + if (n == 0 || off + sizeof (ipv6elem) > len) + goto done; + if (n != 1 && ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: more than one IPv6 addr: %d\n", + DEVNAM(ifp->if_softc), n); + + /* Only pick the first one */ + memcpy(&ipv6elem, data + off, sizeof (ipv6elem)); + memcpy(&addr6, ipv6elem.addr, sizeof (addr6)); + + off = letoh32(ic->ipv6_gwoffs); + memcpy(&gw6, data + off, sizeof (gw6)); + + rv = umb_add_inet6_config(sc, &addr6, ipv6elem.prefixlen, &gw6); + if (rv == 0) + state = UMB_S_UP; + } + + if (avail_v6 & MBIM_IPCONF_HAS_DNSINFO) { + n = letoh32(ic->ipv6_ndnssrv); + off = letoh32(ic->ipv6_dnssrvoffs); + i = 0; + while (n-- > 0) { + if (off + sizeof (addr6) > len) + break; + memcpy(&addr6, data + off, sizeof(addr6)); + if (i < UMB_MAX_DNSSRV) + sc->sc_info.ipv6dns[i++] = addr6; + off += sizeof(addr6); + if (ifp->if_flags & IFF_DEBUG) { + char str[INET6_ADDRSTRLEN]; + log(LOG_INFO, "%s: IPv6 nameserver %s\n", + DEVNAM(ifp->if_softc), inet_ntop(AF_INET6, + &addr6, str, sizeof(str))); + } + } + umb_send_inet_proposal(sc, AF_INET6); + } + + if ((avail_v6 & MBIM_IPCONF_HAS_MTUINFO)) { + val = letoh32(ic->ipv6_mtu); + if (ifp->if_hardmtu != val && val <= sc->sc_maxpktlen) { + hasmtu = 1; + ifp->if_hardmtu = val; + if (ifp->if_mtu > val) + ifp->if_mtu = val; + } } +#endif + +done: + if (hasmtu && (ifp->if_flags & IFF_DEBUG)) + log(LOG_INFO, "%s: MTU %d\n", DEVNAM(sc), ifp->if_hardmtu); + if (state != -1) umb_newstate(sc, state, 0); -done: splx(s); return 1; } @@ -2392,7 +2578,12 @@ umb_send_connect(struct umb_softc *sc, i c->passwd_size = htole32(0); c->authprot = htole32(MBIM_AUTHPROT_NONE); c->compression = htole32(MBIM_COMPRESSION_NONE); +#ifdef INET6 + /* XXX FIXME: support IPv6-only mode, too */ + c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4V6); +#else c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4); +#endif memcpy(c->context, umb_uuid_context_internet, sizeof (c->context)); umb_cmd(sc, MBIM_CID_CONNECT, MBIM_CMDOP_SET, c, off); done: Index: sys/dev/usb/if_umb.h =================================================================== RCS file: /cvs/src/sys/dev/usb/if_umb.h,v retrieving revision 1.5 diff -u -p -u -p -r1.5 if_umb.h --- sys/dev/usb/if_umb.h 26 Aug 2019 15:23:01 -0000 1.5 +++ sys/dev/usb/if_umb.h 23 Jan 2020 09:24:38 -0000 @@ -321,6 +321,7 @@ struct umb_info { #define UMB_MAX_DNSSRV 2 struct in_addr ipv4dns[UMB_MAX_DNSSRV]; + struct in6_addr ipv6dns[UMB_MAX_DNSSRV]; }; #ifdef _KERNEL @@ -380,6 +381,6 @@ struct umb_softc { #define sc_state sc_info.state #define sc_roaming sc_info.enable_roaming - struct umb_info sc_info; + struct umb_info sc_info; }; #endif /* _KERNEL */