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;