]> Sergey Matveev's repositories - ndproxy.git/blob - ndpacket.c
Compatibility with FreeBSD 14
[ndproxy.git] / ndpacket.c
1 /*-
2  * Copyright (c) 2015-2019 Alexandre Fenyo <alex@fenyo.net> - http://www.fenyo.net
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/param.h>
28 #include <sys/socket.h>
29 #include <sys/kdb.h>
30 #include <net/if.h>
31 #include <net/pfil.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>
41
42 #include "ndpacket.h"
43 #include "ndconf.h"
44 #include "ndparse.h"
45
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.
51
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.
59
60 // called by pfil_run_hooks() @ ip6_input.c:ip_input()
61
62 #ifdef PFIL_VERSION
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) {
65 #else
66 int packet(void *packet_arg, struct mbuf **packet_mp, struct ifnet *packet_ifnet,
67            const int packet_dir, struct inpcb *packet_inpcb) {
68 #endif
69   struct mbuf *m = NULL, *mreply = NULL;
70   struct ip6_hdr *ip6, *ip6reply;
71   struct icmp6_hdr *icmp6;
72   struct in6_addr srcaddr, dstaddr;
73   int output_flags = 0;
74   int maxlen, ret, i;
75
76 #ifdef DEBUG_NDPROXY
77   // when debuging, increment counter of received packets from the uplink interface
78   ndproxy_conf_count = ++ndproxy_conf_count < 0 ? 1 : ndproxy_conf_count;
79 #endif
80   
81   if (packet_mp == NULL) {
82     printf("NDPROXY ERROR: no mbuf\n");
83     return 0;
84   }
85   m = *packet_mp;
86
87   // locate start of IPv6 header data
88   ip6 = mtod(m, struct ip6_hdr *);
89   struct in6_addr ip6_src = ip6->ip6_src;
90
91   // handle only packets originating from the uplink interface
92   if (strcmp(if_name(packet_ifnet), ndproxy_conf_str_uplink_interface)) {
93 #ifdef DEBUG_NDPROXY
94     printf("NDPROXY DEBUG: packets from uplink interface: %s - %d\n", if_name(packet_ifnet), ndproxy_conf_count);
95 #endif
96     return 0;
97   }
98
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++) {
120 #ifdef DEBUG_NDPROXY
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");
126 #endif
127     if (IN6_ARE_ADDR_EQUAL(ndproxy_conf_uplink_ipv6_addresses + i, &ip6_src)) break;
128   }
129   if (i == ndproxy_conf_uplink_ipv6_naddresses) {
130 #ifdef DEBUG_NDPROXY
131     printf("NDPROXY INFO: not from uplink router - from: ");
132     printf_ip6addr(&ip6_src, false);
133     printf(" - %d\n", ndproxy_conf_count);
134 #endif
135     return 0;
136   }
137
138 #ifdef DEBUG_NDPROXY
139   printf("NDPROXY DEBUG: got packet from uplink router - %d\n", ndproxy_conf_count);
140 #endif
141   
142   // For security reasons, we explicitely reject neighbor solicitation packets containing any extension header:
143   // such a packet is mainly unattended:
144   //   Fragmentation:
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.
150   //   Routing header:
151   //     commonly used for mobility or source routing, we do not support these headers.
152   //   AH & ESP 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;
158 #ifdef DEBUG_NDPROXY
159   printf("NDPROXY DEBUG: got ICMPv6 from uplink router - %d\n", ndproxy_conf_count);
160 #endif
161
162   // locate start of ICMPv6 header data
163   icmp6 = (struct icmp6_hdr *) ((caddr_t) ip6 + sizeof(struct ip6_hdr));
164
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");
172     return 0;
173   }
174   icmp6->icmp6_cksum = sum;
175
176   if (icmp6->icmp6_type != ND_NEIGHBOR_SOLICIT || icmp6->icmp6_code) return 0;
177 #ifdef DEBUG_NDPROXY
178   printf("NDPROXY DEBUG: got neighbor solicitation from ");
179   printf_ip6addr(&ip6_src, true);
180   printf("\n");
181 #endif
182
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");
189     return 0;
190   }
191   if (max_linkhdr + maxlen > MHLEN)
192     mreply = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
193   else
194     mreply = m_gethdr(M_NOWAIT, MT_DATA);
195   if (mreply == NULL) {
196     printf("NDPROXY ERROR: no more mbufs (ENOBUFS)\n");
197     return 0;
198   }
199
200   // this is a newly created packet
201   mreply->m_pkthdr.rcvif = NULL;
202
203   // packet content:
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;
207
208   // reserve space for the link-layer header
209   mreply->m_data += max_linkhdr;
210
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);
219     m_freem(mreply);
220     return 0;
221   }
222
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;
227   else {
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) {
234 #ifdef DEBUG_NDPROXY
235       printf("NDPROXY DEBUG: unspecified source address and solicited-node multicast destination address\n");
236 #endif
237     } else {
238       printf("NDPROXY ERROR: destination address should be a solicited-node multicast address\n");
239       m_freem(mreply);
240       return 0;
241     }
242
243     output_flags |= M_MCAST;
244     dstaddr = in6addr_linklocal_allnodes;
245   }
246   if ((ret = in6_setscope(&dstaddr, packet_ifnet, NULL))) {
247     printf("NDPROXY ERROR: can not set destination scope id (err=%d)\n", ret);
248     m_freem(mreply);
249     return 0;
250   }
251     
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);
255 #else
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);
261 #endif
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));
265     m_freem(mreply);
266     return 0;
267   }
268   if (ret) {
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");
273     
274 #ifdef DEBUG_NDPROXY
275     printf("NDPROXY INFO: no address in requested scope, using a link-local address to reply\n");
276 #endif
277     if (llifaddr != NULL) {
278       // use the link-local address
279       srcaddr = (llifaddr->ia_addr).sin6_addr;
280       ifa_free((struct ifaddr *) llifaddr);
281     } else
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);
286     
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);
294       m_freem(mreply);
295       return 0;
296     }
297   }
298
299 #ifdef DEBUG_NDPROXY
300   printf("NDPROXY DEBUG: source address used to reply: "); printf_ip6addr(&srcaddr, true); printf("\n");  
301 #endif
302
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;
305
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;
316
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;
321
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;
326
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
329   // be set to zero."
330   // Thus, we do not set the ND_NA_FLAG_OVERRIDE flag in nd_na->nd_na_flags_reserved.
331
332   nd_na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
333
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");
337     m_freem(mreply);
338     return 0;
339   }
340
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;
344
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) {
352         addr_allowed = 1;
353         break;
354       }
355     } else {
356       if (memcmp(ndproxy_conf_exception_ipv6_addresses + i, &nd_na_target, 48/8) == 0) {
357         addr_allowed = 1;
358         break;
359       }
360     }
361   }
362   // printf("ndproxy: ");
363   // printf_ip6addr(&nd_na_target, false);
364   // printf("\n");
365   if (addr_allowed != 1) {
366     m_freem(mreply);
367     return 0;
368   }
369
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);
379 #ifdef DEBUG_NDPROXY
380   printf("NDPROXY INFO: mac option: ");
381   printf_macaddr_network_format(&ndproxy_conf_downlink_mac_address);
382   printf("\n");
383 #endif
384   
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));
389
390 #ifdef DEBUG_NDPROXY
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");
395 #endif
396
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;
403   }
404   
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);
408 #ifdef DEBUG_NDPROXY
409     kdb_backtrace();
410     return 0;
411 #endif
412   } else {
413 #ifdef DEBUG_NDPROXY
414     printf("NDPROXY DEBUG: reply sent\n");
415 #endif
416   }
417
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;
421 #endif
422
423   // do not process this packet by upper layers to avoid sending another advertissement
424   m_freem(m);
425   *packet_mp = NULL;
426   return 1;
427 }