]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
AP: Add support for Proxy ARP, DHCP snooping mechanism
authorKyeyoon Park <kyeyoonp@qca.qualcomm.com>
Fri, 26 Sep 2014 05:32:55 +0000 (22:32 -0700)
committerJouni Malinen <j@w1.fi>
Mon, 27 Oct 2014 23:08:29 +0000 (01:08 +0200)
Proxy ARP allows the AP devices to keep track of the hardware address to
IP address mapping of the STA devices within the BSS. When a request for
such information is made (i.e., ARP request, Neighbor Solicitation), the
AP will respond on behalf of the STA device within the BSS. Such
requests could originate from a device within the BSS or also from the
bridge. In the process of the AP replying to the request (i.e., ARP
reply, Neighbor Advertisement), the AP will drop the original request
frame. The relevant STA will not even know that such information was
ever requested.

This feature is a requirement for Hotspot 2.0, and is defined in IEEE
Std 802.11-2012, 10.23.13. While the Proxy ARP support code mainly
resides in the kernel bridge code, in order to optimize the performance
and simplify kernel implementation, the DHCP snooping code was added to
the hostapd.

Signed-off-by: Kyeyoon Park <kyeyoonp@qca.qualcomm.com>
12 files changed:
hostapd/Makefile
hostapd/config_file.c
hostapd/hostapd.conf
src/ap/ap_config.h
src/ap/dhcp_snoop.c [new file with mode: 0644]
src/ap/dhcp_snoop.h [new file with mode: 0644]
src/ap/hostapd.c
src/ap/hostapd.h
src/ap/ieee802_11.c
src/ap/ieee802_11_shared.c
src/ap/sta_info.c
src/ap/sta_info.h

index dc5eacdb7d425ef00aa7d5761db7560e989bdeab..86b6ea570f6e59c6385bb11d9dc2b462e9453729 100644 (file)
@@ -188,6 +188,11 @@ endif
 
 ifdef CONFIG_HS20
 NEED_AES_OMAC1=y
+CONFIG_PROXYARP=y
+endif
+
+ifdef CONFIG_PROXYARP
+CONFIG_L2_PACKET=y
 endif
 
 ifdef CONFIG_IEEE80211W
@@ -840,6 +845,11 @@ OBJS += ../src/common/gas.o
 OBJS += ../src/ap/gas_serv.o
 endif
 
+ifdef CONFIG_PROXYARP
+CFLAGS += -DCONFIG_PROXYARP
+OBJS += ../src/ap/dhcp_snoop.o
+endif
+
 OBJS += ../src/drivers/driver_common.o
 
 ifdef CONFIG_WPA_CLI_EDIT
index ddebf1f468029e43d9db6865b71d91d24fb487ac..f98e957a6debcf0b4b9d4c3776472555ddff6f7e 100644 (file)
@@ -3008,6 +3008,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                bss->hs20 = atoi(pos);
        } else if (os_strcmp(buf, "disable_dgaf") == 0) {
                bss->disable_dgaf = atoi(pos);
+       } else if (os_strcmp(buf, "proxy_arp") == 0) {
+               bss->proxy_arp = atoi(pos);
        } else if (os_strcmp(buf, "osen") == 0) {
                bss->osen = atoi(pos);
        } else if (os_strcmp(buf, "anqp_domain_id") == 0) {
index 2e6f841bfb74141af968633b337f9a7cd5da938d..eaff3ddea10af3c8d426152ffe09918880a7ae0d 100644 (file)
@@ -1453,6 +1453,11 @@ own_ip_addr=127.0.0.1
 # 1 = enabled
 #bss_transition=1
 
+# Proxy ARP
+# 0 = disabled (default)
+# 1 = enabled
+#proxy_arp=1
+
 ##### IEEE 802.11u-2011 #######################################################
 
 # Enable Interworking service
index 18538c92a244e295c75a87145146667962d201da..8a444016d23cc4dba8d6d742b88c7b4d272282c1 100644 (file)
@@ -484,6 +484,7 @@ struct hostapd_bss_config {
        unsigned int qos_map_set_len;
 
        int osen;
+       int proxy_arp;
 #ifdef CONFIG_HS20
        int hs20;
        int disable_dgaf;
diff --git a/src/ap/dhcp_snoop.c b/src/ap/dhcp_snoop.c
new file mode 100644 (file)
index 0000000..1bd4ba4
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * DHCP snooping for Proxy ARP
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include <linux/filter.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+
+#include "utils/common.h"
+#include "l2_packet/l2_packet.h"
+#include "hostapd.h"
+#include "sta_info.h"
+#include "ap_drv_ops.h"
+#include "dhcp_snoop.h"
+
+struct bootp_pkt {
+       struct iphdr iph;
+       struct udphdr udph;
+       u8 op;
+       u8 htype;
+       u8 hlen;
+       u8 hops;
+       be32 xid;
+       be16 secs;
+       be16 flags;
+       be32 client_ip;
+       be32 your_ip;
+       be32 server_ip;
+       be32 relay_ip;
+       u8 hw_addr[16];
+       u8 serv_name[64];
+       u8 boot_file[128];
+       u8 exten[312];
+};
+
+#define DHCPACK        5
+static const u8 ic_bootp_cookie[] = { 99, 130, 83, 99 };
+
+
+static void handle_dhcp(void *ctx, const u8 *src_addr, const u8 *buf,
+                       size_t len)
+{
+       struct hostapd_data *hapd = ctx;
+       const struct bootp_pkt *b;
+       struct sta_info *sta;
+       int exten_len;
+       const u8 *end, *pos;
+       int res, msgtype = 0, prefixlen = 32;
+       u32 subnet_mask = 0;
+
+       exten_len = len - ETH_HLEN - (sizeof(*b) - sizeof(b->exten));
+       if (exten_len < 4)
+               return;
+
+       b = (const struct bootp_pkt *) &buf[ETH_HLEN];
+       if (os_memcmp(b->exten, ic_bootp_cookie, ARRAY_SIZE(ic_bootp_cookie)))
+               return;
+
+       /* Parse DHCP options */
+       end = (const u8 *) b + ntohs(b->iph.tot_len);
+       pos = &b->exten[4];
+       while (pos < end && *pos != 0xff) {
+               const u8 *opt = pos++;
+
+               if (*opt == 0) /* padding */
+                       continue;
+
+               pos += *pos + 1;
+               if (pos >= end)
+                       break;
+
+               switch (*opt) {
+               case 1:  /* subnet mask */
+                       if (opt[1] == 4)
+                               subnet_mask = WPA_GET_BE32(&opt[2]);
+                       if (subnet_mask == 0)
+                               return;
+                       while (!(subnet_mask & 0x1)) {
+                               subnet_mask >>= 1;
+                               prefixlen--;
+                       }
+                       break;
+               case 53: /* message type */
+                       if (opt[1])
+                               msgtype = opt[2];
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (msgtype == DHCPACK) {
+               if (b->your_ip == 0)
+                       return;
+
+               /* DHCPACK for DHCPREQUEST */
+               sta = ap_get_sta(hapd, b->hw_addr);
+               if (!sta)
+                       return;
+
+               wpa_printf(MSG_DEBUG, "dhcp_snoop: Found DHCPACK for " MACSTR
+                          " @ IPv4 address %X/%d",
+                          MAC2STR(sta->addr), ntohl(b->your_ip), prefixlen);
+
+               if (sta->ipaddr == b->your_ip)
+                       return;
+
+               if (sta->ipaddr != 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "dhcp_snoop: Removing IPv4 address %X from the ip neigh table",
+                                  sta->ipaddr);
+                       hostapd_drv_br_delete_ip_neigh(hapd, sta->ipaddr);
+               }
+
+               res = hostapd_drv_br_add_ip_neigh(hapd, b->your_ip, prefixlen,
+                                                 sta->addr);
+               if (res) {
+                       wpa_printf(MSG_DEBUG,
+                                  "dhcp_snoop: Adding ip neigh table failed: %d",
+                                  res);
+                       return;
+               }
+               sta->ipaddr = b->your_ip;
+       }
+}
+
+
+int dhcp_snoop_init(struct hostapd_data *hapd)
+{
+       struct hostapd_bss_config *conf = hapd->conf;
+
+       if (!conf->isolate) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: ap_isolate must be enabled for DHCP snooping");
+               return -1;
+       }
+
+       if (conf->bridge[0] == '\0') {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Bridge must be configured for DHCP snooping");
+               return -1;
+       }
+
+       hapd->sock_dhcp = l2_packet_init(conf->bridge, NULL, ETH_P_ALL,
+                                        handle_dhcp, hapd, 1);
+       if (hapd->sock_dhcp == NULL) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Failed to initialize L2 packet processing for DHCP packet: %s",
+                          strerror(errno));
+               return -1;
+       }
+
+       if (l2_packet_set_packet_filter(hapd->sock_dhcp,
+                                       L2_PACKET_FILTER_DHCP)) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Failed to set L2 packet filter for DHCP: %s",
+                          strerror(errno));
+               return -1;
+       }
+
+       if (hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE,
+                                        1)) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Failed to enable hairpin_mode on the bridge port");
+               return -1;
+       }
+
+       if (hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_PROXYARP, 1)) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Failed to enable proxyarp on the bridge port");
+               return -1;
+       }
+
+       if (hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT,
+                                        1)) {
+               wpa_printf(MSG_DEBUG,
+                          "dhcp_snoop: Failed to enable accepting gratuitous ARP on the bridge");
+               return -1;
+       }
+
+       return 0;
+}
+
+
+void dhcp_snoop_deinit(struct hostapd_data *hapd)
+{
+       hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT, 0);
+       hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_PROXYARP, 0);
+       hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE, 0);
+       l2_packet_deinit(hapd->sock_dhcp);
+}
diff --git a/src/ap/dhcp_snoop.h b/src/ap/dhcp_snoop.h
new file mode 100644 (file)
index 0000000..93d0050
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * DHCP snooping for Proxy ARP
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef DHCP_SNOOP_H
+#define DHCP_SNOOP_H
+
+#ifdef CONFIG_PROXYARP
+
+int dhcp_snoop_init(struct hostapd_data *hapd);
+void dhcp_snoop_deinit(struct hostapd_data *hapd);
+
+#else /* CONFIG_PROXYARP */
+
+static inline int dhcp_snoop_init(struct hostapd_data *hapd)
+{
+       return 0;
+}
+
+static inline void dhcp_snoop_deinit(struct hostapd_data *hapd)
+{
+}
+
+#endif /* CONFIG_PROXYARP */
+
+#endif /* DHCP_SNOOP_H */
index 9fda339b76568c8b0c14093d040c942846a8b4e9..2871b9b7621b4c84f7afc498c7d8464404c58c74 100644 (file)
@@ -36,6 +36,7 @@
 #include "dfs.h"
 #include "ieee802_11.h"
 #include "bss_load.h"
+#include "dhcp_snoop.h"
 
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
@@ -312,6 +313,7 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd)
 #endif /* CONFIG_INTERWORKING */
 
        bss_load_update_deinit(hapd);
+       dhcp_snoop_deinit(hapd);
 
 #ifdef CONFIG_SQLITE
        bin_clear_free(hapd->tmp_eap_user.identity,
@@ -891,6 +893,11 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first)
                return -1;
        }
 
+       if (conf->proxy_arp && dhcp_snoop_init(hapd)) {
+               wpa_printf(MSG_ERROR, "DHCP snooping initialization failed");
+               return -1;
+       }
+
        if (!hostapd_drv_none(hapd) && vlan_init(hapd)) {
                wpa_printf(MSG_ERROR, "VLAN initialization failed.");
                return -1;
index 3f79413d6c2604b1550c1d615ad3de92bc430f7c..4567036cb109f67a20ebdaec1e4e950ef4fa0a5e 100644 (file)
@@ -241,6 +241,9 @@ struct hostapd_data {
 #ifdef CONFIG_INTERWORKING
        size_t gas_frag_limit;
 #endif /* CONFIG_INTERWORKING */
+#ifdef CONFIG_PROXYARP
+       struct l2_packet_data *sock_dhcp;
+#endif /* CONFIG_PROXYARP */
 #ifdef CONFIG_MESH
        int num_plinks;
        int max_plinks;
index 3eb9456834241e0da435719e88fc61915130f041..d59a2b433c8a18d83aa92e15521334ef4c0aec9b 100644 (file)
@@ -1502,6 +1502,8 @@ static void handle_disassoc(struct hostapd_data *hapd,
         * authenticated. */
        accounting_sta_stop(hapd, sta);
        ieee802_1x_free_station(sta);
+       if (sta->ipaddr)
+               hostapd_drv_br_delete_ip_neigh(hapd, sta->ipaddr);
        hostapd_drv_sta_remove(hapd, sta->addr);
 
        if (sta->timeout_next == STA_NULLFUNC ||
index 12403f99a8b3ba46c9e4ba6fff3a604cada8b64b..d462ac8bf9cde5f289fb0ec9d1d592afcdd61958 100644 (file)
@@ -174,6 +174,8 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx)
                        *pos |= 0x01; /* Bit 0 - Coexistence management */
                break;
        case 1: /* Bits 8-15 */
+               if (hapd->conf->proxy_arp)
+                       *pos |= 0x10; /* Bit 12 - Proxy ARP */
                break;
        case 2: /* Bits 16-23 */
                if (hapd->conf->wnm_sleep_mode)
index ed63aa8dafc3b28b7aa7b84e459524f4ad249c15..ec0e493c37aa9521c390bc4fe3c2bae6e5a9e9ba 100644 (file)
@@ -156,6 +156,9 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
        if (sta->flags & WLAN_STA_WDS)
                hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0);
 
+       if (sta->ipaddr)
+               hostapd_drv_br_delete_ip_neigh(hapd, sta->ipaddr);
+
        if (!hapd->iface->driver_ap_teardown &&
            !(sta->flags & WLAN_STA_PREAUTH))
                hostapd_drv_sta_remove(hapd, sta->addr);
@@ -605,6 +608,9 @@ static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta)
 {
        ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
 
+       if (sta->ipaddr)
+               hostapd_drv_br_delete_ip_neigh(hapd, sta->ipaddr);
+
        wpa_printf(MSG_DEBUG, "Removing STA " MACSTR " from kernel driver",
                   MAC2STR(sta->addr));
        if (hostapd_drv_sta_remove(hapd, sta->addr) &&
index c4c88250aa10e6e30c15bacf9c50e0c29d58ec33..25edd7f03a997c6c5d289071afc9b9e977bbe726 100644 (file)
@@ -46,6 +46,7 @@ struct sta_info {
        struct sta_info *next; /* next entry in sta list */
        struct sta_info *hnext; /* next entry in hash table list */
        u8 addr[6];
+       be32 ipaddr;
        u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */
        u32 flags; /* Bitfield of WLAN_STA_* */
        u16 capability;