/src/hostap/src/ap/ndisc_snoop.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Neighbor Discovery snooping for Proxy ARP |
3 | | * Copyright (c) 2014, Qualcomm Atheros, Inc. |
4 | | * |
5 | | * This software may be distributed under the terms of the BSD license. |
6 | | * See README for more details. |
7 | | */ |
8 | | |
9 | | #include "utils/includes.h" |
10 | | #include <netinet/ip6.h> |
11 | | #include <netinet/icmp6.h> |
12 | | |
13 | | #include "utils/common.h" |
14 | | #include "l2_packet/l2_packet.h" |
15 | | #include "hostapd.h" |
16 | | #include "sta_info.h" |
17 | | #include "ap_drv_ops.h" |
18 | | #include "list.h" |
19 | | #include "x_snoop.h" |
20 | | #include "ndisc_snoop.h" |
21 | | |
22 | | struct ip6addr { |
23 | | struct in6_addr addr; |
24 | | struct dl_list list; |
25 | | }; |
26 | | |
27 | | struct icmpv6_ndmsg { |
28 | | struct ip6_hdr ipv6h; |
29 | | struct icmp6_hdr icmp6h; |
30 | | struct in6_addr target_addr; |
31 | | u8 opt_type; |
32 | | u8 len; |
33 | | u8 opt_lladdr[0]; |
34 | | } STRUCT_PACKED; |
35 | | |
36 | 0 | #define ROUTER_ADVERTISEMENT 134 |
37 | 0 | #define NEIGHBOR_SOLICITATION 135 |
38 | 0 | #define NEIGHBOR_ADVERTISEMENT 136 |
39 | 0 | #define SOURCE_LL_ADDR 1 |
40 | | |
41 | | static int sta_ip6addr_add(struct sta_info *sta, struct in6_addr *addr) |
42 | 0 | { |
43 | 0 | struct ip6addr *ip6addr; |
44 | |
|
45 | 0 | ip6addr = os_zalloc(sizeof(*ip6addr)); |
46 | 0 | if (!ip6addr) |
47 | 0 | return -1; |
48 | | |
49 | 0 | os_memcpy(&ip6addr->addr, addr, sizeof(*addr)); |
50 | |
|
51 | 0 | dl_list_add_tail(&sta->ip6addr, &ip6addr->list); |
52 | |
|
53 | 0 | return 0; |
54 | 0 | } |
55 | | |
56 | | |
57 | | void sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) |
58 | 2.18k | { |
59 | 2.18k | struct ip6addr *ip6addr, *prev; |
60 | | |
61 | 2.18k | dl_list_for_each_safe(ip6addr, prev, &sta->ip6addr, struct ip6addr, |
62 | 2.18k | list) { |
63 | 0 | hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &ip6addr->addr); |
64 | 0 | dl_list_del(&ip6addr->list); |
65 | 0 | os_free(ip6addr); |
66 | 0 | } |
67 | 2.18k | } |
68 | | |
69 | | |
70 | | static int sta_has_ip6addr(struct sta_info *sta, struct in6_addr *addr) |
71 | 0 | { |
72 | 0 | struct ip6addr *ip6addr; |
73 | |
|
74 | 0 | dl_list_for_each(ip6addr, &sta->ip6addr, struct ip6addr, list) { |
75 | 0 | if (ip6addr->addr.s6_addr32[0] == addr->s6_addr32[0] && |
76 | 0 | ip6addr->addr.s6_addr32[1] == addr->s6_addr32[1] && |
77 | 0 | ip6addr->addr.s6_addr32[2] == addr->s6_addr32[2] && |
78 | 0 | ip6addr->addr.s6_addr32[3] == addr->s6_addr32[3]) |
79 | 0 | return 1; |
80 | 0 | } |
81 | | |
82 | 0 | return 0; |
83 | 0 | } |
84 | | |
85 | | |
86 | | static void ucast_to_stas(struct hostapd_data *hapd, const u8 *buf, size_t len) |
87 | 0 | { |
88 | 0 | struct sta_info *sta; |
89 | |
|
90 | 0 | for (sta = hapd->sta_list; sta; sta = sta->next) { |
91 | 0 | if (!(sta->flags & WLAN_STA_AUTHORIZED)) |
92 | 0 | continue; |
93 | 0 | x_snoop_mcast_to_ucast_convert_send(hapd, sta, (u8 *) buf, len); |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | | |
98 | | static void handle_ndisc(void *ctx, const u8 *src_addr, const u8 *buf, |
99 | | size_t len) |
100 | 0 | { |
101 | 0 | struct hostapd_data *hapd = ctx; |
102 | 0 | struct icmpv6_ndmsg *msg; |
103 | 0 | struct in6_addr saddr; |
104 | 0 | struct sta_info *sta; |
105 | 0 | int res; |
106 | 0 | char addrtxt[INET6_ADDRSTRLEN + 1]; |
107 | |
|
108 | 0 | if (len < ETH_HLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) |
109 | 0 | return; |
110 | 0 | msg = (struct icmpv6_ndmsg *) &buf[ETH_HLEN]; |
111 | 0 | switch (msg->icmp6h.icmp6_type) { |
112 | 0 | case NEIGHBOR_SOLICITATION: |
113 | 0 | if (len < ETH_HLEN + sizeof(*msg)) |
114 | 0 | return; |
115 | 0 | if (msg->opt_type != SOURCE_LL_ADDR) |
116 | 0 | return; |
117 | | |
118 | | /* |
119 | | * IPv6 header may not be 32-bit aligned in the buffer, so use |
120 | | * a local copy to avoid unaligned reads. |
121 | | */ |
122 | 0 | os_memcpy(&saddr, &msg->ipv6h.ip6_src, sizeof(saddr)); |
123 | 0 | if (!(saddr.s6_addr32[0] == 0 && saddr.s6_addr32[1] == 0 && |
124 | 0 | saddr.s6_addr32[2] == 0 && saddr.s6_addr32[3] == 0)) { |
125 | 0 | if (len < ETH_HLEN + sizeof(*msg) + ETH_ALEN) |
126 | 0 | return; |
127 | 0 | sta = ap_get_sta(hapd, msg->opt_lladdr); |
128 | 0 | if (!sta) |
129 | 0 | return; |
130 | | |
131 | 0 | if (sta_has_ip6addr(sta, &saddr)) |
132 | 0 | return; |
133 | | |
134 | 0 | if (inet_ntop(AF_INET6, &saddr, addrtxt, |
135 | 0 | sizeof(addrtxt)) == NULL) |
136 | 0 | addrtxt[0] = '\0'; |
137 | 0 | wpa_printf(MSG_DEBUG, "ndisc_snoop: Learned new IPv6 address %s for " |
138 | 0 | MACSTR, addrtxt, MAC2STR(sta->addr)); |
139 | 0 | hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &saddr); |
140 | 0 | res = hostapd_drv_br_add_ip_neigh(hapd, 6, |
141 | 0 | (u8 *) &saddr, |
142 | 0 | 128, sta->addr); |
143 | 0 | if (res) { |
144 | 0 | wpa_printf(MSG_ERROR, |
145 | 0 | "ndisc_snoop: Adding ip neigh failed: %d", |
146 | 0 | res); |
147 | 0 | return; |
148 | 0 | } |
149 | | |
150 | 0 | if (sta_ip6addr_add(sta, &saddr)) |
151 | 0 | return; |
152 | 0 | } |
153 | 0 | break; |
154 | 0 | #ifdef CONFIG_HS20 |
155 | 0 | case ROUTER_ADVERTISEMENT: |
156 | 0 | if (hapd->conf->disable_dgaf) |
157 | 0 | ucast_to_stas(hapd, buf, len); |
158 | 0 | break; |
159 | 0 | #endif /* CONFIG_HS20 */ |
160 | 0 | case NEIGHBOR_ADVERTISEMENT: |
161 | 0 | if (hapd->conf->na_mcast_to_ucast) |
162 | 0 | ucast_to_stas(hapd, buf, len); |
163 | 0 | break; |
164 | 0 | default: |
165 | 0 | break; |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | |
170 | | int ndisc_snoop_init(struct hostapd_data *hapd) |
171 | 0 | { |
172 | 0 | hapd->sock_ndisc = x_snoop_get_l2_packet(hapd, handle_ndisc, |
173 | 0 | L2_PACKET_FILTER_NDISC); |
174 | 0 | if (hapd->sock_ndisc == NULL) { |
175 | 0 | wpa_printf(MSG_DEBUG, |
176 | 0 | "ndisc_snoop: Failed to initialize L2 packet processing for NDISC packets: %s", |
177 | 0 | strerror(errno)); |
178 | 0 | return -1; |
179 | 0 | } |
180 | | |
181 | 0 | return 0; |
182 | 0 | } |
183 | | |
184 | | |
185 | | void ndisc_snoop_deinit(struct hostapd_data *hapd) |
186 | 0 | { |
187 | 0 | l2_packet_deinit(hapd->sock_ndisc); |
188 | 0 | hapd->sock_ndisc = NULL; |
189 | 0 | } |