Introduce 'auto-join' to the wifi 802.11 stack.
authorphessler <phessler@openbsd.org>
Wed, 11 Jul 2018 20:18:09 +0000 (20:18 +0000)
committerphessler <phessler@openbsd.org>
Wed, 11 Jul 2018 20:18:09 +0000 (20:18 +0000)
This allows a system to remember which ESSIDs it wants to connect to, any
relevant security configuration, and switch to it when the network we are
currently connected to is no longer available.

Works when connecting and switching between WPA2/WPA1/WEP/clear encryptions.

example hostname.if:
join home wpakey password
join work wpakey mekmitasdigoat
join open-lounge
join cafe wpakey cafe2018
join "wepnetwork" nwkey "12345"
dhcp
inet6 autoconf
up

OK stsp@ reyk@
and enthusiasm from every hackroom I've been in for the last 3 years

sbin/ifconfig/ifconfig.8
sbin/ifconfig/ifconfig.c
sys/net80211/ieee80211_ioctl.c
sys/net80211/ieee80211_ioctl.h
sys/net80211/ieee80211_node.c
sys/net80211/ieee80211_node.h
sys/net80211/ieee80211_var.h

index 95622f0..951b046 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ifconfig.8,v 1.308 2018/05/05 16:52:59 jmc Exp $
+.\"    $OpenBSD: ifconfig.8,v 1.309 2018/07/11 20:18:09 phessler Exp $
 .\"    $NetBSD: ifconfig.8,v 1.11 1996/01/04 21:27:29 pk Exp $
 .\"     $FreeBSD: ifconfig.8,v 1.16 1998/02/01 07:03:29 steve Exp $
 .\"
@@ -31,7 +31,7 @@
 .\"
 .\"     @(#)ifconfig.8 8.4 (Berkeley) 6/1/94
 .\"
-.Dd $Mdocdate: May 5 2018 $
+.Dd $Mdocdate: July 11 2018 $
 .Dt IFCONFIG 8
 .Os
 .Sh NAME
@@ -897,6 +897,7 @@ will begin advertising as master.
 .Op Oo Fl Oc Ns Cm chan Op Ar n
 .Op Oo Fl Oc Ns Cm nwflag Ar flag
 .Op Oo Fl Oc Ns Cm nwid Ar id
+.Op Oo Fl Oc Ns Cm join Op Ar id
 .Op Oo Fl Oc Ns Cm nwkey Ar key
 .Op Oo Fl Oc Ns Cm powersave Op Ar duration
 .Op Cm scan
@@ -969,6 +970,24 @@ Note that network ID is synonymous with Extended Service Set ID (ESSID).
 .It Cm -nwid
 Set the network ID to the empty string to allow the interface to connect
 to any available access point.
+.It Cm join Op Ar id
+Configure network ID.
+The
+.Ar id
+will be used to auto-join wireless networks.
+The
+.Ar id
+can either be any text string up to 32 characters in length,
+or a series of hexadecimal digits up to 64 digits.
+Any necessary
+.Cm wpakey
+or
+.Cm nwkey
+arguments should be specified on the same line.
+.Pp
+If no
+.Ar id
+is specified, show the list of currently configured auto-join networks.
 .It Cm nwkey Ar key
 Enable WEP encryption using the specified
 .Ar key .
index b741383..af15dac 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ifconfig.c,v 1.367 2018/05/28 08:53:35 kn Exp $       */
+/*     $OpenBSD: ifconfig.c,v 1.368 2018/07/11 20:18:09 phessler Exp $ */
 /*     $NetBSD: ifconfig.c,v 1.40 1997/10/01 02:19:43 enami Exp $      */
 
 /*
@@ -185,6 +185,8 @@ void        setifbroadaddr(const char *, int);
 void   setifmtu(const char *, int);
 void   setifllprio(const char *, int);
 void   setifnwid(const char *, int);
+void   setifjoin(const char *, int);
+void   delifjoin(const char *, int);
 void   setifbssid(const char *, int);
 void   setifnwkey(const char *, int);
 void   setifwpa(const char *, int);
@@ -373,6 +375,8 @@ const struct        cmd {
        { "mtu",        NEXTARG,        0,              setifmtu },
        { "nwid",       NEXTARG,        0,              setifnwid },
        { "-nwid",      -1,             0,              setifnwid },
+       { "join",       NEXTARG0,       0,              setifjoin },
+       { "-join",      NEXTARG0,       0,              delifjoin },
        { "bssid",      NEXTARG,        0,              setifbssid },
        { "-bssid",     -1,             0,              setifbssid },
        { "nwkey",      NEXTARG,        0,              setifnwkey },
@@ -659,6 +663,9 @@ const struct afswtch {
 
 const struct afswtch *afp;     /*the address family being set or asked about*/
 
+char joinname[IEEE80211_NWID_LEN];
+char nwidname[IEEE80211_NWID_LEN];
+
 int ifaliases = 0;
 int aflag = 0;
 
@@ -1636,11 +1643,74 @@ setifnwid(const char *val, int d)
        }
        nwid.i_len = len;
        (void)strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+       (void)strlcpy(nwidname, nwid.i_nwid, sizeof(nwidname));
        ifr.ifr_data = (caddr_t)&nwid;
        if (ioctl(s, SIOCS80211NWID, (caddr_t)&ifr) < 0)
                warn("SIOCS80211NWID");
 }
 
+void
+setifjoin(const char *val, int d)
+{
+       struct ieee80211_join join;
+       int len;
+
+       if (val == NULL) {
+               /* TODO: display the list of join'd networks */
+               return;
+       }
+
+       if (d != 0) {
+               /* no network id is especially desired */
+               memset(&join, 0, sizeof(join));
+               len = 0;
+       } else {
+               len = sizeof(join.i_nwid);
+               if (get_string(val, NULL, join.i_nwid, &len) == NULL)
+                       return;
+       }
+       join.i_len = len;
+       (void)strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+       (void)strlcpy(joinname, join.i_nwid, sizeof(joinname));
+       ifr.ifr_data = (caddr_t)&join;
+       if (ioctl(s, SIOCS80211JOIN, (caddr_t)&ifr) < 0)
+               warn("SIOCS80211JOIN");
+}
+
+void
+delifjoin(const char *val, int d)
+{
+       struct ieee80211_join join;
+       int len;
+
+       memset(&join, 0, sizeof(join));
+       len = 0;
+       join.i_flags |= IEEE80211_JOIN_DEL;
+
+       if (val == NULL) {
+               ifr.ifr_data = (caddr_t)&join;
+               if (ioctl(s, SIOCS80211JOIN, (caddr_t)&ifr) < 0)
+                       warn("SIOCS80211JOIN");
+               return;
+       }
+
+       if (d != 0) {
+               /* no network id is especially desired */
+               memset(&join, 0, sizeof(join));
+               len = 0;
+       } else {
+               len = sizeof(join.i_nwid);
+               if (val != NULL &&
+                   get_string(val, NULL, join.i_nwid, &len) == NULL)
+                       return;
+       }
+       join.i_len = len;
+       (void)strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+       ifr.ifr_data = (caddr_t)&join;
+       if (ioctl(s, SIOCS80211JOIN, (caddr_t)&ifr) < 0)
+               warn("SIOCS80211JOIN");
+}
+
 void
 setifbssid(const char *val, int d)
 {
@@ -1923,8 +1993,20 @@ setifwpakey(const char *val, int d)
                memset(&ifr, 0, sizeof(ifr));
                ifr.ifr_data = (caddr_t)&nwid;
                strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
-               if (ioctl(s, SIOCG80211NWID, (caddr_t)&ifr))
-                       err(1, "SIOCG80211NWID");
+
+               /* Use the value specified in 'join' or 'nwid' */
+               if (strlen(joinname) != 0) {
+                       strlcpy(nwid.i_nwid, joinname, sizeof(nwid.i_nwid));
+                       nwid.i_len = strlen(joinname);
+               } else if (strlen(nwidname) != 0) {
+                       strlcpy(nwid.i_nwid, nwidname, sizeof(nwid.i_nwid));
+                       nwid.i_len = strlen(nwidname);
+               } else {
+                       warnx("no nwid or join command, guessing nwid to use");
+
+                       if (ioctl(s, SIOCG80211NWID, (caddr_t)&ifr))
+                               err(1, "SIOCG80211NWID");
+               }
 
                passlen = strlen(val);
                if (passlen == 2 + 2 * sizeof(psk.i_psk) &&
@@ -2082,9 +2164,10 @@ print_cipherset(u_int32_t cipherset)
 void
 ieee80211_status(void)
 {
-       int len, i, nwkey_verbose, inwid, inwkey, ipsk, ichan, ipwr;
+       int len, i, nwkey_verbose, inwid, ijoin, inwkey, ipsk, ichan, ipwr;
        int ibssid, iwpa;
        struct ieee80211_nwid nwid;
+       struct ieee80211_join join;
        struct ieee80211_nwkey nwkey;
        struct ieee80211_wpapsk psk;
        struct ieee80211_power power;
@@ -2102,6 +2185,10 @@ ieee80211_status(void)
        strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
        inwid = ioctl(s, SIOCG80211NWID, (caddr_t)&ifr);
 
+       ifr.ifr_data = (caddr_t)&join;
+       strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+       ijoin = ioctl(s, SIOCG80211JOIN, (caddr_t)&ifr);
+
        memset(&nwkey, 0, sizeof(nwkey));
        strlcpy(nwkey.i_name, name, sizeof(nwkey.i_name));
        inwkey = ioctl(s, SIOCG80211NWKEY, (caddr_t)&nwkey);
@@ -2127,8 +2214,8 @@ ieee80211_status(void)
        iwpa = ioctl(s, SIOCG80211WPAPARMS, &wpa);
 
        /* check if any ieee80211 option is active */
-       if (inwid == 0 || inwkey == 0 || ipsk == 0 || ipwr == 0 ||
-           ichan == 0 || ibssid == 0 || iwpa == 0)
+       if (inwid == 0 || ijoin == 0 || inwkey == 0 || ipsk == 0 ||
+           ipwr == 0 || ichan == 0 || ibssid == 0 || iwpa == 0)
                fputs("\tieee80211:", stdout);
        else
                return;
@@ -2138,7 +2225,10 @@ ieee80211_status(void)
                len = nwid.i_len;
                if (len > IEEE80211_NWID_LEN)
                        len = IEEE80211_NWID_LEN;
-               fputs(" nwid ", stdout);
+               if (ijoin == 0 && join.i_flags & IEEE80211_JOIN_FOUND)
+                       fputs(" join ", stdout);
+               else
+                       fputs(" nwid ", stdout);
                print_string(nwid.i_nwid, len);
        }
 
index d934bb4..4d6b7eb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_ioctl.c,v 1.60 2018/04/26 12:50:07 pirofti Exp $    */
+/*     $OpenBSD: ieee80211_ioctl.c,v 1.61 2018/07/11 20:18:09 phessler Exp $   */
 /*     $NetBSD: ieee80211_ioctl.c,v 1.15 2004/05/06 02:58:16 dyoung Exp $      */
 
 /*-
@@ -392,6 +392,8 @@ ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
        struct ifreq *ifr = (struct ifreq *)data;
        int i, error = 0;
        struct ieee80211_nwid nwid;
+       struct ieee80211_join join;
+       struct ieee80211_ess *ess;
        struct ieee80211_wpapsk *psk;
        struct ieee80211_keyavail *ka;
        struct ieee80211_keyrun *kr;
@@ -445,10 +447,53 @@ ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
                }
                error = copyout(&nwid, ifr->ifr_data, sizeof(nwid));
                break;
+       case SIOCS80211JOIN:
+               if ((error = suser(curproc)) != 0)
+                       break;
+               if ((error = copyin(ifr->ifr_data, &join, sizeof(join))) != 0)
+                       break;
+               if (join.i_len > IEEE80211_NWID_LEN) {
+                       error = EINVAL;
+                       break;
+               }
+               if (join.i_flags & IEEE80211_JOIN_DEL)
+                       ieee80211_del_ess(ic, join.i_nwid, join.i_len ? 0 : 1);
+               memset(ic->ic_des_essid, 0, IEEE80211_NWID_LEN);
+               ic->ic_des_esslen = join.i_len;
+               memcpy(ic->ic_des_essid, join.i_nwid, join.i_len);
+               /* disable WPA/WEP */
+               ieee80211_disable_rsn(ic);
+               ieee80211_disable_wep(ic);
+               /* save nwid for auto-join */
+               if (!(join.i_flags & IEEE80211_JOIN_DEL))
+                       ieee80211_add_ess(ic, ic->ic_des_essid, 0, 0);
+               ieee80211_set_ess(ic, ic->ic_des_essid);
+               error = ENETRESET;
+               break;
+       case SIOCG80211JOIN:
+               memset(&join, 0, sizeof(join));
+               error = ENOENT;
+               if (ic->ic_bss == NULL)
+                       break;
+               TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+                       if (memcmp(ess->essid, ic->ic_bss->ni_essid,
+                           IEEE80211_NWID_LEN) == 0) {
+                               join.i_len = ic->ic_bss->ni_esslen;
+                               memcpy(join.i_nwid, ic->ic_bss->ni_essid,
+                                   join.i_len);
+                               join.i_flags = IEEE80211_JOIN_FOUND;
+                               error = copyout(&join, ifr->ifr_data,
+                                   sizeof(join));
+                               break;
+                       }
+               }
+               break;
        case SIOCS80211NWKEY:
                if ((error = suser(curproc)) != 0)
                        break;
                error = ieee80211_ioctl_setnwkeys(ic, (void *)data);
+               if (error == ENETRESET)
+                       ieee80211_add_ess(ic, ic->ic_des_essid, 0, 1);
                break;
        case SIOCG80211NWKEY:
                error = ieee80211_ioctl_getnwkeys(ic, (void *)data);
@@ -457,6 +502,8 @@ ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
                if ((error = suser(curproc)) != 0)
                        break;
                error = ieee80211_ioctl_setwpaparms(ic, (void *)data);
+               if (error == ENETRESET)
+                       ieee80211_add_ess(ic, ic->ic_des_essid, 1, 0);
                break;
        case SIOCG80211WPAPARMS:
                error = ieee80211_ioctl_getwpaparms(ic, (void *)data);
@@ -474,6 +521,7 @@ ieee80211_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
                        ic->ic_flags &= ~IEEE80211_F_PSK;
                        memset(ic->ic_psk, 0, sizeof(ic->ic_psk));
                }
+               ieee80211_add_ess(ic, ic->ic_des_essid, 1, 0);
                error = ENETRESET;
                break;
        case SIOCG80211WPAPSK:
index 3ed20bb..9ea7412 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_ioctl.h,v 1.31 2017/10/27 12:22:40 jsg Exp $        */
+/*     $OpenBSD: ieee80211_ioctl.h,v 1.32 2018/07/11 20:18:09 phessler Exp $   */
 /*     $NetBSD: ieee80211_ioctl.h,v 1.7 2004/04/30 22:51:04 dyoung Exp $       */
 
 /*-
@@ -275,6 +275,25 @@ struct ieee80211_keyrun {
 
 #define SIOCS80211SCAN          _IOW('i', 210, struct ifreq)
 
+#define        SIOCS80211JOIN          _IOWR('i', 255, struct ifreq)
+#define        SIOCG80211JOIN          _IOWR('i', 256, struct ifreq)
+
+/* join is pointed at by ifr.ifr_data */
+struct ieee80211_join {
+       u_int8_t        i_len;  /* length of i_nwid */
+       u_int8_t        i_nwid[IEEE80211_NWID_LEN];
+       u_int32_t       i_flags;
+
+       struct ieee80211_wpapsk i_wpapsk;
+       struct ieee80211_nwkey  i_nwkey;
+};
+
+#define IEEE80211_JOIN_SHOW    0x01
+#define IEEE80211_JOIN_FOUND   0x02
+#define IEEE80211_JOIN_DEL     0x04
+#define IEEE80211_JOIN_NWKEY   0x08
+#define IEEE80211_JOIN_WPA     0x10
+
 /* node and requests */
 struct ieee80211_nodereq {
        char            nr_ifname[IFNAMSIZ];            /* e.g. "ath0" */
index 3eba6e9..b5dbc7c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.c,v 1.129 2018/04/28 14:49:07 stsp Exp $       */
+/*     $OpenBSD: ieee80211_node.c,v 1.130 2018/07/11 20:18:09 phessler Exp $   */
 /*     $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $       */
 
 /*-
@@ -67,6 +67,8 @@ u_int8_t ieee80211_node_getrssi(struct ieee80211com *,
     const struct ieee80211_node *);
 int ieee80211_node_checkrssi(struct ieee80211com *,
     const struct ieee80211_node *);
+int ieee80211_ess_is_better(struct ieee80211com *ic, struct ieee80211_node *,
+    struct ieee80211_node *);
 void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
     const u_int8_t *);
 void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
@@ -119,6 +121,331 @@ ieee80211_node_cache_timeout(void *arg)
 }
 #endif
 
+/*
+ * For debug purposes
+ */
+void
+ieee80211_print_ess(struct ieee80211_ess *ess)
+{
+       ieee80211_print_essid(ess->essid, ess->esslen);
+       if (ess->flags & IEEE80211_F_RSNON) {
+               printf(" wpa");
+               if (ess->rsnprotos & IEEE80211_PROTO_RSN)
+                       printf(",wpa2");
+               if (ess->rsnprotos & IEEE80211_PROTO_WPA)
+                       printf(",wpa1");
+
+               if (ess->rsnakms & IEEE80211_AKM_8021X ||
+                   ess->rsnakms & IEEE80211_AKM_SHA256_8021X)
+                       printf(",802.1x");
+               printf(" ");
+
+               if (ess->rsnciphers & IEEE80211_CIPHER_USEGROUP)
+                       printf("usegroup");
+               if (ess->rsnciphers & IEEE80211_CIPHER_WEP40)
+                       printf("wep40");
+               if (ess->rsnciphers & IEEE80211_CIPHER_WEP104)
+                       printf("wep104");
+               if (ess->rsnciphers & IEEE80211_CIPHER_TKIP)
+                       printf("tkip");
+               if (ess->rsnciphers & IEEE80211_CIPHER_CCMP)
+                       printf("ccmp");
+       }
+       if (ess->flags & IEEE80211_F_WEPON) {
+               int i = ess->def_txkey;
+
+               printf(" wep,");
+               if (ess->nw_keys[i].k_cipher & IEEE80211_CIPHER_WEP40)
+                       printf("wep40");
+               if (ess->nw_keys[i].k_cipher & IEEE80211_CIPHER_WEP104)
+                       printf("wep104");
+       }
+       if (ess->flags == 0)
+               printf(" clear");
+       printf("\n");
+}
+
+void
+ieee80211_print_ess_list(struct ieee80211com *ic)
+{
+       struct ifnet            *ifp = &ic->ic_if;
+       struct ieee80211_ess    *ess;
+
+       printf("%s: known networks\n", ifp->if_xname);
+       TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+               ieee80211_print_ess(ess);
+       }
+}
+
+void
+ieee80211_del_ess(struct ieee80211com *ic, char *nwid, int all)
+{
+       struct ieee80211_ess *ess, *next;
+
+       TAILQ_FOREACH_SAFE(ess, &ic->ic_ess, ess_next, next) {
+               if (all == 1 || (memcmp(ess->essid, nwid,
+                   IEEE80211_NWID_LEN) == 0)) {
+                       TAILQ_REMOVE(&ic->ic_ess, ess, ess_next);
+                       explicit_bzero(ess, sizeof(*ess));
+                       free(ess, M_DEVBUF, sizeof(*ess));
+                       if (all != 1)
+                               return;
+               }
+       }
+
+}
+
+int
+ieee80211_add_ess(struct ieee80211com *ic, char *nwid, int wpa, int wep)
+{
+       struct ieee80211_ess *ess;
+       int i = 0, new = 0, ness = 0;
+
+       /* Don't save an empty nwid */
+       if (strnlen(nwid, IEEE80211_NWID_LEN) == 0)
+               return (0);
+
+       TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+               if (memcmp(ess->essid, nwid, IEEE80211_NWID_LEN) == 0)
+                       break;
+               ness++;
+       }
+
+       KASSERTMSG(wpa == 0 || wep == 0,
+           "%s: both wpa and wep are configured", __func__);
+
+       if (ess == NULL) {
+               /* if not found, and wpa/wep are set, then return */
+               if (wpa != 0 || wep != 0) {
+                       return (ENOENT);
+               }
+               if (ness > IEEE80211_CACHE_SIZE)
+                       return (ERANGE);
+               new = 1;
+               ess = malloc(sizeof(*ess), M_DEVBUF, M_NOWAIT|M_ZERO);
+               if (ess == NULL)
+                       return (ENOMEM);
+       }
+
+       memcpy(ess->essid, nwid, ic->ic_des_esslen);
+       ess->esslen = ic->ic_des_esslen;
+
+       if (wpa) {
+               if (ic->ic_flags & (IEEE80211_F_RSNON|IEEE80211_F_PSK)) {
+                       ess->flags = IEEE80211_F_RSNON;
+                       if (ic->ic_flags & IEEE80211_F_PSK)
+                               ess->flags |= IEEE80211_F_PSK;
+                       explicit_bzero(ess->psk, sizeof(ess->psk));
+                       memcpy(ess->psk, ic->ic_psk, IEEE80211_PMK_LEN);
+                       ess->rsnprotos = ic->ic_rsnprotos;
+                       ess->rsnakms = ic->ic_rsnakms;
+                       ess->rsngroupcipher = ic->ic_rsngroupcipher;
+                       ess->rsnciphers = ic->ic_rsnciphers;
+
+                       /* Disable WEP */
+                       for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+                               explicit_bzero(&ess->nw_keys[i],
+                                   sizeof(ess->nw_keys[0]));
+                       }
+                       ess->def_txkey = 0;
+                       ess->flags &= ~IEEE80211_F_WEPON;
+               } else {
+                       /* Disable WPA */
+                       ess->rsnprotos = ess->rsnakms =
+                           ess->rsngroupcipher = ess->rsnciphers = 0;
+                       explicit_bzero(ess->psk, sizeof(ess->psk));
+                       ess->flags &= ~(IEEE80211_F_PSK | IEEE80211_F_RSNON);
+               }
+       } else if (wep) {
+               if (ic->ic_flags & IEEE80211_F_WEPON) {
+                       struct ieee80211_key    *k;
+                       int i;
+
+                       ess->flags = IEEE80211_F_WEPON;
+                       for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+                               memcpy(&ess->nw_keys[i], &ic->ic_nw_keys[i],
+                                   sizeof(struct ieee80211_key));
+                               k = &ic->ic_nw_keys[i];
+                               k->k_priv = NULL;
+                       }
+                       ess->def_txkey = ic->ic_def_txkey;
+
+                       /* Disable WPA */
+                       ess->rsnprotos = ess->rsnakms =
+                           ess->rsngroupcipher = ess->rsnciphers = 0;
+                       explicit_bzero(ess->psk, sizeof(ess->psk));
+                       ess->flags &= ~(IEEE80211_F_PSK | IEEE80211_F_RSNON);
+               } else {
+                       /* Disable WEP */
+                       for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+                               explicit_bzero(&ess->nw_keys[i],
+                                   sizeof(ess->nw_keys[0]));
+                       }
+                       ess->def_txkey = 0;
+                       ess->flags &= ~IEEE80211_F_WEPON;
+               }
+       }
+
+       if (new)
+               TAILQ_INSERT_TAIL(&ic->ic_ess, ess, ess_next);
+
+       return (0);
+}
+
+int
+ieee80211_ess_is_better(struct ieee80211com *ic, struct ieee80211_node *nicur,
+    struct ieee80211_node *selni)
+{
+       uint8_t                  min_5ghz_rssi;
+
+       if (ic->ic_max_rssi)
+               min_5ghz_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ;
+       else
+               min_5ghz_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ;
+
+       if (selni == NULL)
+               return 1;
+
+       /* First 5GHz with acceptable signal */
+       if ((IEEE80211_IS_CHAN_5GHZ(nicur->ni_chan) &&
+           !IEEE80211_IS_CHAN_5GHZ(selni->ni_chan)) &&
+           nicur->ni_rssi > min_5ghz_rssi)
+               return 1;
+
+       /* Prefer 5GHz N over not-N */
+       if ((IEEE80211_IS_CHAN_5GHZ(nicur->ni_chan) &&
+           nicur->ni_rssi > min_5ghz_rssi) &&
+           ieee80211_node_supports_ht(nicur))
+               return 1;
+
+       /* Settle for just N */
+       if (ieee80211_node_supports_ht(nicur) &&
+           !ieee80211_node_supports_ht(selni))
+               return 1;
+
+       /* Last resort of best rssi */
+       if (nicur->ni_rssi > selni->ni_rssi)
+               return 1;
+
+       return 0;
+}
+
+int
+ieee80211_match_ess(struct ieee80211com *ic)
+{
+       struct ifnet            *ifp = &ic->ic_if;
+       struct ieee80211_ess    *ess, *seless = NULL;
+       struct ieee80211_node   *ni, *selni = NULL;
+
+       if (!ISSET(ifp->if_flags, IFF_RUNNING))
+               return (0);
+
+       /*
+        * Apple's iOS uses the algorithm described at
+        * https://support.apple.com/en-us/HT202831
+        *
+        * basically:
+        * last network recently joined
+        * then strongest security
+        * then rssi level
+        */
+
+       /* skip if it's the same network */
+       TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+               if (LINK_STATE_IS_UP(ifp->if_link_state) &&
+                   ess->esslen == ic->ic_des_esslen &&
+                   (memcmp(ic->ic_des_essid, ess->essid,
+                    IEEE80211_NWID_LEN) == 0)) {
+                       if (ifp->if_flags & IFF_DEBUG) {
+                               printf(" %s: staying on ",
+                                   ifp->if_xname);
+                               ieee80211_print_essid(ess->essid, ess->esslen);
+                               printf("\n");
+                       }
+                       return (0);
+               }
+       }
+
+       TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+               RBT_FOREACH(ni, ieee80211_tree, &ic->ic_tree) {
+                       if (memcmp(ess->essid, ni->ni_essid,
+                           IEEE80211_NWID_LEN) != 0 ||
+                           ni->ni_fails != 0)
+                               continue;
+
+                       if (selni == NULL ||
+                           ieee80211_ess_is_better(ic, ni, selni) > 1) {
+                               seless = ess;
+                               selni = ni;
+                       }
+               }
+       }
+
+       if (seless && !(seless->esslen == ic->ic_des_esslen &&
+           (memcmp(ic->ic_des_essid, seless->essid,
+            IEEE80211_NWID_LEN) == 0))) {
+               ieee80211_set_ess(ic, seless->essid);
+               return (1);
+       }
+
+       return (0);
+}
+void
+ieee80211_set_ess(struct ieee80211com *ic, char *nwid)
+{
+       struct ifnet            *ifp = &ic->ic_if;
+       struct ieee80211_ess    *ess;
+
+       TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) {
+               if (memcmp(ess->essid, nwid, IEEE80211_NWID_LEN) == 0)
+                       break;
+       }
+
+       if (ess == NULL)
+               return;
+
+       memset(ic->ic_des_essid, 0, IEEE80211_NWID_LEN);
+       ic->ic_des_esslen = ess->esslen;
+       memcpy(ic->ic_des_essid, ess->essid, ic->ic_des_esslen);
+
+       ieee80211_disable_wep(ic);
+       ieee80211_disable_rsn(ic);
+       if (ess->flags & IEEE80211_F_RSNON) {
+               explicit_bzero(ic->ic_psk, sizeof(ic->ic_psk));
+               memcpy(ic->ic_psk, ess->psk, sizeof(ic->ic_psk));
+
+               ic->ic_rsnprotos = ess->rsnprotos;
+               ic->ic_rsnakms = ess->rsnakms;
+               ic->ic_rsngroupcipher = ess->rsngroupcipher;
+               ic->ic_rsnciphers = ess->rsnciphers;
+               ic->ic_flags |= IEEE80211_F_RSNON;
+               if (ess->flags & IEEE80211_F_PSK)
+                       ic->ic_flags |= IEEE80211_F_PSK;
+       } else if (ess->flags & IEEE80211_F_WEPON) {
+               struct ieee80211_key    *k;
+               int                      i;
+
+               for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+                       k = &ic->ic_nw_keys[i];
+                       if (k->k_cipher != IEEE80211_CIPHER_NONE)
+                               (*ic->ic_delete_key)(ic, NULL, k);
+                       memcpy(&ic->ic_nw_keys[i], &ess->nw_keys[i],
+                           sizeof(struct ieee80211_key));
+                       (*ic->ic_set_key)(ic, NULL, k);
+               }
+               ic->ic_def_txkey = ess->def_txkey;
+               ic->ic_flags |= IEEE80211_F_WEPON;
+       }
+
+       /* join the (new) ess station */
+       if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+               ieee80211_new_state(ic, IEEE80211_S_AUTH, -1);
+               ieee80211_media_change(ifp);
+       }
+
+       return;
+}
+
 void
 ieee80211_node_attach(struct ifnet *ifp)
 {
@@ -165,6 +492,7 @@ ieee80211_node_attach(struct ifnet *ifp)
                    ieee80211_node_cache_timeout, ic);
        }
 #endif
+       TAILQ_INIT(&ic->ic_ess);
 }
 
 struct ieee80211_node *
@@ -205,6 +533,7 @@ ieee80211_node_detach(struct ifnet *ifp)
                (*ic->ic_node_free)(ic, ic->ic_bss);
                ic->ic_bss = NULL;
        }
+       ieee80211_del_ess(ic, NULL, 1);
        ieee80211_free_allnodes(ic, 1);
 #ifndef IEEE80211_STA_ONLY
        free(ic->ic_aid_bitmap, M_DEVBUF,
@@ -740,6 +1069,10 @@ ieee80211_end_scan(struct ifnet *ifp)
                return;
        }
 
+       /* Possibly switch which ssid we are associated with */
+       if (!bgscan)
+               ieee80211_match_ess(ic);
+
        for (; ni != NULL; ni = nextbs) {
                nextbs = RBT_NEXT(ieee80211_tree, ni);
                if (ni->ni_fails) {
@@ -2194,7 +2527,18 @@ ieee80211_node_cmp(const struct ieee80211_node *b1,
        return (memcmp(b1->ni_macaddr, b2->ni_macaddr, IEEE80211_ADDR_LEN));
 }
 
+/*
+ * Compare nodes in the tree by essid
+ */
+int
+ieee80211_ess_cmp(const struct ieee80211_ess_rbt *b1,
+    const struct ieee80211_ess_rbt *b2)
+{
+       return (memcmp(b1->essid, b2->essid, IEEE80211_NWID_LEN));
+}
+
 /*
  * Generate red-black tree function logic
  */
 RBT_GENERATE(ieee80211_tree, ieee80211_node, ni_node, ieee80211_node_cmp);
+RBT_GENERATE(ieee80211_ess_tree, ieee80211_ess_rbt, ess_rbt, ieee80211_ess_cmp);
index 5a414fe..882df58 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.h,v 1.74 2018/04/28 14:49:07 stsp Exp $        */
+/*     $OpenBSD: ieee80211_node.h,v 1.75 2018/07/11 20:18:09 phessler Exp $    */
 /*     $NetBSD: ieee80211_node.h,v 1.9 2004/04/30 22:57:32 dyoung Exp $        */
 
 /*-
@@ -307,6 +307,17 @@ struct ieee80211_node {
 
 RBT_HEAD(ieee80211_tree, ieee80211_node);
 
+struct ieee80211_ess_rbt {
+       RBT_ENTRY(ieee80211_ess_rbt)     ess_rbt;
+       u_int8_t                         esslen;
+       u_int8_t                         essid[IEEE80211_NWID_LEN];
+       struct ieee80211_node           *ni2;
+       struct ieee80211_node           *ni5;
+       struct ieee80211_node           *ni;
+};
+
+RBT_HEAD(ieee80211_ess_tree, ieee80211_ess_rbt);
+
 static inline void
 ieee80211_node_incref(struct ieee80211_node *ni)
 {
@@ -412,6 +423,9 @@ void ieee80211_set_tim(struct ieee80211com *, int, int);
 
 int ieee80211_node_cmp(const struct ieee80211_node *,
                const struct ieee80211_node *);
+int ieee80211_ess_cmp(const struct ieee80211_ess_rbt *,
+               const struct ieee80211_ess_rbt *);
 RBT_PROTOTYPE(ieee80211_tree, ieee80211_node, ni_node, ieee80211_node_cmp);
+RBT_PROTOTYPE(ieee80211_ess_tree, ieee80211_ess_rbt, ess_rbt, ieee80211_ess_cmp);
 
 #endif /* _NET80211_IEEE80211_NODE_H_ */
index 63bedce..4914387 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_var.h,v 1.85 2018/04/26 12:50:07 pirofti Exp $      */
+/*     $OpenBSD: ieee80211_var.h,v 1.86 2018/07/11 20:18:09 phessler Exp $     */
 /*     $NetBSD: ieee80211_var.h,v 1.7 2004/05/06 03:07:10 dyoung Exp $ */
 
 /*-
@@ -334,6 +334,7 @@ struct ieee80211com {
        u_int8_t                ic_aselcaps;
        u_int8_t                ic_dialog_token;
        int                     ic_fixed_mcs;
+       TAILQ_HEAD(, ieee80211_ess)      ic_ess;
 };
 #define        ic_if           ic_ac.ac_if
 #define        ic_softc        ic_if.if_softc
@@ -341,6 +342,30 @@ struct ieee80211com {
 LIST_HEAD(ieee80211com_head, ieee80211com);
 extern struct ieee80211com_head ieee80211com_head;
 
+/* list of APs we want to automatically use */
+/* all data is copied from struct ieee80211com */
+struct ieee80211_ess {
+       /* nwid */
+       int                     esslen;
+       u_int8_t                essid[IEEE80211_NWID_LEN];
+
+       /* clear/wep/wpa */
+       u_int32_t               flags;
+
+       /* nwkey */
+       struct ieee80211_key    nw_keys[IEEE80211_GROUP_NKID];
+       int                     def_txkey;
+
+       /* wpakey */
+       u_int8_t                psk[IEEE80211_PMK_LEN];
+       u_int                   rsnprotos;
+       u_int                   rsnakms;
+       u_int                   rsnciphers;
+       enum ieee80211_cipher   rsngroupcipher;
+
+       TAILQ_ENTRY(ieee80211_ess) ess_next;
+};
+
 #define        IEEE80211_ADDR_EQ(a1,a2)        (memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
 #define        IEEE80211_ADDR_COPY(dst,src)    memcpy(dst,src,IEEE80211_ADDR_LEN)
 
@@ -426,6 +451,9 @@ enum ieee80211_phymode ieee80211_chan2mode(struct ieee80211com *,
                const struct ieee80211_channel *);
 void   ieee80211_disable_wep(struct ieee80211com *); 
 void   ieee80211_disable_rsn(struct ieee80211com *); 
+int    ieee80211_add_ess(struct ieee80211com *, char *, int, int);
+void   ieee80211_del_ess(struct ieee80211com *, char *, int);
+void   ieee80211_set_ess(struct ieee80211com *, char *);
 
 extern int ieee80211_cache_size;