diff --git a/hostapd/Makefile b/hostapd/Makefile index 317271c8b..aeea289a0 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -855,6 +855,7 @@ ifdef CONFIG_PROXYARP CFLAGS += -DCONFIG_PROXYARP OBJS += ../src/ap/x_snoop.o OBJS += ../src/ap/dhcp_snoop.o +OBJS += ../src/ap/ndisc_snoop.o endif OBJS += ../src/drivers/driver_common.o diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 2ed16046e..73e939c85 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -38,6 +38,7 @@ #include "bss_load.h" #include "x_snoop.h" #include "dhcp_snoop.h" +#include "ndisc_snoop.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -314,6 +315,7 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd) #endif /* CONFIG_INTERWORKING */ bss_load_update_deinit(hapd); + ndisc_snoop_deinit(hapd); dhcp_snoop_deinit(hapd); x_snoop_deinit(hapd); @@ -907,6 +909,12 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) "DHCP snooping initialization failed"); return -1; } + + if (ndisc_snoop_init(hapd)) { + wpa_printf(MSG_ERROR, + "Neighbor Discovery snooping initialization failed"); + return -1; + } } if (!hostapd_drv_none(hapd) && vlan_init(hapd)) { diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 7ee3c8771..ba169f499 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -243,6 +243,7 @@ struct hostapd_data { #endif /* CONFIG_INTERWORKING */ #ifdef CONFIG_PROXYARP struct l2_packet_data *sock_dhcp; + struct l2_packet_data *sock_ndisc; #endif /* CONFIG_PROXYARP */ #ifdef CONFIG_MESH int num_plinks; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 1982942dc..cca398412 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -1718,6 +1718,7 @@ static void handle_disassoc(struct hostapd_data *hapd, ieee802_1x_free_station(sta); if (sta->ipaddr) hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); hostapd_drv_sta_remove(hapd, sta->addr); if (sta->timeout_next == STA_NULLFUNC || diff --git a/src/ap/ndisc_snoop.c b/src/ap/ndisc_snoop.c new file mode 100644 index 000000000..81b9d8426 --- /dev/null +++ b/src/ap/ndisc_snoop.c @@ -0,0 +1,156 @@ +/* + * Neighbor Discovery 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 "utils/common.h" +#include "l2_packet/l2_packet.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "list.h" +#include "x_snoop.h" + +struct ip6addr { + struct in6_addr addr; + struct dl_list list; +}; + +struct icmpv6_ndmsg { + struct ipv6hdr ipv6h; + struct icmp6hdr icmp6h; + struct in6_addr target_addr; + u8 opt_type; + u8 len; + u8 opt_lladdr[0]; +}; + +#define NEIGHBOR_SOLICITATION 135 +#define NEIGHBOR_ADVERTISEMENT 136 +#define SOURCE_LL_ADDR 1 + +static int sta_ip6addr_add(struct sta_info *sta, struct in6_addr *addr) +{ + struct ip6addr *ip6addr; + + ip6addr = os_zalloc(sizeof(*ip6addr)); + if (!ip6addr) + return -1; + + os_memcpy(&ip6addr->addr, addr, sizeof(*addr)); + + dl_list_add_tail(&sta->ip6addr, &ip6addr->list); + + return 0; +} + + +void sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct ip6addr *ip6addr, *prev; + + dl_list_for_each_safe(ip6addr, prev, &sta->ip6addr, struct ip6addr, + list) { + hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &ip6addr->addr); + os_free(ip6addr); + } +} + + +static int sta_has_ip6addr(struct sta_info *sta, struct in6_addr *addr) +{ + struct ip6addr *ip6addr; + + dl_list_for_each(ip6addr, &sta->ip6addr, struct ip6addr, list) { + if (ip6addr->addr.s6_addr32[0] == addr->s6_addr32[0] && + ip6addr->addr.s6_addr32[1] == addr->s6_addr32[1] && + ip6addr->addr.s6_addr32[2] == addr->s6_addr32[2] && + ip6addr->addr.s6_addr32[3] == addr->s6_addr32[3]) + return 1; + } + + return 0; +} + + +static void handle_ndisc(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct hostapd_data *hapd = ctx; + struct icmpv6_ndmsg *msg; + struct in6_addr *saddr; + struct sta_info *sta; + int res; + + if (len < ETH_HLEN + sizeof(*msg)) + return; + msg = (struct icmpv6_ndmsg *) &buf[ETH_HLEN]; + switch (msg->icmp6h.icmp6_type) { + case NEIGHBOR_SOLICITATION: + if (msg->opt_type != SOURCE_LL_ADDR) + return; + + saddr = &msg->ipv6h.saddr; + if (!(saddr->s6_addr32[0] == 0 && saddr->s6_addr32[1] == 0 && + saddr->s6_addr32[2] == 0 && saddr->s6_addr32[3] == 0)) { + if (len < ETH_HLEN + sizeof(*msg) + ETH_ALEN) + return; + sta = ap_get_sta(hapd, msg->opt_lladdr); + if (!sta) + return; + + if (sta_has_ip6addr(sta, saddr)) + return; + + hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) saddr); + res = hostapd_drv_br_add_ip_neigh(hapd, 6, (u8 *) saddr, + 128, sta->addr); + if (res) { + wpa_printf(MSG_ERROR, + "ndisc_snoop: Adding ip neigh failed: %d", + res); + return; + } + + if (sta_ip6addr_add(sta, saddr)) + return; + } + break; + case NEIGHBOR_ADVERTISEMENT: + for (sta = hapd->sta_list; sta; sta = sta->next) { + x_snoop_mcast_to_ucast_convert_send(hapd, sta, + (u8 *) buf, len); + } + break; + default: + break; + } +} + + +int ndisc_snoop_init(struct hostapd_data *hapd) +{ + hapd->sock_ndisc = x_snoop_get_l2_packet(hapd, handle_ndisc, + L2_PACKET_FILTER_NDISC); + if (hapd->sock_ndisc == NULL) { + wpa_printf(MSG_DEBUG, + "ndisc_snoop: Failed to initialize L2 packet processing for NDISC packets: %s", + strerror(errno)); + return -1; + } + + return 0; +} + + +void ndisc_snoop_deinit(struct hostapd_data *hapd) +{ + l2_packet_deinit(hapd->sock_ndisc); +} diff --git a/src/ap/ndisc_snoop.h b/src/ap/ndisc_snoop.h new file mode 100644 index 000000000..921775653 --- /dev/null +++ b/src/ap/ndisc_snoop.h @@ -0,0 +1,36 @@ +/* + * Neighbor Discovery 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 NDISC_SNOOP_H +#define NDISC_SNOOP_H + +#ifdef CONFIG_PROXYARP + +int ndisc_snoop_init(struct hostapd_data *hapd); +void ndisc_snoop_deinit(struct hostapd_data *hapd); +void sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta); + +#else /* CONFIG_PROXYARP */ + +static inline int ndisc_snoop_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void ndisc_snoop_deinit(struct hostapd_data *hapd) +{ +} + +static inline void sta_ip6addr_del(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} + +#endif /* CONFIG_PROXYARP */ + +#endif /* NDISC_SNOOP_H */ diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 19ebe9ccf..059eda9c2 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -31,6 +31,7 @@ #include "ap_drv_ops.h" #include "gas_serv.h" #include "wnm_ap.h" +#include "ndisc_snoop.h" #include "sta_info.h" static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd, @@ -144,6 +145,12 @@ static void ap_sta_hash_del(struct hostapd_data *hapd, struct sta_info *sta) } +void ap_sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + sta_ip6addr_del(hapd, sta); +} + + void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) { int set_beacon = 0; @@ -158,6 +165,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) if (sta->ipaddr) hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); if (!hapd->iface->driver_ap_teardown && !(sta->flags & WLAN_STA_PREAUTH)) @@ -605,6 +613,7 @@ struct sta_info * ap_sta_add(struct hostapd_data *hapd, const u8 *addr) sta->ssid = &hapd->conf->ssid; ap_sta_remove_in_other_bss(hapd, sta); sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ; + dl_list_init(&sta->ip6addr); return sta; } @@ -616,6 +625,7 @@ static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta) if (sta->ipaddr) hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); wpa_printf(MSG_DEBUG, "Removing STA " MACSTR " from kernel driver", MAC2STR(sta->addr)); diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index ac7269f6a..588a9e2f2 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -14,6 +14,8 @@ #include "common/defs.h" #endif /* CONFIG_MESH */ +#include "list.h" + /* STA flags */ #define WLAN_STA_AUTH BIT(0) #define WLAN_STA_ASSOC BIT(1) @@ -47,6 +49,7 @@ struct sta_info { struct sta_info *hnext; /* next entry in hash table list */ u8 addr[6]; be32 ipaddr; + struct dl_list ip6addr; /* list head for struct ip6addr */ u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */ u32 flags; /* Bitfield of WLAN_STA_* */ u16 capability; @@ -193,6 +196,7 @@ struct sta_info * ap_get_sta(struct hostapd_data *hapd, const u8 *sta); struct sta_info * ap_get_sta_p2p(struct hostapd_data *hapd, const u8 *addr); void ap_sta_hash_add(struct hostapd_data *hapd, struct sta_info *sta); void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta); void hostapd_free_stas(struct hostapd_data *hapd); void ap_handle_timer(void *eloop_ctx, void *timeout_ctx); void ap_sta_replenish_timeout(struct hostapd_data *hapd, struct sta_info *sta,