1 /*
2  * em_ipset.c		IPset Ematch
3  *
4  * (C) 2012 Florian Westphal <fw@strlen.de>
5  *
6  * Parts taken from iptables libxt_set.h:
7  * Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
8  *                         Patrick Schaaf <bof@bof.de>
9  *                         Martin Josefsson <gandalf@wlug.westbo.se>
10  * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License version 2 as
14  * published by the Free Software Foundation.
15  */
16 
17 #include <stdbool.h>
18 #include <stdio.h>
19 #include <errno.h>
20 #include <netdb.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <getopt.h>
25 
26 #include <xtables.h>
27 #include <linux/netfilter/ipset/ip_set.h>
28 
29 #ifndef IPSET_INVALID_ID
30 typedef __u16 ip_set_id_t;
31 
32 enum ip_set_dim {
33 	IPSET_DIM_ZERO = 0,
34 	IPSET_DIM_ONE,
35 	IPSET_DIM_TWO,
36 	IPSET_DIM_THREE,
37 	IPSET_DIM_MAX = 6,
38 };
39 #endif /* IPSET_INVALID_ID */
40 
41 #include <linux/netfilter/xt_set.h>
42 #include "m_ematch.h"
43 
44 #ifndef IPSET_INVALID_ID
45 #define IPSET_INVALID_ID	65535
46 #define SO_IP_SET		83
47 
48 union ip_set_name_index {
49 	char name[IPSET_MAXNAMELEN];
50 	__u16 index;
51 };
52 
53 #define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
54 struct ip_set_req_get_set {
55 	unsigned int op;
56 	unsigned int version;
57 	union ip_set_name_index set;
58 };
59 
60 #define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
61 /* Uses ip_set_req_get_set */
62 
63 #define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
64 struct ip_set_req_version {
65 	unsigned int op;
66 	unsigned int version;
67 };
68 #endif /* IPSET_INVALID_ID */
69 
70 extern struct ematch_util ipset_ematch_util;
71 
get_version(unsigned int * version)72 static int get_version(unsigned int *version)
73 {
74 	int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
75 	struct ip_set_req_version req_version;
76 	socklen_t size = sizeof(req_version);
77 
78 	if (sockfd < 0) {
79 		fputs("Can't open socket to ipset.\n", stderr);
80 		return -1;
81 	}
82 
83 	req_version.op = IP_SET_OP_VERSION;
84 	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size);
85 	if (res != 0) {
86 		perror("xt_set getsockopt");
87 		close(sockfd);
88 		return -1;
89 	}
90 
91 	*version = req_version.version;
92 	return sockfd;
93 }
94 
do_getsockopt(struct ip_set_req_get_set * req)95 static int do_getsockopt(struct ip_set_req_get_set *req)
96 {
97 	int sockfd, res;
98 	socklen_t size = sizeof(struct ip_set_req_get_set);
99 
100 	sockfd = get_version(&req->version);
101 	if (sockfd < 0)
102 		return -1;
103 	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size);
104 	if (res != 0)
105 		perror("Problem when communicating with ipset");
106 	close(sockfd);
107 	if (res != 0)
108 		return -1;
109 
110 	if (size != sizeof(struct ip_set_req_get_set)) {
111 		fprintf(stderr,
112 			"Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n",
113 			sizeof(struct ip_set_req_get_set), (size_t)size);
114 		return -1;
115 	}
116 
117 	return res;
118 }
119 
120 static int
get_set_byid(char * setname,unsigned int idx)121 get_set_byid(char *setname, unsigned int idx)
122 {
123 	struct ip_set_req_get_set req;
124 	int res;
125 
126 	req.op = IP_SET_OP_GET_BYINDEX;
127 	req.set.index = idx;
128 	res = do_getsockopt(&req);
129 	if (res != 0)
130 		return -1;
131 	if (req.set.name[0] == '\0') {
132 		fprintf(stderr,
133 			"Set with index %i in kernel doesn't exist.\n", idx);
134 		return -1;
135 	}
136 
137 	strncpy(setname, req.set.name, IPSET_MAXNAMELEN);
138 	return 0;
139 }
140 
141 static int
get_set_byname(const char * setname,struct xt_set_info * info)142 get_set_byname(const char *setname, struct xt_set_info *info)
143 {
144 	struct ip_set_req_get_set req;
145 	int res;
146 
147 	req.op = IP_SET_OP_GET_BYNAME;
148 	strlcpy(req.set.name, setname, IPSET_MAXNAMELEN);
149 	res = do_getsockopt(&req);
150 	if (res != 0)
151 		return -1;
152 	if (req.set.index == IPSET_INVALID_ID)
153 		return -1;
154 	info->index = req.set.index;
155 	return 0;
156 }
157 
158 static int
parse_dirs(const char * opt_arg,struct xt_set_info * info)159 parse_dirs(const char *opt_arg, struct xt_set_info *info)
160 {
161 	char *saved = strdup(opt_arg);
162 	char *ptr, *tmp = saved;
163 
164 	if (!tmp) {
165 		perror("strdup");
166 		return -1;
167 	}
168 
169 	while (info->dim < IPSET_DIM_MAX && tmp != NULL) {
170 		info->dim++;
171 		ptr = strsep(&tmp, ",");
172 		if (strncmp(ptr, "src", 3) == 0)
173 			info->flags |= (1 << info->dim);
174 		else if (strncmp(ptr, "dst", 3) != 0) {
175 			fputs("You must specify (the comma separated list of) 'src' or 'dst'\n", stderr);
176 			free(saved);
177 			return -1;
178 		}
179 	}
180 
181 	if (tmp)
182 		fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX);
183 	free(saved);
184 	return tmp ? -1 : 0;
185 }
186 
ipset_print_usage(FILE * fd)187 static void ipset_print_usage(FILE *fd)
188 {
189 	fprintf(fd,
190 	    "Usage: ipset(SETNAME FLAGS)\n" \
191 	    "where: SETNAME:= string\n" \
192 	    "       FLAGS  := { FLAG[,FLAGS] }\n" \
193 	    "       FLAG   := { src | dst }\n" \
194 	    "\n" \
195 	    "Example: 'ipset(bulk src,dst)'\n");
196 }
197 
ipset_parse_eopt(struct nlmsghdr * n,struct tcf_ematch_hdr * hdr,struct bstr * args)198 static int ipset_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
199 			    struct bstr *args)
200 {
201 	struct xt_set_info set_info = {};
202 	int ret;
203 
204 #define PARSE_ERR(CARG, FMT, ARGS...) \
205 	em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT, ##ARGS)
206 
207 	if (args == NULL)
208 		return PARSE_ERR(args, "ipset: missing set name");
209 
210 	if (args->len >= IPSET_MAXNAMELEN)
211 		return PARSE_ERR(args, "ipset: set name too long (max %u)", IPSET_MAXNAMELEN - 1);
212 	ret = get_set_byname(args->data, &set_info);
213 	if (ret < 0)
214 		return PARSE_ERR(args, "ipset: unknown set name '%s'", args->data);
215 
216 	if (args->next == NULL)
217 		return PARSE_ERR(args, "ipset: missing set flags");
218 
219 	args = bstr_next(args);
220 	if (parse_dirs(args->data, &set_info))
221 		return PARSE_ERR(args, "ipset: error parsing set flags");
222 
223 	if (args->next) {
224 		args = bstr_next(args);
225 		return PARSE_ERR(args, "ipset: unknown parameter");
226 	}
227 
228 	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
229 	addraw_l(n, MAX_MSG, &set_info, sizeof(set_info));
230 
231 #undef PARSE_ERR
232 	return 0;
233 }
234 
ipset_print_eopt(FILE * fd,struct tcf_ematch_hdr * hdr,void * data,int data_len)235 static int ipset_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
236 			    int data_len)
237 {
238 	int i;
239 	char setname[IPSET_MAXNAMELEN];
240 	const struct xt_set_info *set_info = data;
241 
242 	if (data_len != sizeof(*set_info)) {
243 		fprintf(stderr, "xt_set_info struct size mismatch\n");
244 		return -1;
245 	}
246 
247 	if (get_set_byid(setname, set_info->index))
248 		return -1;
249 	fputs(setname, fd);
250 	for (i = 1; i <= set_info->dim; i++) {
251 		fprintf(fd, "%s%s", i == 1 ? " " : ",", set_info->flags & (1 << i) ? "src" : "dst");
252 	}
253 
254 	return 0;
255 }
256 
257 struct ematch_util ipset_ematch_util = {
258 	.kind = "ipset",
259 	.kind_num = TCF_EM_IPSET,
260 	.parse_eopt = ipset_parse_eopt,
261 	.print_eopt = ipset_print_eopt,
262 	.print_usage = ipset_print_usage
263 };
264