1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
61
62
63#define IP_SET_OP_VERSION 0x00000100
64struct ip_set_req_version {
65 unsigned int op;
66 unsigned int version;
67};
68#endif
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 close(sockfd);
88 return -1;
89 }
90
91 *version = req_version.version;
92 return sockfd;
93}
94
95static 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
120static int
121get_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
141static int
142get_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
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