Coverage Report

Created: 2025-08-11 06:02

/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
}