| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | /* Shared library add-on to iptables to add source-NAT support. */ | 
 | 2 | #include <stdio.h> | 
 | 3 | #include <netdb.h> | 
 | 4 | #include <string.h> | 
 | 5 | #include <stdlib.h> | 
 | 6 | #include <getopt.h> | 
 | 7 | #include <xtables.h> | 
 | 8 | #include <iptables.h> | 
 | 9 | #include <limits.h> /* INT_MAX in ip_tables.h */ | 
 | 10 | #include <linux/netfilter_ipv4/ip_tables.h> | 
 | 11 | #include <net/netfilter/nf_nat.h> | 
 | 12 |  | 
 | 13 | #define IPT_SNAT_OPT_SOURCE 0x01 | 
 | 14 | #define IPT_SNAT_OPT_RANDOM 0x02 | 
 | 15 |  | 
 | 16 | /* Source NAT data consists of a multi-range, indicating where to map | 
 | 17 |    to. */ | 
 | 18 | struct ipt_natinfo | 
 | 19 | { | 
 | 20 | 	struct xt_entry_target t; | 
 | 21 | 	struct nf_nat_multi_range mr; | 
 | 22 | }; | 
 | 23 |  | 
 | 24 | static void SNAT_help(void) | 
 | 25 | { | 
 | 26 | 	printf( | 
 | 27 | "SNAT target options:\n" | 
 | 28 | " --to-source <ipaddr>[-<ipaddr>][:port-port]\n" | 
 | 29 | "				Address to map source to.\n" | 
 | 30 | "[--random]\n"); | 
 | 31 | } | 
 | 32 |  | 
 | 33 | static const struct option SNAT_opts[] = { | 
 | 34 | 	{ "to-source", 1, NULL, '1' }, | 
 | 35 | 	{ "random", 0, NULL, '2' }, | 
 | 36 | 	{ .name = NULL } | 
 | 37 | }; | 
 | 38 |  | 
 | 39 | static struct ipt_natinfo * | 
 | 40 | append_range(struct ipt_natinfo *info, const struct nf_nat_range *range) | 
 | 41 | { | 
 | 42 | 	unsigned int size; | 
 | 43 |  | 
 | 44 | 	/* One rangesize already in struct ipt_natinfo */ | 
 | 45 | 	size = XT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range)); | 
 | 46 |  | 
 | 47 | 	info = realloc(info, size); | 
 | 48 | 	if (!info) | 
 | 49 | 		xtables_error(OTHER_PROBLEM, "Out of memory\n"); | 
 | 50 |  | 
 | 51 | 	info->t.u.target_size = size; | 
 | 52 | 	info->mr.range[info->mr.rangesize] = *range; | 
 | 53 | 	info->mr.rangesize++; | 
 | 54 |  | 
 | 55 | 	return info; | 
 | 56 | } | 
 | 57 |  | 
 | 58 | /* Ranges expected in network order. */ | 
 | 59 | static struct xt_entry_target * | 
 | 60 | parse_to(char *arg, int portok, struct ipt_natinfo *info) | 
 | 61 | { | 
 | 62 | 	struct nf_nat_range range; | 
 | 63 | 	char *colon, *dash, *error; | 
 | 64 | 	const struct in_addr *ip; | 
 | 65 |  | 
 | 66 | 	memset(&range, 0, sizeof(range)); | 
 | 67 | 	colon = strchr(arg, ':'); | 
 | 68 |  | 
 | 69 | 	if (colon) { | 
 | 70 | 		int port; | 
 | 71 |  | 
 | 72 | 		if (!portok) | 
 | 73 | 			xtables_error(PARAMETER_PROBLEM, | 
 | 74 | 				   "Need TCP, UDP, SCTP or DCCP with port specification"); | 
 | 75 |  | 
 | 76 | 		range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED; | 
 | 77 |  | 
 | 78 | 		port = atoi(colon+1); | 
 | 79 | 		if (port <= 0 || port > 65535) | 
 | 80 | 			xtables_error(PARAMETER_PROBLEM, | 
 | 81 | 				   "Port `%s' not valid\n", colon+1); | 
 | 82 |  | 
 | 83 | 		error = strchr(colon+1, ':'); | 
 | 84 | 		if (error) | 
 | 85 | 			xtables_error(PARAMETER_PROBLEM, | 
 | 86 | 				   "Invalid port:port syntax - use dash\n"); | 
 | 87 |  | 
 | 88 | 		dash = strchr(colon, '-'); | 
 | 89 | 		if (!dash) { | 
 | 90 | 			range.min.tcp.port | 
 | 91 | 				= range.max.tcp.port | 
 | 92 | 				= htons(port); | 
 | 93 | 		} else { | 
 | 94 | 			int maxport; | 
 | 95 |  | 
 | 96 | 			maxport = atoi(dash + 1); | 
 | 97 | 			if (maxport <= 0 || maxport > 65535) | 
 | 98 | 				xtables_error(PARAMETER_PROBLEM, | 
 | 99 | 					   "Port `%s' not valid\n", dash+1); | 
 | 100 | 			if (maxport < port) | 
 | 101 | 				/* People are stupid. */ | 
 | 102 | 				xtables_error(PARAMETER_PROBLEM, | 
 | 103 | 					   "Port range `%s' funky\n", colon+1); | 
 | 104 | 			range.min.tcp.port = htons(port); | 
 | 105 | 			range.max.tcp.port = htons(maxport); | 
 | 106 | 		} | 
 | 107 | 		/* Starts with a colon? No IP info...*/ | 
 | 108 | 		if (colon == arg) | 
 | 109 | 			return &(append_range(info, &range)->t); | 
 | 110 | 		*colon = '\0'; | 
 | 111 | 	} | 
 | 112 |  | 
 | 113 | 	range.flags |= IP_NAT_RANGE_MAP_IPS; | 
 | 114 | 	dash = strchr(arg, '-'); | 
 | 115 | 	if (colon && dash && dash > colon) | 
 | 116 | 		dash = NULL; | 
 | 117 |  | 
 | 118 | 	if (dash) | 
 | 119 | 		*dash = '\0'; | 
 | 120 |  | 
 | 121 | 	ip = xtables_numeric_to_ipaddr(arg); | 
 | 122 | 	if (!ip) | 
 | 123 | 		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n", | 
 | 124 | 			   arg); | 
 | 125 | 	range.min_ip = ip->s_addr; | 
 | 126 | 	if (dash) { | 
 | 127 | 		ip = xtables_numeric_to_ipaddr(dash+1); | 
 | 128 | 		if (!ip) | 
 | 129 | 			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n", | 
 | 130 | 				   dash+1); | 
 | 131 | 		range.max_ip = ip->s_addr; | 
 | 132 | 	} else | 
 | 133 | 		range.max_ip = range.min_ip; | 
 | 134 |  | 
 | 135 | 	return &(append_range(info, &range)->t); | 
 | 136 | } | 
 | 137 |  | 
 | 138 | static int SNAT_parse(int c, char **argv, int invert, unsigned int *flags, | 
 | 139 |                       const void *e, struct xt_entry_target **target) | 
 | 140 | { | 
 | 141 | 	const struct ipt_entry *entry = e; | 
 | 142 | 	struct ipt_natinfo *info = (void *)*target; | 
 | 143 | 	int portok; | 
 | 144 |  | 
 | 145 | 	if (entry->ip.proto == IPPROTO_TCP | 
 | 146 | 	    || entry->ip.proto == IPPROTO_UDP | 
 | 147 | 	    || entry->ip.proto == IPPROTO_SCTP | 
 | 148 | 	    || entry->ip.proto == IPPROTO_DCCP | 
 | 149 | 	    || entry->ip.proto == IPPROTO_ICMP) | 
 | 150 | 		portok = 1; | 
 | 151 | 	else | 
 | 152 | 		portok = 0; | 
 | 153 |  | 
 | 154 | 	switch (c) { | 
 | 155 | 	case '1': | 
 | 156 | 		if (xtables_check_inverse(optarg, &invert, NULL, 0)) | 
 | 157 | 			xtables_error(PARAMETER_PROBLEM, | 
 | 158 | 				   "Unexpected `!' after --to-source"); | 
 | 159 |  | 
 | 160 | 		if (*flags & IPT_SNAT_OPT_SOURCE) { | 
 | 161 | 			if (!kernel_version) | 
 | 162 | 				get_kernel_version(); | 
 | 163 | 			if (kernel_version > LINUX_VERSION(2, 6, 10)) | 
 | 164 | 				xtables_error(PARAMETER_PROBLEM, | 
 | 165 | 					   "Multiple --to-source not supported"); | 
 | 166 | 		} | 
 | 167 | 		*target = parse_to(optarg, portok, info); | 
 | 168 | 		/* WTF do we need this for?? */ | 
 | 169 | 		if (*flags & IPT_SNAT_OPT_RANDOM) | 
 | 170 | 			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM; | 
 | 171 | 		*flags |= IPT_SNAT_OPT_SOURCE; | 
 | 172 | 		return 1; | 
 | 173 |  | 
 | 174 | 	case '2': | 
 | 175 | 		if (*flags & IPT_SNAT_OPT_SOURCE) { | 
 | 176 | 			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM; | 
 | 177 | 			*flags |= IPT_SNAT_OPT_RANDOM; | 
 | 178 | 		} else | 
 | 179 | 			*flags |= IPT_SNAT_OPT_RANDOM; | 
 | 180 | 		return 1; | 
 | 181 |  | 
 | 182 | 	default: | 
 | 183 | 		return 0; | 
 | 184 | 	} | 
 | 185 | } | 
 | 186 |  | 
 | 187 | static void SNAT_check(unsigned int flags) | 
 | 188 | { | 
 | 189 | 	if (!(flags & IPT_SNAT_OPT_SOURCE)) | 
 | 190 | 		xtables_error(PARAMETER_PROBLEM, | 
 | 191 | 			   "You must specify --to-source"); | 
 | 192 | } | 
 | 193 |  | 
 | 194 | static void print_range(const struct nf_nat_range *r) | 
 | 195 | { | 
 | 196 | 	if (r->flags & IP_NAT_RANGE_MAP_IPS) { | 
 | 197 | 		struct in_addr a; | 
 | 198 |  | 
 | 199 | 		a.s_addr = r->min_ip; | 
 | 200 | 		printf("%s", xtables_ipaddr_to_numeric(&a)); | 
 | 201 | 		if (r->max_ip != r->min_ip) { | 
 | 202 | 			a.s_addr = r->max_ip; | 
 | 203 | 			printf("-%s", xtables_ipaddr_to_numeric(&a)); | 
 | 204 | 		} | 
 | 205 | 	} | 
 | 206 | 	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { | 
 | 207 | 		printf(":"); | 
 | 208 | 		printf("%hu", ntohs(r->min.tcp.port)); | 
 | 209 | 		if (r->max.tcp.port != r->min.tcp.port) | 
 | 210 | 			printf("-%hu", ntohs(r->max.tcp.port)); | 
 | 211 | 	} | 
 | 212 | } | 
 | 213 |  | 
 | 214 | static void SNAT_print(const void *ip, const struct xt_entry_target *target, | 
 | 215 |                        int numeric) | 
 | 216 | { | 
 | 217 | 	struct ipt_natinfo *info = (void *)target; | 
 | 218 | 	unsigned int i = 0; | 
 | 219 |  | 
 | 220 | 	printf("to:"); | 
 | 221 | 	for (i = 0; i < info->mr.rangesize; i++) { | 
 | 222 | 		print_range(&info->mr.range[i]); | 
 | 223 | 		printf(" "); | 
 | 224 | 		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM) | 
 | 225 | 			printf("random "); | 
 | 226 | 	} | 
 | 227 | } | 
 | 228 |  | 
 | 229 | static void SNAT_save(const void *ip, const struct xt_entry_target *target) | 
 | 230 | { | 
 | 231 | 	struct ipt_natinfo *info = (void *)target; | 
 | 232 | 	unsigned int i = 0; | 
 | 233 |  | 
 | 234 | 	for (i = 0; i < info->mr.rangesize; i++) { | 
 | 235 | 		printf("--to-source "); | 
 | 236 | 		print_range(&info->mr.range[i]); | 
 | 237 | 		printf(" "); | 
 | 238 | 		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM) | 
 | 239 | 			printf("--random "); | 
 | 240 | 	} | 
 | 241 | } | 
 | 242 |  | 
 | 243 | static struct xtables_target snat_tg_reg = { | 
 | 244 | 	.name		= "SNAT", | 
 | 245 | 	.version	= XTABLES_VERSION, | 
 | 246 | 	.family		= NFPROTO_IPV4, | 
 | 247 | 	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)), | 
 | 248 | 	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)), | 
 | 249 | 	.help		= SNAT_help, | 
 | 250 | 	.parse		= SNAT_parse, | 
 | 251 | 	.final_check	= SNAT_check, | 
 | 252 | 	.print		= SNAT_print, | 
 | 253 | 	.save		= SNAT_save, | 
 | 254 | 	.extra_opts	= SNAT_opts, | 
 | 255 | }; | 
 | 256 |  | 
 | 257 | void _init(void) | 
 | 258 | { | 
 | 259 | 	xtables_register_target(&snat_tg_reg); | 
 | 260 | } |