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 <kyeyoonp@qca.qualcomm.com>
This commit is contained in:
Kyeyoon Park 2014-09-25 22:32:55 -07:00 committed by Jouni Malinen
parent 6897556455
commit 7d597d4617
12 changed files with 265 additions and 0 deletions

View file

@ -188,6 +188,11 @@ endif
ifdef CONFIG_HS20 ifdef CONFIG_HS20
NEED_AES_OMAC1=y NEED_AES_OMAC1=y
CONFIG_PROXYARP=y
endif
ifdef CONFIG_PROXYARP
CONFIG_L2_PACKET=y
endif endif
ifdef CONFIG_IEEE80211W ifdef CONFIG_IEEE80211W
@ -840,6 +845,11 @@ OBJS += ../src/common/gas.o
OBJS += ../src/ap/gas_serv.o OBJS += ../src/ap/gas_serv.o
endif endif
ifdef CONFIG_PROXYARP
CFLAGS += -DCONFIG_PROXYARP
OBJS += ../src/ap/dhcp_snoop.o
endif
OBJS += ../src/drivers/driver_common.o OBJS += ../src/drivers/driver_common.o
ifdef CONFIG_WPA_CLI_EDIT ifdef CONFIG_WPA_CLI_EDIT

View file

@ -3008,6 +3008,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
bss->hs20 = atoi(pos); bss->hs20 = atoi(pos);
} else if (os_strcmp(buf, "disable_dgaf") == 0) { } else if (os_strcmp(buf, "disable_dgaf") == 0) {
bss->disable_dgaf = atoi(pos); 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) { } else if (os_strcmp(buf, "osen") == 0) {
bss->osen = atoi(pos); bss->osen = atoi(pos);
} else if (os_strcmp(buf, "anqp_domain_id") == 0) { } else if (os_strcmp(buf, "anqp_domain_id") == 0) {

View file

@ -1453,6 +1453,11 @@ own_ip_addr=127.0.0.1
# 1 = enabled # 1 = enabled
#bss_transition=1 #bss_transition=1
# Proxy ARP
# 0 = disabled (default)
# 1 = enabled
#proxy_arp=1
##### IEEE 802.11u-2011 ####################################################### ##### IEEE 802.11u-2011 #######################################################
# Enable Interworking service # Enable Interworking service

View file

@ -484,6 +484,7 @@ struct hostapd_bss_config {
unsigned int qos_map_set_len; unsigned int qos_map_set_len;
int osen; int osen;
int proxy_arp;
#ifdef CONFIG_HS20 #ifdef CONFIG_HS20
int hs20; int hs20;
int disable_dgaf; int disable_dgaf;

196
src/ap/dhcp_snoop.c Normal file
View file

@ -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);
}

30
src/ap/dhcp_snoop.h Normal file
View file

@ -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 */

View file

@ -36,6 +36,7 @@
#include "dfs.h" #include "dfs.h"
#include "ieee802_11.h" #include "ieee802_11.h"
#include "bss_load.h" #include "bss_load.h"
#include "dhcp_snoop.h"
static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); 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 */ #endif /* CONFIG_INTERWORKING */
bss_load_update_deinit(hapd); bss_load_update_deinit(hapd);
dhcp_snoop_deinit(hapd);
#ifdef CONFIG_SQLITE #ifdef CONFIG_SQLITE
bin_clear_free(hapd->tmp_eap_user.identity, 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; 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)) { if (!hostapd_drv_none(hapd) && vlan_init(hapd)) {
wpa_printf(MSG_ERROR, "VLAN initialization failed."); wpa_printf(MSG_ERROR, "VLAN initialization failed.");
return -1; return -1;

View file

@ -241,6 +241,9 @@ struct hostapd_data {
#ifdef CONFIG_INTERWORKING #ifdef CONFIG_INTERWORKING
size_t gas_frag_limit; size_t gas_frag_limit;
#endif /* CONFIG_INTERWORKING */ #endif /* CONFIG_INTERWORKING */
#ifdef CONFIG_PROXYARP
struct l2_packet_data *sock_dhcp;
#endif /* CONFIG_PROXYARP */
#ifdef CONFIG_MESH #ifdef CONFIG_MESH
int num_plinks; int num_plinks;
int max_plinks; int max_plinks;

View file

@ -1502,6 +1502,8 @@ static void handle_disassoc(struct hostapd_data *hapd,
* authenticated. */ * authenticated. */
accounting_sta_stop(hapd, sta); accounting_sta_stop(hapd, sta);
ieee802_1x_free_station(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); hostapd_drv_sta_remove(hapd, sta->addr);
if (sta->timeout_next == STA_NULLFUNC || if (sta->timeout_next == STA_NULLFUNC ||

View 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 */ *pos |= 0x01; /* Bit 0 - Coexistence management */
break; break;
case 1: /* Bits 8-15 */ case 1: /* Bits 8-15 */
if (hapd->conf->proxy_arp)
*pos |= 0x10; /* Bit 12 - Proxy ARP */
break; break;
case 2: /* Bits 16-23 */ case 2: /* Bits 16-23 */
if (hapd->conf->wnm_sleep_mode) if (hapd->conf->wnm_sleep_mode)

View file

@ -156,6 +156,9 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
if (sta->flags & WLAN_STA_WDS) if (sta->flags & WLAN_STA_WDS)
hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0); 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 && if (!hapd->iface->driver_ap_teardown &&
!(sta->flags & WLAN_STA_PREAUTH)) !(sta->flags & WLAN_STA_PREAUTH))
hostapd_drv_sta_remove(hapd, sta->addr); 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); 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", wpa_printf(MSG_DEBUG, "Removing STA " MACSTR " from kernel driver",
MAC2STR(sta->addr)); MAC2STR(sta->addr));
if (hostapd_drv_sta_remove(hapd, sta->addr) && if (hostapd_drv_sta_remove(hapd, sta->addr) &&

View file

@ -46,6 +46,7 @@ struct sta_info {
struct sta_info *next; /* next entry in sta list */ struct sta_info *next; /* next entry in sta list */
struct sta_info *hnext; /* next entry in hash table list */ struct sta_info *hnext; /* next entry in hash table list */
u8 addr[6]; u8 addr[6];
be32 ipaddr;
u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */ u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */
u32 flags; /* Bitfield of WLAN_STA_* */ u32 flags; /* Bitfield of WLAN_STA_* */
u16 capability; u16 capability;