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