1228753Smm/* 2228753Smm * Neighbor Discovery snooping for Proxy ARP 3228753Smm * Copyright (c) 2014, Qualcomm Atheros, Inc. 4228753Smm * 5228753Smm * This software may be distributed under the terms of the BSD license. 6228753Smm * See README for more details. 7228753Smm */ 8228753Smm 9228753Smm#include "utils/includes.h" 10228753Smm#include <netinet/ip6.h> 11228753Smm#include <netinet/icmp6.h> 12228753Smm 13228753Smm#include "utils/common.h" 14228753Smm#include "l2_packet/l2_packet.h" 15228753Smm#include "hostapd.h" 16228753Smm#include "sta_info.h" 17228753Smm#include "ap_drv_ops.h" 18228753Smm#include "list.h" 19228753Smm#include "x_snoop.h" 20228753Smm#include "ndisc_snoop.h" 21228753Smm 22228753Smmstruct ip6addr { 23228753Smm struct in6_addr addr; 24228753Smm struct dl_list list; 25228753Smm}; 26228753Smm 27229592Smmstruct icmpv6_ndmsg { 28228753Smm struct ip6_hdr ipv6h; 29228753Smm struct icmp6_hdr icmp6h; 30228753Smm struct in6_addr target_addr; 31228753Smm u8 opt_type; 32228753Smm u8 len; 33228753Smm u8 opt_lladdr[0]; 34228753Smm} STRUCT_PACKED; 35228753Smm 36228753Smm#define ROUTER_ADVERTISEMENT 134 37228753Smm#define NEIGHBOR_SOLICITATION 135 38228753Smm#define NEIGHBOR_ADVERTISEMENT 136 39228753Smm#define SOURCE_LL_ADDR 1 40228753Smm 41228753Smmstatic int sta_ip6addr_add(struct sta_info *sta, struct in6_addr *addr) 42228753Smm{ 43228753Smm struct ip6addr *ip6addr; 44228753Smm 45228753Smm ip6addr = os_zalloc(sizeof(*ip6addr)); 46228753Smm if (!ip6addr) 47228753Smm return -1; 48228753Smm 49228753Smm os_memcpy(&ip6addr->addr, addr, sizeof(*addr)); 50228753Smm 51228753Smm dl_list_add_tail(&sta->ip6addr, &ip6addr->list); 52228753Smm 53228753Smm return 0; 54228753Smm} 55228753Smm 56228753Smm 57228753Smmvoid sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) 58228753Smm{ 59228753Smm struct ip6addr *ip6addr, *prev; 60228753Smm 61228753Smm dl_list_for_each_safe(ip6addr, prev, &sta->ip6addr, struct ip6addr, 62228753Smm list) { 63228753Smm hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &ip6addr->addr); 64228753Smm os_free(ip6addr); 65228753Smm } 66228753Smm} 67228753Smm 68228753Smm 69228753Smmstatic int sta_has_ip6addr(struct sta_info *sta, struct in6_addr *addr) 70228753Smm{ 71228753Smm struct ip6addr *ip6addr; 72228753Smm 73228753Smm dl_list_for_each(ip6addr, &sta->ip6addr, struct ip6addr, list) { 74228753Smm if (ip6addr->addr.s6_addr32[0] == addr->s6_addr32[0] && 75228753Smm ip6addr->addr.s6_addr32[1] == addr->s6_addr32[1] && 76228753Smm ip6addr->addr.s6_addr32[2] == addr->s6_addr32[2] && 77228753Smm ip6addr->addr.s6_addr32[3] == addr->s6_addr32[3]) 78228753Smm return 1; 79228753Smm } 80228753Smm 81228753Smm return 0; 82228753Smm} 83228753Smm 84228753Smm 85228753Smmstatic void ucast_to_stas(struct hostapd_data *hapd, const u8 *buf, size_t len) 86228753Smm{ 87228753Smm struct sta_info *sta; 88228753Smm 89228753Smm for (sta = hapd->sta_list; sta; sta = sta->next) { 90228753Smm if (!(sta->flags & WLAN_STA_AUTHORIZED)) 91228753Smm continue; 92228753Smm x_snoop_mcast_to_ucast_convert_send(hapd, sta, (u8 *) buf, len); 93228753Smm } 94228753Smm} 95228753Smm 96228753Smm 97228753Smmstatic void handle_ndisc(void *ctx, const u8 *src_addr, const u8 *buf, 98228753Smm size_t len) 99228753Smm{ 100228753Smm struct hostapd_data *hapd = ctx; 101228753Smm struct icmpv6_ndmsg *msg; 102228753Smm struct in6_addr saddr; 103228753Smm struct sta_info *sta; 104228753Smm int res; 105228753Smm char addrtxt[INET6_ADDRSTRLEN + 1]; 106228753Smm 107228753Smm if (len < ETH_HLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) 108228753Smm return; 109228753Smm msg = (struct icmpv6_ndmsg *) &buf[ETH_HLEN]; 110228753Smm switch (msg->icmp6h.icmp6_type) { 111228753Smm case NEIGHBOR_SOLICITATION: 112228753Smm if (len < ETH_HLEN + sizeof(*msg)) 113228753Smm return; 114228753Smm if (msg->opt_type != SOURCE_LL_ADDR) 115228753Smm return; 116228753Smm 117228753Smm /* 118228753Smm * IPv6 header may not be 32-bit aligned in the buffer, so use 119228753Smm * a local copy to avoid unaligned reads. 120228753Smm */ 121228753Smm os_memcpy(&saddr, &msg->ipv6h.ip6_src, sizeof(saddr)); 122228753Smm if (!(saddr.s6_addr32[0] == 0 && saddr.s6_addr32[1] == 0 && 123228753Smm saddr.s6_addr32[2] == 0 && saddr.s6_addr32[3] == 0)) { 124228753Smm if (len < ETH_HLEN + sizeof(*msg) + ETH_ALEN) 125228753Smm return; 126228753Smm sta = ap_get_sta(hapd, msg->opt_lladdr); 127228753Smm if (!sta) 128228753Smm return; 129228753Smm 130228753Smm if (sta_has_ip6addr(sta, &saddr)) 131228753Smm return; 132228753Smm 133228753Smm if (inet_ntop(AF_INET6, &saddr, addrtxt, 134228753Smm sizeof(addrtxt)) == NULL) 135228753Smm addrtxt[0] = '\0'; 136228753Smm wpa_printf(MSG_DEBUG, "ndisc_snoop: Learned new IPv6 address %s for " 137228753Smm MACSTR, addrtxt, MAC2STR(sta->addr)); 138228753Smm hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &saddr); 139228753Smm res = hostapd_drv_br_add_ip_neigh(hapd, 6, 140228753Smm (u8 *) &saddr, 141228753Smm 128, sta->addr); 142228753Smm if (res) { 143228753Smm wpa_printf(MSG_ERROR, 144228753Smm "ndisc_snoop: Adding ip neigh failed: %d", 145228753Smm res); 146228753Smm return; 147228753Smm } 148228753Smm 149228753Smm if (sta_ip6addr_add(sta, &saddr)) 150228753Smm return; 151228753Smm } 152228753Smm break; 153228753Smm case ROUTER_ADVERTISEMENT: 154228753Smm if (hapd->conf->disable_dgaf) 155228753Smm ucast_to_stas(hapd, buf, len); 156228753Smm break; 157228753Smm case NEIGHBOR_ADVERTISEMENT: 158228753Smm if (hapd->conf->na_mcast_to_ucast) 159228753Smm ucast_to_stas(hapd, buf, len); 160228753Smm break; 161228753Smm default: 162228753Smm break; 163228753Smm } 164228753Smm} 165228753Smm 166228753Smm 167228753Smmint ndisc_snoop_init(struct hostapd_data *hapd) 168228753Smm{ 169228753Smm hapd->sock_ndisc = x_snoop_get_l2_packet(hapd, handle_ndisc, 170228753Smm L2_PACKET_FILTER_NDISC); 171228753Smm if (hapd->sock_ndisc == NULL) { 172228753Smm wpa_printf(MSG_DEBUG, 173228753Smm "ndisc_snoop: Failed to initialize L2 packet processing for NDISC packets: %s", 174228753Smm strerror(errno)); 175228753Smm return -1; 176228753Smm } 177228753Smm 178228753Smm return 0; 179228753Smm} 180228753Smm 181228753Smm 182228753Smmvoid ndisc_snoop_deinit(struct hostapd_data *hapd) 183228753Smm{ 184228753Smm l2_packet_deinit(hapd->sock_ndisc); 185228753Smm hapd->sock_ndisc = NULL; 186228753Smm} 187228753Smm