From 7d597d461777cd9af5d49dd678e29d0d400c27e1 Mon Sep 17 00:00:00 2001 From: Kyeyoon Park Date: Thu, 25 Sep 2014 22:32:55 -0700 Subject: [PATCH] AP: Add support for Proxy ARP, DHCP snooping mechanism 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 --- hostapd/Makefile | 10 ++ hostapd/config_file.c | 2 + hostapd/hostapd.conf | 5 + src/ap/ap_config.h | 1 + src/ap/dhcp_snoop.c | 196 +++++++++++++++++++++++++++++++++++++ src/ap/dhcp_snoop.h | 30 ++++++ src/ap/hostapd.c | 7 ++ src/ap/hostapd.h | 3 + src/ap/ieee802_11.c | 2 + src/ap/ieee802_11_shared.c | 2 + src/ap/sta_info.c | 6 ++ src/ap/sta_info.h | 1 + 12 files changed, 265 insertions(+) create mode 100644 src/ap/dhcp_snoop.c create mode 100644 src/ap/dhcp_snoop.h diff --git a/hostapd/Makefile b/hostapd/Makefile index dc5eacdb7..86b6ea570 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -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 diff --git a/hostapd/config_file.c b/hostapd/config_file.c index ddebf1f46..f98e957a6 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -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) { diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 2e6f841bf..eaff3ddea 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -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 diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 18538c92a..8a444016d 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -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 index 000000000..1bd4ba456 --- /dev/null +++ b/src/ap/dhcp_snoop.c @@ -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 +#include +#include + +#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 index 000000000..93d0050fa --- /dev/null +++ b/src/ap/dhcp_snoop.h @@ -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 */ diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 9fda339b7..2871b9b76 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -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; diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 3f79413d6..4567036cb 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -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; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 3eb945683..d59a2b433 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -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 || diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c index 12403f99a..d462ac8bf 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -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) diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index ed63aa8da..ec0e493c3 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -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) && diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index c4c88250a..25edd7f03 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -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;