em_ipset.c revision d17b136f7d7dd6ed7ea518e4f068d3de735e8756
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
30typedef __u16 ip_set_id_t;
31
32enum 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
48union 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 */
54struct 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 */
64struct ip_set_req_version {
65	unsigned int op;
66	unsigned int version;
67};
68#endif /* IPSET_INVALID_ID */
69
70extern struct ematch_util ipset_ematch_util;
71
72static 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		return -1;
88	}
89
90	*version = req_version.version;
91	return sockfd;
92}
93
94static int do_getsockopt(struct ip_set_req_get_set *req)
95{
96	int sockfd, res;
97	socklen_t size = sizeof(struct ip_set_req_get_set);
98
99	sockfd = get_version(&req->version);
100	if (sockfd < 0)
101		return -1;
102	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size);
103	if (res != 0)
104		perror("Problem when communicating with ipset");
105	close(sockfd);
106	if (res != 0)
107		return -1;
108
109	if (size != sizeof(struct ip_set_req_get_set)) {
110		fprintf(stderr,
111			"Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n",
112			sizeof(struct ip_set_req_get_set), (size_t)size);
113		return -1;
114	}
115
116	return res;
117}
118
119static int
120get_set_byid(char *setname, unsigned int idx)
121{
122	struct ip_set_req_get_set req;
123	int res;
124
125	req.op = IP_SET_OP_GET_BYINDEX;
126	req.set.index = idx;
127	res = do_getsockopt(&req);
128	if (res != 0)
129		return -1;
130	if (req.set.name[0] == '\0') {
131		fprintf(stderr,
132			"Set with index %i in kernel doesn't exist.\n", idx);
133		return -1;
134	}
135
136	strncpy(setname, req.set.name, IPSET_MAXNAMELEN);
137	return 0;
138}
139
140static int
141get_set_byname(const char *setname, struct xt_set_info *info)
142{
143	struct ip_set_req_get_set req;
144	int res;
145
146	req.op = IP_SET_OP_GET_BYNAME;
147	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
148	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
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
158static int
159parse_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
187static 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
198static 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
235static 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
257struct 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