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