2 * Copyright (c) 2015-2019 Alexandre Fenyo <alex@fenyo.net> - http://www.fenyo.net
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/param.h>
28 #include <sys/socket.h>
32 #include <net/if_var.h>
33 #include <net/ethernet.h>
34 #include <netinet/in.h>
35 #include <netinet/in_pcb.h>
36 #include <netinet/icmp6.h>
37 #include <netinet/ip6.h>
38 #include <netinet6/in6_var.h>
39 #include <netinet6/ip6_var.h>
40 #include <netinet6/scope6_var.h>
46 // Reply to neighbor solicitations with a specific neighbor advertisement, in order
47 // to let the uplink router send packets to a downlink router, that may or may not
48 // be the current host that run ndproxy.
49 // The current host, the uplink router and the downlink router must be attached
50 // to a common layer-2 link with broadcast multi-access capability.
52 // Neighbor solicitation messages are multicast to the solicited-node multicast
53 // address of the target address. Since we do not know the target address,
54 // we can not join the corresponding group. So, to capture the solicitation messages,
55 // the uplink interface must be set in permanent promiscuous mode and MLD snooping
56 // must be disabled on the switches that share the layer-2 link relative to
57 // the uplink interface. Note that MLD snooping must not be disabled entirely on
58 // each switch, but only on the corresponding vlan.
60 // called by pfil_run_hooks() @ ip6_input.c:ip_input()
63 pfil_return_t packet(struct mbuf **packet_mp, struct ifnet *packet_ifnet,
64 const int packet_dir, void *packet_arg, struct inpcb *packet_inpcb) {
66 int packet(void *packet_arg, struct mbuf **packet_mp, struct ifnet *packet_ifnet,
67 const int packet_dir, struct inpcb *packet_inpcb) {
69 struct mbuf *m = NULL, *mreply = NULL;
70 struct ip6_hdr *ip6, *ip6reply;
71 struct icmp6_hdr *icmp6;
72 struct in6_addr srcaddr, dstaddr;
77 // when debuging, increment counter of received packets from the uplink interface
78 ndproxy_conf_count = ++ndproxy_conf_count < 0 ? 1 : ndproxy_conf_count;
81 if (packet_mp == NULL) {
82 printf("NDPROXY ERROR: no mbuf\n");
87 // locate start of IPv6 header data
88 ip6 = mtod(m, struct ip6_hdr *);
89 struct in6_addr ip6_src = ip6->ip6_src;
91 // handle only packets originating from the uplink interface
92 if (strcmp(if_name(packet_ifnet), ndproxy_conf_str_uplink_interface)) {
94 printf("NDPROXY DEBUG: packets from uplink interface: %s - %d\n", if_name(packet_ifnet), ndproxy_conf_count);
99 // Handle only packets originating from one of the uplink router addresses.
100 // Note that different source addresses can be choosen from the same uplink router, depending on the packet
101 // that triggered the address resolution process or depending on other external factors.
102 // Here are some cases when it can happen:
103 // - the uplink router may have multiple interfaces;
104 // - there may be multiple uplink routers;
105 // - many routers choose to use a link-local address when sending neighbor solicitations,
106 // but when an administrator of such a router, also having a global address assigned on the same link,
107 // tries to send packets (echo request, for instance) to an on-link destination global address,
108 // the source address of the echo request packet prompting the solicitation may be global-scoped according
109 // to the selection algorithm described in RFC-6724. Therefore, the source address of the Neighbor Solicitation
110 // packet should also be selected in the same global scope, according to RFC-4861 (§7.2.2);
111 // - when the uplink router does not yet know its own address, it must use the unspecified address,
112 // according to RFC-4861.
113 // So, it can not be assumed that an uplink router will always use the same IPv6 address to send
114 // neighbor solicitations. Every assigned addresses to the downlink interface of the uplink router
115 // should then be declared to ndproxy via sysctl (net.inet6.ndproxyconf_uplink_ipv6_addresses).
116 // Since the unsolicited address can be used by many different nodes, another node than the uplink router could
117 // make use of such a source IP. This is why if such a node exists, the unsolicited address should not be
118 // declared in the net.inet6.ndproxyconf_uplink_ipv6_addresses sysctl configuration parameter.
119 for (i = 0; i < ndproxy_conf_uplink_ipv6_naddresses; i++) {
121 printf("NDPROXY INFO: compare: ");
122 printf_ip6addr(ndproxy_conf_uplink_ipv6_addresses + i, false);
123 printf(" (uplink router address) with ");
124 printf_ip6addr(&ip6_src, false);
125 printf(" (source address)\n");
127 if (IN6_ARE_ADDR_EQUAL(ndproxy_conf_uplink_ipv6_addresses + i, &ip6_src)) break;
129 if (i == ndproxy_conf_uplink_ipv6_naddresses) {
131 printf("NDPROXY INFO: not from uplink router - from: ");
132 printf_ip6addr(&ip6_src, false);
133 printf(" - %d\n", ndproxy_conf_count);
139 printf("NDPROXY DEBUG: got packet from uplink router - %d\n", ndproxy_conf_count);
142 // For security reasons, we explicitely reject neighbor solicitation packets containing any extension header:
143 // such a packet is mainly unattended:
145 // According to RFC-6980, IPv6 fragmentation header is forbidden in all neighbor discovery messages.
146 // Hop-by-hop header:
147 // commonly used for jumbograms or for MLD. Should not involve neighbor solicitation packets.
148 // Destination mobility headers:
149 // commonly used for mobility, we do not support these headers.
151 // commonly used for mobility or source routing, we do not support these headers.
153 // securing the neighbor discovery process is not done with IPsec but with the SEcure Neighbor
154 // Discovery protocol (RFC-3971). We can not support RFC-3971, since proxifying ND packets is
155 // some kind of a spoofing process.
156 // look for an ICMPv6 payload and no IPv6 extension header
157 if (ip6->ip6_nxt != IPPROTO_ICMPV6) return 0;
159 printf("NDPROXY DEBUG: got ICMPv6 from uplink router - %d\n", ndproxy_conf_count);
162 // locate start of ICMPv6 header data
163 icmp6 = (struct icmp6_hdr *) ((caddr_t) ip6 + sizeof(struct ip6_hdr));
165 // check the checksum
166 const u_int16_t sum = icmp6->icmp6_cksum;
167 icmp6->icmp6_cksum = 0;
168 if (sum != in6_cksum(m, IPPROTO_ICMPV6, sizeof(struct ip6_hdr),
169 m->m_len - sizeof(struct ip6_hdr))) {
170 icmp6->icmp6_cksum = sum;
171 printf("NDPROXY ERROR: bad checksum\n");
174 icmp6->icmp6_cksum = sum;
176 if (icmp6->icmp6_type != ND_NEIGHBOR_SOLICIT || icmp6->icmp6_code) return 0;
178 printf("NDPROXY DEBUG: got neighbor solicitation from ");
179 printf_ip6addr(&ip6_src, true);
183 // create a new mbuf to send a neighbor advertisement
184 // ICMPv6 options are rounded up to 8 bytes alignment
185 maxlen = (sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_advert) +
186 sizeof(struct nd_opt_hdr) + packet_ifnet->if_addrlen + 7) & ~7;
187 if (max_linkhdr + maxlen > MCLBYTES) {
188 printf("NDPROXY ERROR: reply length > MCLBYTES\n");
191 if (max_linkhdr + maxlen > MHLEN)
192 mreply = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
194 mreply = m_gethdr(M_NOWAIT, MT_DATA);
195 if (mreply == NULL) {
196 printf("NDPROXY ERROR: no more mbufs (ENOBUFS)\n");
200 // this is a newly created packet
201 mreply->m_pkthdr.rcvif = NULL;
204 // IPv6 header + ICMPv6 Neighbor Advertisement including target address + target link-layer ICMPv6 address option
205 mreply->m_pkthdr.len = mreply->m_len = (sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_advert)
206 + sizeof(struct nd_opt_hdr) + packet_ifnet->if_addrlen + 7) & ~7;
208 // reserve space for the link-layer header
209 mreply->m_data += max_linkhdr;
211 // fill in the destination address we want to reply to
212 struct sockaddr_in6 dst_sa;
213 bzero(&dst_sa, sizeof(struct sockaddr_in6));
214 dst_sa.sin6_family = AF_INET6;
215 dst_sa.sin6_len = sizeof(struct sockaddr_in6);
216 dst_sa.sin6_addr = ip6->ip6_src;
217 if ((ret = in6_setscope(&dst_sa.sin6_addr, packet_ifnet, NULL))) {
218 printf("NDPROXY ERROR: can not set source scope id (err=%d)\n", ret);
223 // According to RFC-4861 (§7.2.4), "The Target Address of the advertisement is copied from the Target Address
224 // of the solicitation. [...] If the source of the solicitation is the unspecified address, the
225 // node MUST [...] multicast the advertisement to the all-nodes address.".
226 if (!IN6_IS_ADDR_UNSPECIFIED(&ip6_src)) dstaddr = ip6->ip6_src;
228 // Check compliance to RFC-4861: "If the IP source address is the unspecified address, the IP
229 // destination address is a solicited-node multicast address.".
230 if (ip6->ip6_dst.s6_addr16[0] == IPV6_ADDR_INT16_MLL &&
231 ip6->ip6_dst.s6_addr32[1] == 0 &&
232 ip6->ip6_dst.s6_addr32[2] == IPV6_ADDR_INT32_ONE &&
233 ip6->ip6_dst.s6_addr8[12] == 0xff) {
235 printf("NDPROXY DEBUG: unspecified source address and solicited-node multicast destination address\n");
238 printf("NDPROXY ERROR: destination address should be a solicited-node multicast address\n");
243 output_flags |= M_MCAST;
244 dstaddr = in6addr_linklocal_allnodes;
246 if ((ret = in6_setscope(&dstaddr, packet_ifnet, NULL))) {
247 printf("NDPROXY ERROR: can not set destination scope id (err=%d)\n", ret);
252 // first, apply the RFC-3484 default address selection algorithm to get a source address for the advertisement packet.
253 #if (__FreeBSD_version < 1100000)
254 ret = in6_selectsrc(&dst_sa, NULL, NULL, NULL, NULL, NULL, &srcaddr);
256 uint32_t _dst_sa_scopeid;
257 struct in6_addr _dst_sa;
258 in6_splitscope(&dst_sa.sin6_addr, &_dst_sa, &_dst_sa_scopeid);
259 ret = in6_selectsrc_addr(RT_DEFAULT_FIB, &_dst_sa,
260 _dst_sa_scopeid, packet_ifnet, &srcaddr, NULL);
262 if (ret && (ret != EHOSTUNREACH || in6_addrscope(&ip6_src) == IPV6_ADDR_SCOPE_LINKLOCAL)) {
263 printf("NDPROXY ERROR: can not select a source address to reply (err=%d), source scope is %x\n",
264 ret, in6_addrscope(&ip6_src));
269 // secondly, try to reply with a link-local address attached to the receiving interface
270 struct in6_ifaddr *llifaddr = in6ifa_ifpforlinklocal(packet_ifnet, 0);
271 if (llifaddr == NULL)
272 printf("NDPROXY WARNING: no link-local address attached to the receiving interface\n");
275 printf("NDPROXY INFO: no address in requested scope, using a link-local address to reply\n");
277 if (llifaddr != NULL) {
278 // use the link-local address
279 srcaddr = (llifaddr->ia_addr).sin6_addr;
280 ifa_free((struct ifaddr *) llifaddr);
282 // No link-local address, we may for instance currently be verifying that the link-local stateless
283 // autoconfiguration address is unused.
284 // Then, we temporary use the unspecified address (::).
285 bzero(&srcaddr, sizeof srcaddr);
287 // Since we have no source address in the same scope of the destination address of the request packet,
288 // we can not simply reply to the source address of the request packet.
289 // Then we reply to the link-local all nodes multicast address (ff02::1).
290 output_flags |= M_MCAST;
291 dstaddr = in6addr_linklocal_allnodes;
292 if ((ret = in6_setscope(&dstaddr, packet_ifnet, NULL))) {
293 printf("NDPROXY ERROR: can not set destination scope id (err=%d)\n", ret);
300 printf("NDPROXY DEBUG: source address used to reply: "); printf_ip6addr(&srcaddr, true); printf("\n");
303 struct nd_neighbor_solicit *nd_ns = (struct nd_neighbor_solicit *) (ip6 + 1);
304 struct in6_addr nd_ns_target = nd_ns->nd_ns_target;
306 // fill in the IPv6 header
307 ip6reply = mtod(mreply, struct ip6_hdr *);
308 ip6reply->ip6_flow = 0;
309 ip6reply->ip6_vfc &= ~IPV6_VERSION_MASK;
310 ip6reply->ip6_vfc |= IPV6_VERSION;
311 ip6reply->ip6_plen = htons((u_short) (mreply->m_len - sizeof(struct ip6_hdr)));
312 ip6reply->ip6_nxt = IPPROTO_ICMPV6;
313 ip6reply->ip6_hlim = 255;
314 ip6reply->ip6_dst = dstaddr;
315 ip6reply->ip6_src = srcaddr;
317 // fill in the ICMPv6 neighbor advertisement header
318 struct nd_neighbor_advert *nd_na = (struct nd_neighbor_advert *) (ip6reply + 1);
319 nd_na->nd_na_type = ND_NEIGHBOR_ADVERT;
320 nd_na->nd_na_code = 0;
322 nd_na->nd_na_flags_reserved = 0;
323 // According to RFC-4861 (§7.2.4), "If the source of the solicitation is the unspecified address, the
324 // node MUST set the Solicited flag to zero [...]"
325 if (!IN6_IS_ADDR_UNSPECIFIED(&ip6_src)) nd_na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED;
327 // According to RFC-4861 (§7.2.4), "If the Target Address is either an anycast address or a unicast
328 // address for which the node is providing proxy service, [...] the Override flag SHOULD
330 // Thus, we do not set the ND_NA_FLAG_OVERRIDE flag in nd_na->nd_na_flags_reserved.
332 nd_na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
334 // according to RFC-4861 (§7.2.3), the target address can not be a multicast address
335 if (IN6_IS_ADDR_MULTICAST(&nd_ns_target)) {
336 printf("NDPROXY WARNING: rejecting multicast target address\n");
341 // we send a solicited neighbor advertisement relative to the target contained in the received neighbor solicitation
342 nd_na->nd_na_target = nd_ns->nd_ns_target;
343 struct in6_addr nd_na_target = nd_na->nd_na_target;
345 // do not manage packets relative to exception target addresses
346 int addr_allowed = 0;
347 for (i = 0; i < ndproxy_conf_exception_ipv6_naddresses; i++) {
348 if (IN6_IS_ADDR_LINKLOCAL(&nd_na_target) && IN6_IS_ADDR_LINKLOCAL(ndproxy_conf_exception_ipv6_addresses + i)) {
349 unsigned char *addr1 = (unsigned char *)(ndproxy_conf_exception_ipv6_addresses + i);
350 unsigned char *addr2 = (unsigned char *)(&nd_na_target);
351 if (memcmp(addr1+8, addr2+8, 64/8) == 0) {
356 if (memcmp(ndproxy_conf_exception_ipv6_addresses + i, &nd_na_target, 48/8) == 0) {
362 // printf("ndproxy: ");
363 // printf_ip6addr(&nd_na_target, false);
365 if (addr_allowed != 1) {
370 // proxy to the downlink router: fill in the target link-layer address option with the MAC downlink router address
371 int optlen = sizeof(struct nd_opt_hdr) + ETHER_ADDR_LEN;
372 struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *) (nd_na + 1);
373 // roundup to 8 bytes alignment
374 optlen = (optlen + 7) & ~7;
375 bzero((caddr_t) nd_opt, optlen);
376 nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
377 nd_opt->nd_opt_len = optlen >> 3;
378 bcopy(&ndproxy_conf_downlink_mac_address, (caddr_t) (nd_opt + 1), ETHER_ADDR_LEN);
380 printf("NDPROXY INFO: mac option: ");
381 printf_macaddr_network_format(&ndproxy_conf_downlink_mac_address);
385 // compute outgoing packet checksum
386 nd_na->nd_na_cksum = 0;
387 nd_na->nd_na_cksum = in6_cksum(mreply, IPPROTO_ICMPV6, sizeof(struct ip6_hdr),
388 mreply->m_len - sizeof(struct ip6_hdr));
391 struct in6_addr ip6reply_ip6_src = ip6reply->ip6_src;
392 struct in6_addr ip6reply_ip6_dst = ip6reply->ip6_dst;
393 printf("NDPROXY DEBUG: src="); printf_ip6addr(&ip6reply_ip6_src, false); printf(" / ");
394 printf("dst="); printf_ip6addr(&ip6reply_ip6_dst, false); printf("\n");
397 struct ip6_moptions im6o;
398 if (output_flags & M_MCAST) {
399 bzero(&im6o, sizeof im6o);
400 im6o.im6o_multicast_hlim = 255;
401 im6o.im6o_multicast_loop = false;
402 im6o.im6o_multicast_ifp = NULL;
405 // send router advertisement
406 if ((ret = ip6_output(mreply, NULL, NULL, output_flags, output_flags & M_MCAST ? &im6o : NULL, NULL, NULL))) {
407 printf("NDPROXY DEBUG: can not send packet (err=%d)\n", ret);
414 printf("NDPROXY DEBUG: reply sent\n");
418 #ifndef DEBUG_NDPROXY
419 // when NOT debuging, increment counter for each neighbor advertisement sent
420 ndproxy_conf_count = ++ndproxy_conf_count < 0 ? 1 : ndproxy_conf_count;
423 // do not process this packet by upper layers to avoid sending another advertissement