brintos

brintos / linux-shallow public Read only

0
0
Text · 18.2 KiB · 571cc07 Raw
781 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Inject packets with all sorts of encapsulation into the kernel.4 *5 * IPv4/IPv6	outer layer 36 * GRE/GUE/BARE outer layer 4, where bare is IPIP/SIT/IPv4-in-IPv6/..7 * IPv4/IPv6    inner layer 38 */9 10#define _GNU_SOURCE11 12#include <stddef.h>13#include <arpa/inet.h>14#include <asm/byteorder.h>15#include <error.h>16#include <errno.h>17#include <linux/if_packet.h>18#include <linux/if_ether.h>19#include <linux/ipv6.h>20#include <netinet/ip.h>21#include <netinet/in.h>22#include <netinet/udp.h>23#include <poll.h>24#include <stdbool.h>25#include <stdlib.h>26#include <stdio.h>27#include <string.h>28#include <sys/ioctl.h>29#include <sys/socket.h>30#include <sys/stat.h>31#include <sys/time.h>32#include <sys/types.h>33#include <unistd.h>34 35#define CFG_PORT_INNER	800036 37/* Add some protocol definitions that do not exist in userspace */38 39struct grehdr {40	uint16_t unused;41	uint16_t protocol;42} __attribute__((packed));43 44struct guehdr {45	union {46		struct {47#if defined(__LITTLE_ENDIAN_BITFIELD)48			__u8	hlen:5,49				control:1,50				version:2;51#elif defined (__BIG_ENDIAN_BITFIELD)52			__u8	version:2,53				control:1,54				hlen:5;55#else56#error  "Please fix <asm/byteorder.h>"57#endif58			__u8	proto_ctype;59			__be16	flags;60		};61		__be32	word;62	};63};64 65static uint8_t	cfg_dsfield_inner;66static uint8_t	cfg_dsfield_outer;67static uint8_t	cfg_encap_proto;68static bool	cfg_expect_failure = false;69static int	cfg_l3_extra = AF_UNSPEC;	/* optional SIT prefix */70static int	cfg_l3_inner = AF_UNSPEC;71static int	cfg_l3_outer = AF_UNSPEC;72static int	cfg_num_pkt = 10;73static int	cfg_num_secs = 0;74static char	cfg_payload_char = 'a';75static int	cfg_payload_len = 100;76static int	cfg_port_gue = 6080;77static bool	cfg_only_rx;78static bool	cfg_only_tx;79static int	cfg_src_port = 9;80 81static char	buf[ETH_DATA_LEN];82 83#define INIT_ADDR4(name, addr4, port)				\84	static struct sockaddr_in name = {			\85		.sin_family = AF_INET,				\86		.sin_port = __constant_htons(port),		\87		.sin_addr.s_addr = __constant_htonl(addr4),	\88	};89 90#define INIT_ADDR6(name, addr6, port)				\91	static struct sockaddr_in6 name = {			\92		.sin6_family = AF_INET6,			\93		.sin6_port = __constant_htons(port),		\94		.sin6_addr = addr6,				\95	};96 97INIT_ADDR4(in_daddr4, INADDR_LOOPBACK, CFG_PORT_INNER)98INIT_ADDR4(in_saddr4, INADDR_LOOPBACK + 2, 0)99INIT_ADDR4(out_daddr4, INADDR_LOOPBACK, 0)100INIT_ADDR4(out_saddr4, INADDR_LOOPBACK + 1, 0)101INIT_ADDR4(extra_daddr4, INADDR_LOOPBACK, 0)102INIT_ADDR4(extra_saddr4, INADDR_LOOPBACK + 1, 0)103 104INIT_ADDR6(in_daddr6, IN6ADDR_LOOPBACK_INIT, CFG_PORT_INNER)105INIT_ADDR6(in_saddr6, IN6ADDR_LOOPBACK_INIT, 0)106INIT_ADDR6(out_daddr6, IN6ADDR_LOOPBACK_INIT, 0)107INIT_ADDR6(out_saddr6, IN6ADDR_LOOPBACK_INIT, 0)108INIT_ADDR6(extra_daddr6, IN6ADDR_LOOPBACK_INIT, 0)109INIT_ADDR6(extra_saddr6, IN6ADDR_LOOPBACK_INIT, 0)110 111static unsigned long util_gettime(void)112{113	struct timeval tv;114 115	gettimeofday(&tv, NULL);116	return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);117}118 119static void util_printaddr(const char *msg, struct sockaddr *addr)120{121	unsigned long off = 0;122	char nbuf[INET6_ADDRSTRLEN];123 124	switch (addr->sa_family) {125	case PF_INET:126		off = __builtin_offsetof(struct sockaddr_in, sin_addr);127		break;128	case PF_INET6:129		off = __builtin_offsetof(struct sockaddr_in6, sin6_addr);130		break;131	default:132		error(1, 0, "printaddr: unsupported family %u\n",133		      addr->sa_family);134	}135 136	if (!inet_ntop(addr->sa_family, ((void *) addr) + off, nbuf,137		       sizeof(nbuf)))138		error(1, errno, "inet_ntop");139 140	fprintf(stderr, "%s: %s\n", msg, nbuf);141}142 143static unsigned long add_csum_hword(const uint16_t *start, int num_u16)144{145	unsigned long sum = 0;146	int i;147 148	for (i = 0; i < num_u16; i++)149		sum += start[i];150 151	return sum;152}153 154static uint16_t build_ip_csum(const uint16_t *start, int num_u16,155			      unsigned long sum)156{157	sum += add_csum_hword(start, num_u16);158 159	while (sum >> 16)160		sum = (sum & 0xffff) + (sum >> 16);161 162	return ~sum;163}164 165static void build_ipv4_header(void *header, uint8_t proto,166			      uint32_t src, uint32_t dst,167			      int payload_len, uint8_t tos)168{169	struct iphdr *iph = header;170 171	iph->ihl = 5;172	iph->version = 4;173	iph->tos = tos;174	iph->ttl = 8;175	iph->tot_len = htons(sizeof(*iph) + payload_len);176	iph->id = htons(1337);177	iph->protocol = proto;178	iph->saddr = src;179	iph->daddr = dst;180	iph->check = build_ip_csum((void *) iph, iph->ihl << 1, 0);181}182 183static void ipv6_set_dsfield(struct ipv6hdr *ip6h, uint8_t dsfield)184{185	uint16_t val, *ptr = (uint16_t *)ip6h;186 187	val = ntohs(*ptr);188	val &= 0xF00F;189	val |= ((uint16_t) dsfield) << 4;190	*ptr = htons(val);191}192 193static void build_ipv6_header(void *header, uint8_t proto,194			      struct sockaddr_in6 *src,195			      struct sockaddr_in6 *dst,196			      int payload_len, uint8_t dsfield)197{198	struct ipv6hdr *ip6h = header;199 200	ip6h->version = 6;201	ip6h->payload_len = htons(payload_len);202	ip6h->nexthdr = proto;203	ip6h->hop_limit = 8;204	ipv6_set_dsfield(ip6h, dsfield);205 206	memcpy(&ip6h->saddr, &src->sin6_addr, sizeof(ip6h->saddr));207	memcpy(&ip6h->daddr, &dst->sin6_addr, sizeof(ip6h->daddr));208}209 210static uint16_t build_udp_v4_csum(const struct iphdr *iph,211				  const struct udphdr *udph,212				  int num_words)213{214	unsigned long pseudo_sum;215	int num_u16 = sizeof(iph->saddr);	/* halfwords: twice byte len */216 217	pseudo_sum = add_csum_hword((void *) &iph->saddr, num_u16);218	pseudo_sum += htons(IPPROTO_UDP);219	pseudo_sum += udph->len;220	return build_ip_csum((void *) udph, num_words, pseudo_sum);221}222 223static uint16_t build_udp_v6_csum(const struct ipv6hdr *ip6h,224				  const struct udphdr *udph,225				  int num_words)226{227	unsigned long pseudo_sum;228	int num_u16 = sizeof(ip6h->saddr);	/* halfwords: twice byte len */229 230	pseudo_sum = add_csum_hword((void *) &ip6h->saddr, num_u16);231	pseudo_sum += htons(ip6h->nexthdr);232	pseudo_sum += ip6h->payload_len;233	return build_ip_csum((void *) udph, num_words, pseudo_sum);234}235 236static void build_udp_header(void *header, int payload_len,237			     uint16_t dport, int family)238{239	struct udphdr *udph = header;240	int len = sizeof(*udph) + payload_len;241 242	udph->source = htons(cfg_src_port);243	udph->dest = htons(dport);244	udph->len = htons(len);245	udph->check = 0;246	if (family == AF_INET)247		udph->check = build_udp_v4_csum(header - sizeof(struct iphdr),248						udph, len >> 1);249	else250		udph->check = build_udp_v6_csum(header - sizeof(struct ipv6hdr),251						udph, len >> 1);252}253 254static void build_gue_header(void *header, uint8_t proto)255{256	struct guehdr *gueh = header;257 258	gueh->proto_ctype = proto;259}260 261static void build_gre_header(void *header, uint16_t proto)262{263	struct grehdr *greh = header;264 265	greh->protocol = htons(proto);266}267 268static int l3_length(int family)269{270	if (family == AF_INET)271		return sizeof(struct iphdr);272	else273		return sizeof(struct ipv6hdr);274}275 276static int build_packet(void)277{278	int ol3_len = 0, ol4_len = 0, il3_len = 0, il4_len = 0;279	int el3_len = 0;280 281	if (cfg_l3_extra)282		el3_len = l3_length(cfg_l3_extra);283 284	/* calculate header offsets */285	if (cfg_encap_proto) {286		ol3_len = l3_length(cfg_l3_outer);287 288		if (cfg_encap_proto == IPPROTO_GRE)289			ol4_len = sizeof(struct grehdr);290		else if (cfg_encap_proto == IPPROTO_UDP)291			ol4_len = sizeof(struct udphdr) + sizeof(struct guehdr);292	}293 294	il3_len = l3_length(cfg_l3_inner);295	il4_len = sizeof(struct udphdr);296 297	if (el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len >=298	    sizeof(buf))299		error(1, 0, "packet too large\n");300 301	/*302	 * Fill packet from inside out, to calculate correct checksums.303	 * But create ip before udp headers, as udp uses ip for pseudo-sum.304	 */305	memset(buf + el3_len + ol3_len + ol4_len + il3_len + il4_len,306	       cfg_payload_char, cfg_payload_len);307 308	/* add zero byte for udp csum padding */309	buf[el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len] = 0;310 311	switch (cfg_l3_inner) {312	case PF_INET:313		build_ipv4_header(buf + el3_len + ol3_len + ol4_len,314				  IPPROTO_UDP,315				  in_saddr4.sin_addr.s_addr,316				  in_daddr4.sin_addr.s_addr,317				  il4_len + cfg_payload_len,318				  cfg_dsfield_inner);319		break;320	case PF_INET6:321		build_ipv6_header(buf + el3_len + ol3_len + ol4_len,322				  IPPROTO_UDP,323				  &in_saddr6, &in_daddr6,324				  il4_len + cfg_payload_len,325				  cfg_dsfield_inner);326		break;327	}328 329	build_udp_header(buf + el3_len + ol3_len + ol4_len + il3_len,330			 cfg_payload_len, CFG_PORT_INNER, cfg_l3_inner);331 332	if (!cfg_encap_proto)333		return il3_len + il4_len + cfg_payload_len;334 335	switch (cfg_l3_outer) {336	case PF_INET:337		build_ipv4_header(buf + el3_len, cfg_encap_proto,338				  out_saddr4.sin_addr.s_addr,339				  out_daddr4.sin_addr.s_addr,340				  ol4_len + il3_len + il4_len + cfg_payload_len,341				  cfg_dsfield_outer);342		break;343	case PF_INET6:344		build_ipv6_header(buf + el3_len, cfg_encap_proto,345				  &out_saddr6, &out_daddr6,346				  ol4_len + il3_len + il4_len + cfg_payload_len,347				  cfg_dsfield_outer);348		break;349	}350 351	switch (cfg_encap_proto) {352	case IPPROTO_UDP:353		build_gue_header(buf + el3_len + ol3_len + ol4_len -354				 sizeof(struct guehdr),355				 cfg_l3_inner == PF_INET ? IPPROTO_IPIP356							 : IPPROTO_IPV6);357		build_udp_header(buf + el3_len + ol3_len,358				 sizeof(struct guehdr) + il3_len + il4_len +359				 cfg_payload_len,360				 cfg_port_gue, cfg_l3_outer);361		break;362	case IPPROTO_GRE:363		build_gre_header(buf + el3_len + ol3_len,364				 cfg_l3_inner == PF_INET ? ETH_P_IP365							 : ETH_P_IPV6);366		break;367	}368 369	switch (cfg_l3_extra) {370	case PF_INET:371		build_ipv4_header(buf,372				  cfg_l3_outer == PF_INET ? IPPROTO_IPIP373							  : IPPROTO_IPV6,374				  extra_saddr4.sin_addr.s_addr,375				  extra_daddr4.sin_addr.s_addr,376				  ol3_len + ol4_len + il3_len + il4_len +377				  cfg_payload_len, 0);378		break;379	case PF_INET6:380		build_ipv6_header(buf,381				  cfg_l3_outer == PF_INET ? IPPROTO_IPIP382							  : IPPROTO_IPV6,383				  &extra_saddr6, &extra_daddr6,384				  ol3_len + ol4_len + il3_len + il4_len +385				  cfg_payload_len, 0);386		break;387	}388 389	return el3_len + ol3_len + ol4_len + il3_len + il4_len +390	       cfg_payload_len;391}392 393/* sender transmits encapsulated over RAW or unencap'd over UDP */394static int setup_tx(void)395{396	int family, fd, ret;397 398	if (cfg_l3_extra)399		family = cfg_l3_extra;400	else if (cfg_l3_outer)401		family = cfg_l3_outer;402	else403		family = cfg_l3_inner;404 405	fd = socket(family, SOCK_RAW, IPPROTO_RAW);406	if (fd == -1)407		error(1, errno, "socket tx");408 409	if (cfg_l3_extra) {410		if (cfg_l3_extra == PF_INET)411			ret = connect(fd, (void *) &extra_daddr4,412				      sizeof(extra_daddr4));413		else414			ret = connect(fd, (void *) &extra_daddr6,415				      sizeof(extra_daddr6));416		if (ret)417			error(1, errno, "connect tx");418	} else if (cfg_l3_outer) {419		/* connect to destination if not encapsulated */420		if (cfg_l3_outer == PF_INET)421			ret = connect(fd, (void *) &out_daddr4,422				      sizeof(out_daddr4));423		else424			ret = connect(fd, (void *) &out_daddr6,425				      sizeof(out_daddr6));426		if (ret)427			error(1, errno, "connect tx");428	} else {429		/* otherwise using loopback */430		if (cfg_l3_inner == PF_INET)431			ret = connect(fd, (void *) &in_daddr4,432				      sizeof(in_daddr4));433		else434			ret = connect(fd, (void *) &in_daddr6,435				      sizeof(in_daddr6));436		if (ret)437			error(1, errno, "connect tx");438	}439 440	return fd;441}442 443/* receiver reads unencapsulated UDP */444static int setup_rx(void)445{446	int fd, ret;447 448	fd = socket(cfg_l3_inner, SOCK_DGRAM, 0);449	if (fd == -1)450		error(1, errno, "socket rx");451 452	if (cfg_l3_inner == PF_INET)453		ret = bind(fd, (void *) &in_daddr4, sizeof(in_daddr4));454	else455		ret = bind(fd, (void *) &in_daddr6, sizeof(in_daddr6));456	if (ret)457		error(1, errno, "bind rx");458 459	return fd;460}461 462static int do_tx(int fd, const char *pkt, int len)463{464	int ret;465 466	ret = write(fd, pkt, len);467	if (ret == -1)468		error(1, errno, "send");469	if (ret != len)470		error(1, errno, "send: len (%d < %d)\n", ret, len);471 472	return 1;473}474 475static int do_poll(int fd, short events, int timeout)476{477	struct pollfd pfd;478	int ret;479 480	pfd.fd = fd;481	pfd.events = events;482 483	ret = poll(&pfd, 1, timeout);484	if (ret == -1)485		error(1, errno, "poll");486	if (ret && !(pfd.revents & POLLIN))487		error(1, errno, "poll: unexpected event 0x%x\n", pfd.revents);488 489	return ret;490}491 492static int do_rx(int fd)493{494	char rbuf;495	int ret, num = 0;496 497	while (1) {498		ret = recv(fd, &rbuf, 1, MSG_DONTWAIT);499		if (ret == -1 && errno == EAGAIN)500			break;501		if (ret == -1)502			error(1, errno, "recv");503		if (rbuf != cfg_payload_char)504			error(1, 0, "recv: payload mismatch");505		num++;506	}507 508	return num;509}510 511static int do_main(void)512{513	unsigned long tstop, treport, tcur;514	int fdt = -1, fdr = -1, len, tx = 0, rx = 0;515 516	if (!cfg_only_tx)517		fdr = setup_rx();518	if (!cfg_only_rx)519		fdt = setup_tx();520 521	len = build_packet();522 523	tcur = util_gettime();524	treport = tcur + 1000;525	tstop = tcur + (cfg_num_secs * 1000);526 527	while (1) {528		if (!cfg_only_rx)529			tx += do_tx(fdt, buf, len);530 531		if (!cfg_only_tx)532			rx += do_rx(fdr);533 534		if (cfg_num_secs) {535			tcur = util_gettime();536			if (tcur >= tstop)537				break;538			if (tcur >= treport) {539				fprintf(stderr, "pkts: tx=%u rx=%u\n", tx, rx);540				tx = 0;541				rx = 0;542				treport = tcur + 1000;543			}544		} else {545			if (tx == cfg_num_pkt)546				break;547		}548	}549 550	/* read straggler packets, if any */551	if (rx < tx) {552		tstop = util_gettime() + 100;553		while (rx < tx) {554			tcur = util_gettime();555			if (tcur >= tstop)556				break;557 558			do_poll(fdr, POLLIN, tstop - tcur);559			rx += do_rx(fdr);560		}561	}562 563	fprintf(stderr, "pkts: tx=%u rx=%u\n", tx, rx);564 565	if (fdr != -1 && close(fdr))566		error(1, errno, "close rx");567	if (fdt != -1 && close(fdt))568		error(1, errno, "close tx");569 570	/*571	 * success (== 0) only if received all packets572	 * unless failure is expected, in which case none must arrive.573	 */574	if (cfg_expect_failure)575		return rx != 0;576	else577		return rx != tx;578}579 580 581static void __attribute__((noreturn)) usage(const char *filepath)582{583	fprintf(stderr, "Usage: %s [-e gre|gue|bare|none] [-i 4|6] [-l len] "584			"[-O 4|6] [-o 4|6] [-n num] [-t secs] [-R] [-T] "585			"[-s <osrc> [-d <odst>] [-S <isrc>] [-D <idst>] "586			"[-x <otos>] [-X <itos>] [-f <isport>] [-F]\n",587		filepath);588	exit(1);589}590 591static void parse_addr(int family, void *addr, const char *optarg)592{593	int ret;594 595	ret = inet_pton(family, optarg, addr);596	if (ret == -1)597		error(1, errno, "inet_pton");598	if (ret == 0)599		error(1, 0, "inet_pton: bad string");600}601 602static void parse_addr4(struct sockaddr_in *addr, const char *optarg)603{604	parse_addr(AF_INET, &addr->sin_addr, optarg);605}606 607static void parse_addr6(struct sockaddr_in6 *addr, const char *optarg)608{609	parse_addr(AF_INET6, &addr->sin6_addr, optarg);610}611 612static int parse_protocol_family(const char *filepath, const char *optarg)613{614	if (!strcmp(optarg, "4"))615		return PF_INET;616	if (!strcmp(optarg, "6"))617		return PF_INET6;618 619	usage(filepath);620}621 622static void parse_opts(int argc, char **argv)623{624	int c;625 626	while ((c = getopt(argc, argv, "d:D:e:f:Fhi:l:n:o:O:Rs:S:t:Tx:X:")) != -1) {627		switch (c) {628		case 'd':629			if (cfg_l3_outer == AF_UNSPEC)630				error(1, 0, "-d must be preceded by -o");631			if (cfg_l3_outer == AF_INET)632				parse_addr4(&out_daddr4, optarg);633			else634				parse_addr6(&out_daddr6, optarg);635			break;636		case 'D':637			if (cfg_l3_inner == AF_UNSPEC)638				error(1, 0, "-D must be preceded by -i");639			if (cfg_l3_inner == AF_INET)640				parse_addr4(&in_daddr4, optarg);641			else642				parse_addr6(&in_daddr6, optarg);643			break;644		case 'e':645			if (!strcmp(optarg, "gre"))646				cfg_encap_proto = IPPROTO_GRE;647			else if (!strcmp(optarg, "gue"))648				cfg_encap_proto = IPPROTO_UDP;649			else if (!strcmp(optarg, "bare"))650				cfg_encap_proto = IPPROTO_IPIP;651			else if (!strcmp(optarg, "none"))652				cfg_encap_proto = IPPROTO_IP;	/* == 0 */653			else654				usage(argv[0]);655			break;656		case 'f':657			cfg_src_port = strtol(optarg, NULL, 0);658			break;659		case 'F':660			cfg_expect_failure = true;661			break;662		case 'h':663			usage(argv[0]);664			break;665		case 'i':666			if (!strcmp(optarg, "4"))667				cfg_l3_inner = PF_INET;668			else if (!strcmp(optarg, "6"))669				cfg_l3_inner = PF_INET6;670			else671				usage(argv[0]);672			break;673		case 'l':674			cfg_payload_len = strtol(optarg, NULL, 0);675			break;676		case 'n':677			cfg_num_pkt = strtol(optarg, NULL, 0);678			break;679		case 'o':680			cfg_l3_outer = parse_protocol_family(argv[0], optarg);681			break;682		case 'O':683			cfg_l3_extra = parse_protocol_family(argv[0], optarg);684			break;685		case 'R':686			cfg_only_rx = true;687			break;688		case 's':689			if (cfg_l3_outer == AF_INET)690				parse_addr4(&out_saddr4, optarg);691			else692				parse_addr6(&out_saddr6, optarg);693			break;694		case 'S':695			if (cfg_l3_inner == AF_INET)696				parse_addr4(&in_saddr4, optarg);697			else698				parse_addr6(&in_saddr6, optarg);699			break;700		case 't':701			cfg_num_secs = strtol(optarg, NULL, 0);702			break;703		case 'T':704			cfg_only_tx = true;705			break;706		case 'x':707			cfg_dsfield_outer = strtol(optarg, NULL, 0);708			break;709		case 'X':710			cfg_dsfield_inner = strtol(optarg, NULL, 0);711			break;712		}713	}714 715	if (cfg_only_rx && cfg_only_tx)716		error(1, 0, "options: cannot combine rx-only and tx-only");717 718	if (cfg_encap_proto && cfg_l3_outer == AF_UNSPEC)719		error(1, 0, "options: must specify outer with encap");720	else if ((!cfg_encap_proto) && cfg_l3_outer != AF_UNSPEC)721		error(1, 0, "options: cannot combine no-encap and outer");722	else if ((!cfg_encap_proto) && cfg_l3_extra != AF_UNSPEC)723		error(1, 0, "options: cannot combine no-encap and extra");724 725	if (cfg_l3_inner == AF_UNSPEC)726		cfg_l3_inner = AF_INET6;727	if (cfg_l3_inner == AF_INET6 && cfg_encap_proto == IPPROTO_IPIP)728		cfg_encap_proto = IPPROTO_IPV6;729 730	/* RFC 6040 4.2:731	 *   on decap, if outer encountered congestion (CE == 0x3),732	 *   but inner cannot encode ECN (NoECT == 0x0), then drop packet.733	 */734	if (((cfg_dsfield_outer & 0x3) == 0x3) &&735	    ((cfg_dsfield_inner & 0x3) == 0x0))736		cfg_expect_failure = true;737}738 739static void print_opts(void)740{741	if (cfg_l3_inner == PF_INET6) {742		util_printaddr("inner.dest6", (void *) &in_daddr6);743		util_printaddr("inner.source6", (void *) &in_saddr6);744	} else {745		util_printaddr("inner.dest4", (void *) &in_daddr4);746		util_printaddr("inner.source4", (void *) &in_saddr4);747	}748 749	if (!cfg_l3_outer)750		return;751 752	fprintf(stderr, "encap proto:   %u\n", cfg_encap_proto);753 754	if (cfg_l3_outer == PF_INET6) {755		util_printaddr("outer.dest6", (void *) &out_daddr6);756		util_printaddr("outer.source6", (void *) &out_saddr6);757	} else {758		util_printaddr("outer.dest4", (void *) &out_daddr4);759		util_printaddr("outer.source4", (void *) &out_saddr4);760	}761 762	if (!cfg_l3_extra)763		return;764 765	if (cfg_l3_outer == PF_INET6) {766		util_printaddr("extra.dest6", (void *) &extra_daddr6);767		util_printaddr("extra.source6", (void *) &extra_saddr6);768	} else {769		util_printaddr("extra.dest4", (void *) &extra_daddr4);770		util_printaddr("extra.source4", (void *) &extra_saddr4);771	}772 773}774 775int main(int argc, char **argv)776{777	parse_opts(argc, argv);778	print_opts();779	return do_main();780}781