aboutgitcodebugslistschat
path: root/ndp.c
blob: a1ee8349481c85e74b827dd1eecbeb4174e1a890 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                            
 
                                         



                                         


                                
                                       
                                              







                   
                      
                       

                       

                             
                         
 
                     
                 
               
                  
                
                





                   



































































































































                                                                               


                                                       
                             

                                   


                                                             

                                                                               
 








































                                                                                
                                                               
                                  
                    
 
                                                       

                         


                         
                                   





                                                                              
                                                   

                                 
                                                     


                                                         
                                                                        
 
                                          
                                     

                         


                                 
                                                     


                                                                    
 
                                   








                                                                        

                 


                                      
                                                                           
                        







                                                                          
                                                 

                                                                      
                         

                                                                


                                                                            

                 
                                                          








                                                                               

                                                 
                                           

                                          
                                               
 




                                                                              


                                                                             

                                                                  

                         

                                                                             
                 
 
         
                                                                    



                         
                                         
                                             
            
                                          
 
                                    
 






                                                            


                 
// SPDX-License-Identifier: GPL-2.0-or-later

/* PASST - Plug A Simple Socket Transport
 *  for qemu/UNIX domain socket mode
 *
 * PASTA - Pack A Subtle Tap Abstraction
 *  for network namespace/tap device mode
 *
 * ndp.c - NDP support for PASST
 *
 * Copyright (c) 2020-2021 Red Hat GmbH
 * Author: Stefano Brivio <sbrivio@redhat.com>
 *
 */

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/if_ether.h>

#include <linux/icmpv6.h>

#include "checksum.h"
#include "util.h"
#include "ip.h"
#include "passt.h"
#include "tap.h"
#include "log.h"

#define RS	133
#define RA	134
#define NS	135
#define NA	136

enum ndp_option_types {
	OPT_SRC_L2_ADDR		= 1,
	OPT_TARGET_L2_ADDR	= 2,
	OPT_PREFIX_INFO		= 3,
	OPT_MTU			= 5,
	OPT_RDNSS_TYPE		= 25,
	OPT_DNSSL_TYPE		= 31,
};

/**
 * struct opt_header - Option header
 * @type:	Option type
 * @len:	Option length, in units of 8 bytes
*/
struct opt_header {
	uint8_t type;
	uint8_t len;
} __attribute__((packed));

/**
 * struct opt_l2_addr - Link-layer address
 * @header:	Option header
 * @mac:	MAC address
 */
struct opt_l2_addr {
	struct opt_header header;
	unsigned char mac[ETH_ALEN];
} __attribute__((packed));

/**
 * struct ndp_na - NDP Neighbor Advertisement (NA) message
 * @ih:			ICMPv6 header
 * @target_addr:	Target IPv6 address
 * @target_l2_addr:	Target link-layer address
 */
struct ndp_na {
	struct icmp6hdr ih;
	struct in6_addr target_addr;
	struct opt_l2_addr target_l2_addr;
} __attribute__((packed));

/**
 * struct opt_prefix_info - Prefix Information option
 * @header:		Option header
 * @prefix_len:		The number of leading bits in the Prefix that are valid
 * @prefix_flags:	Flags associated with the prefix
 * @valid_lifetime:	Valid lifetime (ms)
 * @pref_lifetime:	Preferred lifetime (ms)
 * @reserved:		Unused
 */
struct opt_prefix_info {
	struct opt_header header;
	uint8_t prefix_len;
	uint8_t prefix_flags;
	uint32_t valid_lifetime;
	uint32_t pref_lifetime;
	uint32_t reserved;
} __attribute__((packed));

/**
 * struct opt_mtu - Maximum transmission unit (MTU) option
 * @header:		Option header
 * @reserved:		Unused
 * @value:		MTU value, network order
 */
struct opt_mtu {
	struct opt_header header;
	uint16_t reserved;
	uint32_t value;
} __attribute__((packed));

/**
 * struct rdnss - Recursive DNS Server (RDNSS) option
 * @header:	Option header
 * @reserved:	Unused
 * @lifetime:	Validity time (s)
 * @dns:	List of DNS server addresses
 */
struct opt_rdnss {
	struct opt_header header;
	uint16_t reserved;
	uint32_t lifetime;
	struct in6_addr dns[MAXNS + 1];
} __attribute__((packed));

/**
 * struct dnssl - DNS Search List (DNSSL) option
 * @header:		Option header
 * @reserved:		Unused
 * @lifetime:		Validity time (s)
 * @domains:		List of NULL-seperated search domains
 */
struct opt_dnssl {
	struct opt_header header;
	uint16_t reserved;
	uint32_t lifetime;
	unsigned char domains[MAXDNSRCH * NS_MAXDNAME];
} __attribute__((packed));

/**
 * struct ndp_ra - NDP Router Advertisement (RA) message
 * @ih:			ICMPv6 header
 * @reachable:		Reachability time, after confirmation (ms)
 * @retrans:		Time between retransmitted NS messages (ms)
 * @prefix_info:	Prefix Information option
 * @prefix:		IPv6 prefix
 * @mtu:		MTU option
 * @source_ll:		Target link-layer address
 * @var:		Variable fields
 */
struct ndp_ra {
	struct icmp6hdr ih;
	uint32_t reachable;
	uint32_t retrans;
	struct opt_prefix_info prefix_info;
	struct in6_addr prefix;
	struct opt_l2_addr source_ll;

	unsigned char var[sizeof(struct opt_mtu) + sizeof(struct opt_rdnss) +
			  sizeof(struct opt_dnssl)];
} __attribute__((packed));

/**
 * struct ndp_ns - NDP Neighbor Solicitation (NS) message
 * @ih:			ICMPv6 header
 * @target_addr:	Target IPv6 address
 */
struct ndp_ns {
	struct icmp6hdr ih;
	struct in6_addr target_addr;
} __attribute__((packed));

/**
 * ndp() - Check for NDP solicitations, reply as needed
 * @c:		Execution context
 * @ih:		ICMPv6 header
 * @saddr:	Source IPv6 address
 * @p:		Packet pool
 *
 * Return: 0 if not handled here, 1 if handled, -1 on failure
 */
int ndp(struct ctx *c, const struct icmp6hdr *ih, const struct in6_addr *saddr,
	const struct pool *p)
{
	struct ndp_na na = {
		.ih = {
			.icmp6_type		= NA,
			.icmp6_code		= 0,
			.icmp6_router		= 1,
			.icmp6_solicited	= 1,
			.icmp6_override		= 1,
		},
		.target_l2_addr = {
			.header	= {
				.type		= OPT_TARGET_L2_ADDR,
				.len		= 1,
			},
		}
	};
	struct ndp_ra ra = {
		.ih = {
			.icmp6_type		= RA,
			.icmp6_code		= 0,
			.icmp6_hop_limit	= 255,
			/* RFC 8319 */
			.icmp6_rt_lifetime	= htons_constant(65535),
			.icmp6_addrconf_managed	= 1,
		},
		.prefix_info = {
			.header = {
				.type		= OPT_PREFIX_INFO,
				.len		= 4,
			},
			.prefix_len		= 64,
			.prefix_flags		= 0xc0,	/* prefix flags: L, A */
			.valid_lifetime		= ~0U,
			.pref_lifetime		= ~0U,
		},
		.source_ll = {
			.header = {
				.type		= OPT_SRC_L2_ADDR,
				.len		= 1,
			},
		},
	};
	const struct in6_addr *rsaddr; /* src addr for reply */
	unsigned char *ptr = NULL;
	size_t dlen;

	if (ih->icmp6_type < RS || ih->icmp6_type > NA)
		return 0;

	if (c->no_ndp)
		return 1;

	if (ih->icmp6_type == NS) {
		struct ndp_ns *ns = packet_get(p, 0, 0, sizeof(struct ndp_ns),
					       NULL);

		if (!ns)
			return -1;

		if (IN6_IS_ADDR_UNSPECIFIED(saddr))
			return 1;

		info("NDP: received NS, sending NA");

		memcpy(&na.target_addr, &ns->target_addr,
		       sizeof(na.target_addr));
		memcpy(na.target_l2_addr.mac, c->our_tap_mac, ETH_ALEN);

	} else if (ih->icmp6_type == RS) {
		size_t dns_s_len = 0;
		int i, n;

		if (c->no_ra)
			return 1;

		info("NDP: received RS, sending RA");
		memcpy(&ra.prefix, &c->ip6.addr, sizeof(ra.prefix));

		ptr = &ra.var[0];

		if (c->mtu != -1) {
			struct opt_mtu *mtu = (struct opt_mtu *)ptr;
			*mtu = (struct opt_mtu) {
				.header = {
					.type		= OPT_MTU,
					.len		= 1,
				},
				.value			= htonl(c->mtu),
			};
			ptr += sizeof(struct opt_mtu);
		}

		if (c->no_dhcp_dns)
			goto dns_done;

		for (n = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[n]); n++);
		if (n) {
			struct opt_rdnss *rdnss = (struct opt_rdnss *)ptr;
			*rdnss = (struct opt_rdnss) {
				.header = {
					.type		= OPT_RDNSS_TYPE,
					.len		= 1 + 2 * n,
				},
				.lifetime		= ~0U,
			};
			for (i = 0; i < n; i++) {
				memcpy(&rdnss->dns[i], &c->ip6.dns[i],
				       sizeof(rdnss->dns[i]));
			}
			ptr += offsetof(struct opt_rdnss, dns) +
			       i * sizeof(rdnss->dns[0]);

			for (n = 0; *c->dns_search[n].n; n++)
				dns_s_len += strlen(c->dns_search[n].n) + 2;
		}

		if (!c->no_dhcp_dns_search && dns_s_len) {
			struct opt_dnssl *dnssl = (struct opt_dnssl *)ptr;
			*dnssl = (struct opt_dnssl) {
				.header = {
					.type = OPT_DNSSL_TYPE,
					.len  = DIV_ROUND_UP(dns_s_len, 8) + 1,
				},
				.lifetime     = ~0U,
			};
			ptr = dnssl->domains;

			for (i = 0; i < n; i++) {
				size_t len;
				char *dot;

				*(ptr++) = '.';

				len = sizeof(dnssl->domains) -
				      (ptr - dnssl->domains);

				strncpy((char *)ptr, c->dns_search[i].n, len);
				for (dot = (char *)ptr - 1; *dot; dot++) {
					if (*dot == '.')
						*dot = strcspn(dot + 1, ".");
				}
				ptr += strlen(c->dns_search[i].n);
				*(ptr++) = 0;
			}

			memset(ptr, 0, 8 - dns_s_len % 8);	/* padding */
			ptr += 8 - dns_s_len % 8;
		}

dns_done:
		memcpy(&ra.source_ll.mac, c->our_tap_mac, ETH_ALEN);
	} else {
		return 1;
	}

	if (IN6_IS_ADDR_LINKLOCAL(saddr))
		c->ip6.addr_ll_seen = *saddr;
	else
		c->ip6.addr_seen = *saddr;

	rsaddr = &c->ip6.our_tap_ll;

	if (ih->icmp6_type == NS) {
		dlen = sizeof(struct ndp_na);
		tap_icmp6_send(c, rsaddr, saddr, &na, dlen);
	} else if (ih->icmp6_type == RS) {
		dlen = ptr - (unsigned char *)&ra;
		tap_icmp6_send(c, rsaddr, saddr, &ra, dlen);
	}

	return 1;
}