]>
Commit | Line | Data |
---|---|---|
0fdebb3b | 1 | /* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@netfilter.org) |
8e0608d3 | 2 | * |
3f83fda4 JK |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License version 2 as | |
8e0608d3 JK |
5 | * published by the Free Software Foundation. |
6 | */ | |
7 | #include <assert.h> /* assert */ | |
5a602182 | 8 | #include <endian.h> /* htobe64 */ |
8e0608d3 | 9 | #include <errno.h> /* errno */ |
ad4d8027 | 10 | #include <setjmp.h> /* setjmp, longjmp */ |
8e0608d3 JK |
11 | #include <stdio.h> /* snprintf */ |
12 | #include <stdarg.h> /* va_* */ | |
55fdd96e | 13 | #include <stdbool.h> /* bool */ |
8e0608d3 JK |
14 | #include <stdlib.h> /* free */ |
15 | #include <string.h> /* str* */ | |
16 | #include <unistd.h> /* getpagesize */ | |
17 | #include <net/ethernet.h> /* ETH_ALEN */ | |
418a3a4f | 18 | #include <net/if.h> /* IFNAMSIZ */ |
8e0608d3 | 19 | |
910d9b62 | 20 | #include <libipset/compat.h> /* be64toh() */ |
3fd6b24a | 21 | #include <libipset/debug.h> /* D() */ |
8e0608d3 JK |
22 | #include <libipset/data.h> /* IPSET_OPT_* */ |
23 | #include <libipset/errcode.h> /* ipset_errcode */ | |
24 | #include <libipset/print.h> /* ipset_print_* */ | |
25 | #include <libipset/types.h> /* struct ipset_type */ | |
26 | #include <libipset/transport.h> /* transport */ | |
27 | #include <libipset/mnl.h> /* default backend */ | |
28 | #include <libipset/utils.h> /* STREQ */ | |
55fdd96e | 29 | #include <libipset/ipset.h> /* IPSET_ENV_* */ |
3713072d | 30 | #include <libipset/list_sort.h> /* list_sort */ |
8e0608d3 JK |
31 | #include <libipset/session.h> /* prototypes */ |
32 | ||
33 | #define IPSET_NEST_MAX 4 | |
34 | ||
3713072d JK |
35 | /* When we want to sort the entries */ |
36 | struct ipset_sorted { | |
37 | struct list_head list; | |
38 | size_t offset; /* Offset in outbuf */ | |
39 | }; | |
40 | ||
41 | ||
8e0608d3 JK |
42 | /* The session structure */ |
43 | struct ipset_session { | |
44 | const struct ipset_transport *transport;/* Transport protocol */ | |
45 | struct ipset_handle *handle; /* Transport handler */ | |
46 | struct ipset_data *data; /* Input/output data */ | |
47 | /* Command state */ | |
48 | enum ipset_cmd cmd; /* Current command */ | |
49 | uint32_t lineno; /* Current lineno in restore mode */ | |
31ee222f | 50 | uint32_t printed_set; /* Printed sets so far */ |
8e0608d3 | 51 | char saved_setname[IPSET_MAXNAMELEN]; /* Saved setname */ |
020936c8 | 52 | const struct ipset_type *saved_type; /* Saved type */ |
8e0608d3 JK |
53 | struct nlattr *nested[IPSET_NEST_MAX]; /* Pointer to nest levels */ |
54 | uint8_t nestid; /* Current nest level */ | |
b934ebcd | 55 | uint8_t protocol; /* The protocol used */ |
8e0608d3 JK |
56 | bool version_checked; /* Version checked */ |
57 | /* Output buffer */ | |
3713072d JK |
58 | char *outbuf; /* Output buffer */ |
59 | size_t outbuflen; /* Output buffer size */ | |
60 | size_t pos; /* Printing position in outbuf */ | |
61 | struct list_head sorted; /* Sorted entries */ | |
62 | struct list_head pool; /* Pool to reuse */ | |
8e0608d3 | 63 | enum ipset_output_mode mode; /* Output mode */ |
55fdd96e JK |
64 | ipset_print_outfn print_outfn; /* Output function to file */ |
65 | void *p; /* Private data for print_outfn */ | |
3713072d | 66 | bool sort; /* Print sorted hash:* types */ |
0b08f9f1 | 67 | size_t save_elem_prefix; /* "add setname " */ |
55fdd96e JK |
68 | /* Session IO */ |
69 | bool normal_io, full_io; /* Default/normal/full IO */ | |
70 | FILE *istream, *ostream; /* Session input/output stream */ | |
8e0608d3 JK |
71 | /* Error/warning reporting */ |
72 | char report[IPSET_ERRORBUFLEN]; /* Error/report buffer */ | |
c387170f | 73 | enum ipset_err_type err_type; /* ERROR/WARNING/NOTICE */ |
8e0608d3 JK |
74 | uint8_t envopts; /* Session env opts */ |
75 | /* Kernel message buffer */ | |
76 | size_t bufsize; | |
8dd10256 | 77 | void *buffer; |
8e0608d3 JK |
78 | }; |
79 | ||
80 | /* | |
81 | * Glue functions | |
82 | */ | |
83 | ||
84 | /** | |
85 | * ipset_session_data - return pointer to the data | |
86 | * @session: session structure | |
87 | * | |
88 | * Returns the pointer to the data structure of the session. | |
89 | */ | |
90 | struct ipset_data * | |
91 | ipset_session_data(const struct ipset_session *session) | |
92 | { | |
93 | assert(session); | |
94 | return session->data; | |
95 | } | |
96 | ||
97 | /** | |
98 | * ipset_session_handle - return pointer to the handle | |
99 | * @session: session structure | |
100 | * | |
101 | * Returns the pointer to the transport handle structure of the session. | |
102 | */ | |
103 | struct ipset_handle * | |
104 | ipset_session_handle(const struct ipset_session *session) | |
105 | { | |
106 | assert(session); | |
107 | return session->handle; | |
108 | } | |
109 | ||
020936c8 JK |
110 | /** |
111 | * ipset_saved_type - return pointer to the saved type | |
112 | * @session: session structure | |
113 | * | |
114 | * Returns the pointer to the saved type from the last ipset_cmd | |
115 | * It is required to decode type-specific error codes in restore mode. | |
116 | */ | |
117 | const struct ipset_type * | |
118 | ipset_saved_type(const struct ipset_session *session) | |
119 | { | |
120 | assert(session); | |
121 | return session->saved_type; | |
122 | } | |
123 | ||
cac607bd JK |
124 | /** |
125 | * ipset_session_lineno - set session lineno | |
126 | * @session: session structure | |
127 | * | |
128 | * Set session lineno to report parser errors correctly. | |
129 | */ | |
130 | void | |
131 | ipset_session_lineno(struct ipset_session *session, uint32_t lineno) | |
132 | { | |
133 | assert(session); | |
134 | session->lineno = lineno; | |
135 | } | |
136 | ||
8e0608d3 | 137 | /** |
55fdd96e | 138 | * ipset_session_printf_private - returns the session private pointer |
8e0608d3 | 139 | * @session: session structure |
8e0608d3 | 140 | * |
55fdd96e JK |
141 | * Returns the private pointer in the session structure, |
142 | * for private/custom print fuctions. | |
8e0608d3 | 143 | */ |
55fdd96e JK |
144 | void * |
145 | ipset_session_printf_private(struct ipset_session *session) | |
8e0608d3 JK |
146 | { |
147 | assert(session); | |
55fdd96e | 148 | return session->p; |
8e0608d3 JK |
149 | } |
150 | ||
55fdd96e JK |
151 | /* |
152 | * Environment options | |
153 | */ | |
154 | ||
8e0608d3 JK |
155 | /** |
156 | * ipset_envopt_test - test environment option | |
157 | * @session: session structure | |
158 | * @opt: environment option | |
159 | * | |
160 | * Test whether the environment option is set in the session. | |
161 | * | |
162 | * Returns true or false. | |
163 | */ | |
164 | bool | |
165 | ipset_envopt_test(struct ipset_session *session, enum ipset_envopt opt) | |
166 | { | |
167 | assert(session); | |
168 | return session->envopts & opt; | |
169 | } | |
170 | ||
55fdd96e JK |
171 | /** |
172 | * ipset_envopt_set - set environment option | |
173 | * @session: session structure | |
174 | * @opt: environment option | |
175 | * | |
176 | * Set an environment option of the session. | |
177 | */ | |
178 | void | |
179 | ipset_envopt_set(struct ipset_session *session, enum ipset_envopt opt) | |
180 | { | |
181 | assert(session); | |
182 | session->envopts |= opt; | |
183 | } | |
184 | ||
185 | /** | |
186 | * ipset_envopt_unset - unset environment option | |
187 | * @session: session structure | |
188 | * @opt: environment option | |
189 | * | |
190 | * Unset an environment option of the session. | |
191 | */ | |
192 | void | |
193 | ipset_envopt_unset(struct ipset_session *session, enum ipset_envopt opt) | |
194 | { | |
195 | assert(session); | |
196 | session->envopts &= ~opt; | |
197 | } | |
198 | ||
8e0608d3 JK |
199 | /** |
200 | * ipset_session_output - set the session output mode | |
201 | * @session: session structure | |
202 | * @mode: output mode | |
203 | * | |
204 | * Set the output mode for the session. | |
205 | * | |
206 | * Returns 0 on success or a negative error code. | |
207 | */ | |
208 | int | |
209 | ipset_session_output(struct ipset_session *session, | |
210 | enum ipset_output_mode mode) | |
211 | { | |
212 | assert(session); | |
213 | session->mode = mode; | |
214 | return 0; | |
215 | } | |
216 | ||
217 | /* | |
218 | * Error and warning reporting | |
219 | */ | |
220 | ||
221 | /** | |
222 | * ipset_session_report - fill the report buffer | |
223 | * @session: session structure | |
224 | * @type: report type | |
225 | * @fmt: message format | |
226 | * | |
227 | * Fill the report buffer with an error or warning message. | |
228 | * Depending on the report type, set the error or warning | |
229 | * message pointer. | |
230 | * | |
231 | * Returns -1. | |
232 | */ | |
3f83fda4 | 233 | int __attribute__((format(printf, 3, 4))) |
8e0608d3 | 234 | ipset_session_report(struct ipset_session *session, |
3f83fda4 | 235 | enum ipset_err_type type, |
8e0608d3 JK |
236 | const char *fmt, ...) |
237 | { | |
238 | int len, offset = 0; | |
239 | va_list args; | |
3f83fda4 | 240 | |
8e0608d3 JK |
241 | assert(session); |
242 | assert(fmt); | |
243 | ||
c387170f JK |
244 | /* Suppress warning/notice when more important message is required */ |
245 | if (session->err_type > IPSET_NO_ERROR && session->err_type < type) | |
246 | session->report[0] = '\0'; | |
247 | ||
8e0608d3 JK |
248 | if (session->lineno != 0 && type == IPSET_ERROR) { |
249 | sprintf(session->report, "Error in line %u: ", | |
250 | session->lineno); | |
251 | } | |
252 | offset = strlen(session->report); | |
3f83fda4 | 253 | |
8e0608d3 JK |
254 | va_start(args, fmt); |
255 | len = vsnprintf(session->report + offset, | |
3f83fda4 JK |
256 | IPSET_ERRORBUFLEN - 1 - offset, |
257 | fmt, args); | |
8e0608d3 | 258 | va_end(args); |
3f83fda4 | 259 | |
8e0608d3 JK |
260 | if (len >= IPSET_ERRORBUFLEN - 1 - offset) |
261 | session->report[IPSET_ERRORBUFLEN - 1] = '\0'; | |
3fd6b24a JK |
262 | if (strlen(session->report) < IPSET_ERRORBUFLEN - 1) |
263 | strcat(session->report, "\n"); | |
8e0608d3 | 264 | |
c387170f JK |
265 | session->err_type = type; |
266 | if (type == IPSET_ERROR) | |
dbd6853f | 267 | ipset_data_reset(ipset_session_data(session)); |
c387170f | 268 | |
8e0608d3 JK |
269 | return -1; |
270 | } | |
271 | ||
51660085 JK |
272 | /** |
273 | * ipset_session_warning_as_error - set warning as error | |
274 | * @session: session structrure | |
275 | * | |
276 | * Returns -1. | |
277 | */ | |
278 | int | |
279 | ipset_session_warning_as_error(struct ipset_session *session) | |
280 | { | |
c387170f | 281 | session->err_type = IPSET_ERROR; |
51660085 JK |
282 | ipset_data_reset(ipset_session_data(session)); |
283 | return -1; | |
284 | } | |
285 | ||
8e0608d3 JK |
286 | /** |
287 | * ipset_session_reset - reset the report buffer | |
288 | * @session: session structure | |
289 | * | |
290 | * Reset the report buffer, the error and warning pointers. | |
291 | */ | |
292 | void | |
293 | ipset_session_report_reset(struct ipset_session *session) | |
294 | { | |
295 | assert(session); | |
296 | session->report[0] = '\0'; | |
c387170f | 297 | session->err_type = IPSET_NO_ERROR; |
8e0608d3 JK |
298 | } |
299 | ||
300 | /** | |
c387170f | 301 | * ipset_session_report_msg - return the report buffer |
8e0608d3 JK |
302 | * @session: session structure |
303 | * | |
c387170f JK |
304 | * Return the pointer to the report buffer. |
305 | * If there is no error message, the buffer is empty. | |
8e0608d3 JK |
306 | */ |
307 | const char * | |
c387170f | 308 | ipset_session_report_msg(const struct ipset_session *session) |
8e0608d3 JK |
309 | { |
310 | assert(session); | |
311 | ||
c387170f | 312 | return session->report; |
8e0608d3 JK |
313 | } |
314 | ||
315 | /** | |
c387170f | 316 | * ipset_session_report_type - return the type of the report |
8e0608d3 JK |
317 | * @session: session structure |
318 | * | |
c387170f | 319 | * Return the type of the message in the report buffer. |
8e0608d3 | 320 | */ |
c387170f JK |
321 | enum ipset_err_type |
322 | ipset_session_report_type(const struct ipset_session *session) | |
8e0608d3 JK |
323 | { |
324 | assert(session); | |
325 | ||
c387170f | 326 | return session->err_type; |
8e0608d3 JK |
327 | } |
328 | ||
329 | /* | |
330 | * Receive data from the kernel | |
331 | */ | |
332 | ||
333 | struct ipset_attr_policy { | |
334 | uint16_t type; | |
335 | uint16_t len; | |
336 | enum ipset_opt opt; | |
337 | }; | |
338 | ||
339 | /* Attribute policies and mapping to options */ | |
95b9dfb3 | 340 | static const struct ipset_attr_policy cmd_attrs[] = { |
8e0608d3 JK |
341 | [IPSET_ATTR_PROTOCOL] = { |
342 | .type = MNL_TYPE_U8, | |
343 | }, | |
344 | [IPSET_ATTR_SETNAME] = { | |
345 | .type = MNL_TYPE_NUL_STRING, | |
346 | .opt = IPSET_SETNAME, | |
347 | .len = IPSET_MAXNAMELEN, | |
348 | }, | |
349 | [IPSET_ATTR_TYPENAME] = { | |
350 | .type = MNL_TYPE_NUL_STRING, | |
351 | .opt = IPSET_OPT_TYPENAME, | |
352 | .len = IPSET_MAXNAMELEN, | |
353 | }, | |
354 | /* IPSET_ATTR_SETNAME2 is an alias for IPSET_ATTR_TYPENAME */ | |
355 | [IPSET_ATTR_REVISION] = { | |
356 | .type = MNL_TYPE_U8, | |
357 | .opt = IPSET_OPT_REVISION, | |
358 | }, | |
359 | [IPSET_ATTR_FAMILY] = { | |
360 | .type = MNL_TYPE_U8, | |
361 | .opt = IPSET_OPT_FAMILY, | |
362 | }, | |
3fd6b24a JK |
363 | [IPSET_ATTR_FLAGS] = { |
364 | .type = MNL_TYPE_U32, | |
365 | .opt = IPSET_OPT_FLAGS, | |
366 | }, | |
8e0608d3 JK |
367 | [IPSET_ATTR_DATA] = { |
368 | .type = MNL_TYPE_NESTED, | |
369 | }, | |
370 | [IPSET_ATTR_ADT] = { | |
371 | .type = MNL_TYPE_NESTED, | |
372 | }, | |
373 | [IPSET_ATTR_REVISION_MIN] = { | |
374 | .type = MNL_TYPE_U8, | |
375 | .opt = IPSET_OPT_REVISION_MIN, | |
376 | }, | |
377 | /* IPSET_ATTR_PROTOCOL_MIN is an alias for IPSET_ATTR_REVISION_MIN */ | |
378 | [IPSET_ATTR_LINENO] = { | |
379 | .type = MNL_TYPE_U32, | |
380 | .opt = IPSET_OPT_LINENO, | |
381 | }, | |
4a1797e2 JK |
382 | [IPSET_ATTR_INDEX] = { |
383 | .type = MNL_TYPE_U16, | |
384 | .opt = IPSET_OPT_INDEX, | |
385 | }, | |
8e0608d3 JK |
386 | }; |
387 | ||
95b9dfb3 | 388 | static const struct ipset_attr_policy create_attrs[] = { |
8e0608d3 | 389 | [IPSET_ATTR_IP] = { |
0d32c5c0 | 390 | .type = MNL_TYPE_NESTED, |
8e0608d3 JK |
391 | .opt = IPSET_OPT_IP, |
392 | }, | |
393 | [IPSET_ATTR_IP_TO] = { | |
0d32c5c0 | 394 | .type = MNL_TYPE_NESTED, |
8e0608d3 JK |
395 | .opt = IPSET_OPT_IP_TO, |
396 | }, | |
397 | [IPSET_ATTR_CIDR] = { | |
398 | .type = MNL_TYPE_U8, | |
399 | .opt = IPSET_OPT_CIDR, | |
400 | }, | |
401 | [IPSET_ATTR_PORT] = { | |
402 | .type = MNL_TYPE_U16, | |
403 | .opt = IPSET_OPT_PORT, | |
404 | }, | |
405 | [IPSET_ATTR_PORT_TO] = { | |
406 | .type = MNL_TYPE_U16, | |
407 | .opt = IPSET_OPT_PORT_TO, | |
408 | }, | |
409 | [IPSET_ATTR_TIMEOUT] = { | |
410 | .type = MNL_TYPE_U32, | |
411 | .opt = IPSET_OPT_TIMEOUT, | |
412 | }, | |
020936c8 JK |
413 | [IPSET_ATTR_PROTO] = { |
414 | .type = MNL_TYPE_U8, | |
415 | .opt = IPSET_OPT_PROTO, | |
416 | }, | |
3fd6b24a | 417 | [IPSET_ATTR_CADT_FLAGS] = { |
8e0608d3 | 418 | .type = MNL_TYPE_U32, |
3fd6b24a | 419 | .opt = IPSET_OPT_CADT_FLAGS, |
8e0608d3 | 420 | }, |
280fe2d4 | 421 | [IPSET_ATTR_INITVAL] = { |
8e0608d3 | 422 | .type = MNL_TYPE_U32, |
280fe2d4 | 423 | .opt = IPSET_OPT_INITVAL, |
8e0608d3 JK |
424 | }, |
425 | [IPSET_ATTR_HASHSIZE] = { | |
426 | .type = MNL_TYPE_U32, | |
427 | .opt = IPSET_OPT_HASHSIZE, | |
428 | }, | |
429 | [IPSET_ATTR_MAXELEM] = { | |
430 | .type = MNL_TYPE_U32, | |
431 | .opt = IPSET_OPT_MAXELEM, | |
432 | }, | |
2dfb973c VD |
433 | [IPSET_ATTR_MARKMASK] = { |
434 | .type = MNL_TYPE_U32, | |
435 | .opt = IPSET_OPT_MARKMASK, | |
436 | }, | |
8e0608d3 JK |
437 | [IPSET_ATTR_NETMASK] = { |
438 | .type = MNL_TYPE_U8, | |
439 | .opt = IPSET_OPT_NETMASK, | |
440 | }, | |
de340a7f | 441 | [IPSET_ATTR_BUCKETSIZE] = { |
8e0608d3 | 442 | .type = MNL_TYPE_U8, |
de340a7f | 443 | .opt = IPSET_OPT_BUCKETSIZE, |
8e0608d3 JK |
444 | }, |
445 | [IPSET_ATTR_RESIZE] = { | |
446 | .type = MNL_TYPE_U8, | |
447 | .opt = IPSET_OPT_RESIZE, | |
448 | }, | |
449 | [IPSET_ATTR_SIZE] = { | |
450 | .type = MNL_TYPE_U32, | |
451 | .opt = IPSET_OPT_SIZE, | |
452 | }, | |
453 | [IPSET_ATTR_ELEMENTS] = { | |
454 | .type = MNL_TYPE_U32, | |
455 | .opt = IPSET_OPT_ELEMENTS, | |
456 | }, | |
457 | [IPSET_ATTR_REFERENCES] = { | |
458 | .type = MNL_TYPE_U32, | |
459 | .opt = IPSET_OPT_REFERENCES, | |
460 | }, | |
461 | [IPSET_ATTR_MEMSIZE] = { | |
462 | .type = MNL_TYPE_U32, | |
463 | .opt = IPSET_OPT_MEMSIZE, | |
464 | }, | |
b50666c0 VP |
465 | [IPSET_ATTR_BITMASK] = { |
466 | .type = MNL_TYPE_NESTED, | |
467 | .opt = IPSET_OPT_BITMASK, | |
468 | }, | |
8e0608d3 JK |
469 | }; |
470 | ||
95b9dfb3 | 471 | static const struct ipset_attr_policy adt_attrs[] = { |
8e0608d3 | 472 | [IPSET_ATTR_IP] = { |
0d32c5c0 | 473 | .type = MNL_TYPE_NESTED, |
8e0608d3 JK |
474 | .opt = IPSET_OPT_IP, |
475 | }, | |
476 | [IPSET_ATTR_IP_TO] = { | |
0d32c5c0 | 477 | .type = MNL_TYPE_NESTED, |
8e0608d3 JK |
478 | .opt = IPSET_OPT_IP_TO, |
479 | }, | |
480 | [IPSET_ATTR_CIDR] = { | |
481 | .type = MNL_TYPE_U8, | |
482 | .opt = IPSET_OPT_CIDR, | |
483 | }, | |
14ea38fc VD |
484 | [IPSET_ATTR_MARK] = { |
485 | .type = MNL_TYPE_U32, | |
486 | .opt = IPSET_OPT_MARK, | |
487 | }, | |
8e0608d3 JK |
488 | [IPSET_ATTR_PORT] = { |
489 | .type = MNL_TYPE_U16, | |
490 | .opt = IPSET_OPT_PORT, | |
491 | }, | |
492 | [IPSET_ATTR_PORT_TO] = { | |
493 | .type = MNL_TYPE_U16, | |
494 | .opt = IPSET_OPT_PORT_TO, | |
495 | }, | |
020936c8 JK |
496 | [IPSET_ATTR_PROTO] = { |
497 | .type = MNL_TYPE_U8, | |
498 | .opt = IPSET_OPT_PROTO, | |
499 | }, | |
8e0608d3 JK |
500 | [IPSET_ATTR_TIMEOUT] = { |
501 | .type = MNL_TYPE_U32, | |
502 | .opt = IPSET_OPT_TIMEOUT, | |
503 | }, | |
3fd6b24a | 504 | [IPSET_ATTR_CADT_FLAGS] = { |
8e0608d3 | 505 | .type = MNL_TYPE_U32, |
3fd6b24a | 506 | .opt = IPSET_OPT_CADT_FLAGS, |
8e0608d3 JK |
507 | }, |
508 | [IPSET_ATTR_LINENO] = { | |
509 | .type = MNL_TYPE_U32, | |
510 | .opt = IPSET_OPT_LINENO, | |
511 | }, | |
512 | [IPSET_ATTR_ETHER] = { | |
513 | .type = MNL_TYPE_BINARY, | |
514 | .opt = IPSET_OPT_ETHER, | |
515 | .len = ETH_ALEN, | |
516 | }, | |
517 | [IPSET_ATTR_NAME] = { | |
518 | .type = MNL_TYPE_NUL_STRING, | |
519 | .opt = IPSET_OPT_NAME, | |
520 | .len = IPSET_MAXNAMELEN, | |
521 | }, | |
522 | [IPSET_ATTR_NAMEREF] = { | |
523 | .type = MNL_TYPE_NUL_STRING, | |
524 | .opt = IPSET_OPT_NAMEREF, | |
525 | .len = IPSET_MAXNAMELEN, | |
526 | }, | |
527 | [IPSET_ATTR_IP2] = { | |
0d32c5c0 | 528 | .type = MNL_TYPE_NESTED, |
8e0608d3 JK |
529 | .opt = IPSET_OPT_IP2, |
530 | }, | |
531 | [IPSET_ATTR_CIDR2] = { | |
532 | .type = MNL_TYPE_U8, | |
533 | .opt = IPSET_OPT_CIDR2, | |
534 | }, | |
bb4f6b81 JK |
535 | [IPSET_ATTR_IP2_TO] = { |
536 | .type = MNL_TYPE_NESTED, | |
537 | .opt = IPSET_OPT_IP2_TO, | |
538 | }, | |
418a3a4f JK |
539 | [IPSET_ATTR_IFACE] = { |
540 | .type = MNL_TYPE_NUL_STRING, | |
541 | .opt = IPSET_OPT_IFACE, | |
542 | .len = IFNAMSIZ, | |
543 | }, | |
5a602182 JK |
544 | [IPSET_ATTR_PACKETS] = { |
545 | .type = MNL_TYPE_U64, | |
546 | .opt = IPSET_OPT_PACKETS, | |
547 | }, | |
548 | [IPSET_ATTR_BYTES] = { | |
549 | .type = MNL_TYPE_U64, | |
550 | .opt = IPSET_OPT_BYTES, | |
551 | }, | |
e1cc3d78 OS |
552 | [IPSET_ATTR_COMMENT] = { |
553 | .type = MNL_TYPE_NUL_STRING, | |
554 | .opt = IPSET_OPT_ADT_COMMENT, | |
555 | .len = IPSET_MAX_COMMENT_SIZE + 1, | |
556 | }, | |
c1dd8442 AD |
557 | [IPSET_ATTR_SKBMARK] = { |
558 | .type = MNL_TYPE_U64, | |
559 | .opt = IPSET_OPT_SKBMARK, | |
560 | }, | |
561 | [IPSET_ATTR_SKBPRIO] = { | |
562 | .type = MNL_TYPE_U32, | |
563 | .opt = IPSET_OPT_SKBPRIO, | |
564 | }, | |
565 | [IPSET_ATTR_SKBQUEUE] = { | |
566 | .type = MNL_TYPE_U16, | |
567 | .opt = IPSET_OPT_SKBQUEUE, | |
568 | }, | |
6728c0b2 JK |
569 | [IPSET_ATTR_PAD] = { |
570 | .type = MNL_TYPE_UNSPEC, | |
571 | .len = 0, | |
572 | }, | |
8e0608d3 JK |
573 | }; |
574 | ||
95b9dfb3 | 575 | static const struct ipset_attr_policy ipaddr_attrs[] = { |
0d32c5c0 JK |
576 | [IPSET_ATTR_IPADDR_IPV4] = { |
577 | .type = MNL_TYPE_U32, | |
578 | }, | |
579 | [IPSET_ATTR_IPADDR_IPV6] = { | |
580 | .type = MNL_TYPE_BINARY, | |
581 | .len = sizeof(union nf_inet_addr), | |
582 | }, | |
583 | }; | |
584 | ||
eb77f0db JK |
585 | #ifdef IPSET_DEBUG |
586 | static int debug = 1; | |
587 | #endif | |
588 | ||
0d32c5c0 JK |
589 | static int |
590 | generic_data_attr_cb(const struct nlattr *attr, void *data, | |
591 | int attr_max, const struct ipset_attr_policy *policy) | |
592 | { | |
8673a740 | 593 | const struct nlattr **tb = data; |
0d32c5c0 | 594 | int type = mnl_attr_get_type(attr); |
3f83fda4 | 595 | |
eb77f0db | 596 | IF_D(debug, "attr type: %u, len %u", type, attr->nla_len); |
0d32c5c0 | 597 | if (mnl_attr_type_valid(attr, attr_max) < 0) { |
eb77f0db | 598 | IF_D(debug, "attr type: %u INVALID", type); |
0d32c5c0 JK |
599 | return MNL_CB_ERROR; |
600 | } | |
601 | if (mnl_attr_validate(attr, policy[type].type) < 0) { | |
eb77f0db JK |
602 | IF_D(debug, "attr type: %u POLICY, attrlen %u", type, |
603 | mnl_attr_get_payload_len(attr)); | |
0d32c5c0 JK |
604 | return MNL_CB_ERROR; |
605 | } | |
3f83fda4 | 606 | if (policy[type].type == MNL_TYPE_NUL_STRING && |
e1cc3d78 | 607 | mnl_attr_get_payload_len(attr) > policy[type].len) |
3f83fda4 | 608 | return MNL_CB_ERROR; |
0d32c5c0 JK |
609 | tb[type] = attr; |
610 | return MNL_CB_OK; | |
611 | } | |
612 | ||
613 | static int | |
614 | create_attr_cb(const struct nlattr *attr, void *data) | |
615 | { | |
616 | return generic_data_attr_cb(attr, data, | |
617 | IPSET_ATTR_CREATE_MAX, create_attrs); | |
618 | } | |
619 | ||
620 | static int | |
621 | adt_attr_cb(const struct nlattr *attr, void *data) | |
622 | { | |
623 | return generic_data_attr_cb(attr, data, | |
624 | IPSET_ATTR_ADT_MAX, adt_attrs); | |
625 | } | |
626 | ||
627 | static int | |
628 | ipaddr_attr_cb(const struct nlattr *attr, void *data) | |
629 | { | |
630 | return generic_data_attr_cb(attr, data, | |
631 | IPSET_ATTR_IPADDR_MAX, ipaddr_attrs); | |
632 | } | |
633 | ||
8e0608d3 JK |
634 | #define FAILURE(format, args...) \ |
635 | { ipset_err(session, format , ## args); return MNL_CB_ERROR; } | |
636 | ||
637 | static int | |
638 | attr2data(struct ipset_session *session, struct nlattr *nla[], | |
639 | int type, const struct ipset_attr_policy attrs[]) | |
640 | { | |
641 | struct ipset_data *data = session->data; | |
642 | const struct ipset_attr_policy *attr; | |
643 | const void *d; | |
5a602182 | 644 | uint64_t v64; |
24b35d0b JK |
645 | uint32_t v32; |
646 | uint16_t v16; | |
3fd6b24a | 647 | int ret; |
8e0608d3 JK |
648 | |
649 | attr = &attrs[type]; | |
650 | d = mnl_attr_get_payload(nla[type]); | |
651 | ||
6728c0b2 JK |
652 | if (attr->type == MNL_TYPE_UNSPEC) |
653 | return 0; | |
0d32c5c0 JK |
654 | if (attr->type == MNL_TYPE_NESTED && attr->opt) { |
655 | /* IP addresses */ | |
656 | struct nlattr *ipattr[IPSET_ATTR_IPADDR_MAX+1] = {}; | |
8e0608d3 | 657 | uint8_t family = ipset_data_family(data); |
0d32c5c0 | 658 | int atype; |
24b35d0b | 659 | D("IP attr type %u", type); |
0d32c5c0 JK |
660 | if (mnl_attr_parse_nested(nla[type], |
661 | ipaddr_attr_cb, ipattr) < 0) | |
662 | FAILURE("Broken kernel message, cannot validate " | |
663 | "IP address attribute!"); | |
8e0608d3 JK |
664 | |
665 | /* Validate by hand */ | |
666 | switch (family) { | |
541e3286 | 667 | case NFPROTO_IPV4: |
0d32c5c0 JK |
668 | atype = IPSET_ATTR_IPADDR_IPV4; |
669 | if (!ipattr[atype]) | |
670 | FAILURE("Broken kernel message: IPv4 address " | |
671 | "expected but not received!"); | |
672 | if (ipattr[atype]->nla_len < sizeof(uint32_t)) | |
8e0608d3 | 673 | FAILURE("Broken kernel message: " |
0d32c5c0 JK |
674 | "cannot validate IPv4 " |
675 | "address attribute!"); | |
8e0608d3 | 676 | break; |
541e3286 | 677 | case NFPROTO_IPV6: |
0d32c5c0 JK |
678 | atype = IPSET_ATTR_IPADDR_IPV6; |
679 | if (!ipattr[atype]) | |
680 | FAILURE("Broken kernel message: IPv6 address " | |
681 | "expected but not received!"); | |
682 | if (ipattr[atype]->nla_len < sizeof(struct in6_addr)) | |
8e0608d3 | 683 | FAILURE("Broken kernel message: " |
0d32c5c0 JK |
684 | "cannot validate IPv6 " |
685 | "address attribute!"); | |
8e0608d3 JK |
686 | break; |
687 | default: | |
688 | FAILURE("Broken kernel message: " | |
689 | "IP address attribute but " | |
690 | "family is unspecified!"); | |
691 | } | |
0d32c5c0 | 692 | d = mnl_attr_get_payload(ipattr[atype]); |
8e0608d3 | 693 | } else if (nla[type]->nla_type & NLA_F_NET_BYTEORDER) { |
24b35d0b | 694 | D("netorder attr type %u", type); |
8e0608d3 | 695 | switch (attr->type) { |
5a602182 | 696 | case MNL_TYPE_U64: { |
05169249 JK |
697 | uint64_t tmp; |
698 | /* Ensure data alignment */ | |
699 | memcpy(&tmp, d, sizeof(tmp)); | |
700 | v64 = be64toh(tmp); | |
5a602182 JK |
701 | d = &v64; |
702 | break; | |
703 | } | |
8e0608d3 | 704 | case MNL_TYPE_U32: { |
24b35d0b JK |
705 | v32 = ntohl(*(const uint32_t *)d); |
706 | d = &v32; | |
8e0608d3 JK |
707 | break; |
708 | } | |
709 | case MNL_TYPE_U16: { | |
24b35d0b JK |
710 | v16 = ntohs(*(const uint16_t *)d); |
711 | d = &v16; | |
8e0608d3 JK |
712 | break; |
713 | } | |
714 | default: | |
715 | break; | |
716 | } | |
4c16de1a SB |
717 | } else if (attr->type == MNL_TYPE_NUL_STRING) { |
718 | if (!d || strlen(d) >= attr->len) | |
719 | FAILURE("Broken kernel message: " | |
720 | "string type attribute missing or too long!"); | |
8e0608d3 | 721 | } |
97a12ba3 | 722 | #ifdef IPSET_DEBUG |
24b35d0b JK |
723 | else |
724 | D("hostorder attr type %u", type); | |
3f83fda4 | 725 | if (type == IPSET_ATTR_TYPENAME) |
8dd10256 | 726 | D("nla typename %s", (const char *) d); |
97a12ba3 | 727 | #endif |
3fd6b24a | 728 | ret = ipset_data_set(data, attr->opt, d); |
97a12ba3 | 729 | #ifdef IPSET_DEBUG |
3f83fda4 | 730 | if (type == IPSET_ATTR_TYPENAME) |
3fd6b24a | 731 | D("nla typename %s", |
8dd10256 | 732 | (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); |
3f83fda4 | 733 | #endif |
3fd6b24a | 734 | return ret; |
8e0608d3 JK |
735 | } |
736 | ||
737 | #define ATTR2DATA(session, nla, type, attrs) \ | |
738 | if (attr2data(session, nla, type, attrs) < 0) \ | |
739 | return MNL_CB_ERROR | |
740 | ||
741 | static const char cmd2name[][9] = { | |
742 | [IPSET_CMD_NONE] = "NONE", | |
743 | [IPSET_CMD_CREATE] = "CREATE", | |
744 | [IPSET_CMD_DESTROY] = "DESTROY", | |
745 | [IPSET_CMD_FLUSH] = "FLUSH", | |
746 | [IPSET_CMD_RENAME] = "RENAME", | |
747 | [IPSET_CMD_SWAP] = "SWAP", | |
748 | [IPSET_CMD_LIST] = "LIST", | |
749 | [IPSET_CMD_SAVE] = "SAVE", | |
750 | [IPSET_CMD_ADD] = "ADD", | |
751 | [IPSET_CMD_DEL] = "DEL", | |
752 | [IPSET_CMD_TEST] = "TEST", | |
753 | [IPSET_CMD_HEADER] = "HEADER", | |
754 | [IPSET_CMD_TYPE] = "TYPE", | |
755 | [IPSET_CMD_PROTOCOL] = "PROTOCOL", | |
756 | }; | |
757 | ||
758 | static inline int | |
759 | call_outfn(struct ipset_session *session) | |
760 | { | |
55fdd96e JK |
761 | int ret = session->print_outfn(session, session->p, |
762 | "%s", session->outbuf); | |
3f83fda4 | 763 | |
8e0608d3 | 764 | session->outbuf[0] = '\0'; |
3713072d | 765 | session->pos = 0; |
3f83fda4 | 766 | |
8e0608d3 JK |
767 | return ret < 0 ? ret : 0; |
768 | } | |
769 | ||
ad4d8027 JK |
770 | /* Handle printing failures */ |
771 | static jmp_buf printf_failure; | |
772 | ||
3713072d JK |
773 | static void |
774 | realloc_outbuf(struct ipset_session *session) | |
775 | { | |
776 | char *buf = realloc(session->outbuf, | |
777 | session->outbuflen + IPSET_OUTBUFLEN); | |
778 | if (!buf) { | |
779 | ipset_err(session, | |
780 | "Could not allocate memory to print sorted!"); | |
781 | longjmp(printf_failure, 1); | |
782 | } | |
783 | session->outbuf = buf; | |
784 | session->outbuflen += IPSET_OUTBUFLEN; | |
785 | } | |
786 | ||
a875d3fb JK |
787 | static int |
788 | handle_snprintf_error(struct ipset_session *session, | |
3713072d | 789 | int ret, int loop) |
8e0608d3 | 790 | { |
3713072d JK |
791 | if (ret < 0 || ret + session->pos >= session->outbuflen) { |
792 | if (session->sort && !loop) { | |
793 | realloc_outbuf(session); | |
794 | return 1; | |
795 | } | |
8e0608d3 | 796 | /* Buffer was too small, push it out and retry */ |
3713072d JK |
797 | D("print buffer and try again: outbuflen: %lu, pos %lu, ret: %d", |
798 | session->outbuflen, session->pos, ret); | |
a875d3fb | 799 | if (loop) { |
ad4d8027 | 800 | ipset_err(session, |
8e0608d3 | 801 | "Internal error at printing, loop detected!"); |
ad4d8027 JK |
802 | longjmp(printf_failure, 1); |
803 | } | |
8e0608d3 | 804 | |
3713072d | 805 | session->outbuf[session->pos] = '\0'; |
a875d3fb JK |
806 | if (call_outfn(session)) { |
807 | ipset_err(session, | |
808 | "Internal error, could not print output buffer!"); | |
809 | longjmp(printf_failure, 1); | |
810 | } | |
811 | return 1; | |
8e0608d3 | 812 | } |
3713072d | 813 | session->pos += ret; |
a875d3fb JK |
814 | return 0; |
815 | } | |
816 | ||
817 | static int __attribute__((format(printf, 2, 3))) | |
818 | safe_snprintf(struct ipset_session *session, const char *fmt, ...) | |
819 | { | |
820 | va_list args; | |
3713072d | 821 | int ret, loop = 0; |
a875d3fb JK |
822 | |
823 | do { | |
3713072d JK |
824 | D("outbuflen: %lu, pos: %lu, retry %u", |
825 | session->outbuflen, session->pos, loop); | |
a875d3fb | 826 | va_start(args, fmt); |
3713072d JK |
827 | ret = vsnprintf(session->outbuf + session->pos, |
828 | session->outbuflen - session->pos, | |
a875d3fb JK |
829 | fmt, args); |
830 | va_end(args); | |
3713072d | 831 | loop = handle_snprintf_error(session, ret, loop); |
a875d3fb JK |
832 | } while (loop); |
833 | ||
8e0608d3 JK |
834 | return ret; |
835 | } | |
836 | ||
837 | static int | |
838 | safe_dprintf(struct ipset_session *session, ipset_printfn fn, | |
839 | enum ipset_opt opt) | |
840 | { | |
3713072d | 841 | int ret, loop = 0; |
8e0608d3 | 842 | |
a875d3fb | 843 | do { |
3713072d JK |
844 | D("outbuflen: %lu, len: %lu, retry %u", |
845 | session->outbuflen, session->pos, loop); | |
846 | ret = fn(session->outbuf + session->pos, | |
847 | session->outbuflen - session->pos, | |
a875d3fb | 848 | session->data, opt, session->envopts); |
3713072d | 849 | loop = handle_snprintf_error(session, ret, loop); |
a875d3fb | 850 | } while (loop); |
8e0608d3 | 851 | |
8e0608d3 JK |
852 | return ret; |
853 | } | |
3f83fda4 | 854 | |
8e0608d3 JK |
855 | static int |
856 | list_adt(struct ipset_session *session, struct nlattr *nla[]) | |
857 | { | |
404c4638 | 858 | const struct ipset_data *data = session->data; |
8e0608d3 JK |
859 | const struct ipset_type *type; |
860 | const struct ipset_arg *arg; | |
3713072d | 861 | size_t offset = 0; |
3fd6b24a | 862 | int i, found = 0; |
2885607f | 863 | static char last_setname[IPSET_MAXNAMELEN] = ""; |
3fd6b24a | 864 | |
3f83fda4 | 865 | D("enter"); |
8e0608d3 JK |
866 | /* Check and load type, family */ |
867 | if (!ipset_data_test(data, IPSET_OPT_TYPE)) | |
868 | type = ipset_type_get(session, IPSET_CMD_ADD); | |
869 | else | |
870 | type = ipset_data_get(data, IPSET_OPT_TYPE); | |
871 | ||
872 | if (type == NULL) | |
873 | return MNL_CB_ERROR; | |
8e0608d3 | 874 | |
3fd6b24a JK |
875 | for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) |
876 | if (nla[i]) { | |
877 | found++; | |
878 | ATTR2DATA(session, nla, i, adt_attrs); | |
879 | } | |
880 | D("attr found %u", found); | |
881 | if (!found) | |
882 | return MNL_CB_OK; | |
883 | ||
3713072d JK |
884 | if (session->sort) { |
885 | if (session->outbuflen <= session->pos + 1) | |
886 | realloc_outbuf(session); | |
887 | session->pos++; /* \0 */ | |
888 | offset = session->pos; | |
889 | } | |
890 | ||
8e0608d3 JK |
891 | switch (session->mode) { |
892 | case IPSET_LIST_SAVE: | |
893 | safe_snprintf(session, "add %s ", ipset_data_setname(data)); | |
894 | break; | |
895 | case IPSET_LIST_XML: | |
f535c61d | 896 | safe_snprintf(session, "<member><elem>"); |
8e0608d3 | 897 | break; |
2885607f TO |
898 | case IPSET_LIST_JSON: |
899 | /* print separator if a member for this set was printed before */ | |
900 | if (STREQ(ipset_data_setname(data), last_setname)) | |
901 | safe_snprintf(session, ","); | |
902 | strcpy(last_setname, ipset_data_setname(data)); | |
903 | safe_snprintf(session, "\n {\n \"elem\" : \""); | |
904 | break; | |
8e0608d3 JK |
905 | case IPSET_LIST_PLAIN: |
906 | default: | |
907 | break; | |
908 | } | |
3f83fda4 | 909 | |
8e0608d3 | 910 | safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); |
f535c61d JK |
911 | if (session->mode == IPSET_LIST_XML) |
912 | safe_snprintf(session, "</elem>"); | |
2885607f TO |
913 | if (session->mode == IPSET_LIST_JSON) |
914 | safe_snprintf(session, "\""); | |
8e0608d3 | 915 | |
d71dd935 JK |
916 | for (i = 0; type->cmd[IPSET_ADD].args[i] != IPSET_ARG_NONE; i++) { |
917 | arg = ipset_keyword(type->cmd[IPSET_ADD].args[i]); | |
918 | D("print arg opt %u (%s) %s", arg->opt, arg->name[0], | |
5a602182 JK |
919 | ipset_data_test(data, arg->opt) ? "(yes)" : "(missing)"); |
920 | if (!(arg->print && ipset_data_test(data, arg->opt))) | |
8e0608d3 JK |
921 | continue; |
922 | switch (session->mode) { | |
923 | case IPSET_LIST_SAVE: | |
924 | case IPSET_LIST_PLAIN: | |
f535c61d JK |
925 | if (arg->has_arg == IPSET_NO_ARG) { |
926 | safe_snprintf(session, " %s", arg->name[0]); | |
8e0608d3 | 927 | break; |
f535c61d JK |
928 | } |
929 | safe_snprintf(session, " %s ", arg->name[0]); | |
8e0608d3 JK |
930 | safe_dprintf(session, arg->print, arg->opt); |
931 | break; | |
932 | case IPSET_LIST_XML: | |
933 | if (arg->has_arg == IPSET_NO_ARG) { | |
934 | safe_snprintf(session, | |
f535c61d | 935 | "<%s/>", arg->name[0]); |
8e0608d3 JK |
936 | break; |
937 | } | |
f535c61d | 938 | safe_snprintf(session, "<%s>", arg->name[0]); |
8e0608d3 | 939 | safe_dprintf(session, arg->print, arg->opt); |
f535c61d | 940 | safe_snprintf(session, "</%s>", arg->name[0]); |
8e0608d3 | 941 | break; |
2885607f TO |
942 | case IPSET_LIST_JSON: |
943 | if (arg->has_arg == IPSET_NO_ARG) { | |
944 | safe_snprintf(session, | |
945 | ",\n \"%s\" : true", arg->name[0]); | |
946 | break; | |
947 | } | |
948 | safe_snprintf(session, ",\n \"%s\" : ", arg->name[0]); | |
949 | safe_dprintf(session, arg->print, arg->opt); | |
950 | break; | |
8e0608d3 JK |
951 | default: |
952 | break; | |
953 | } | |
954 | } | |
3f83fda4 | 955 | |
8e0608d3 JK |
956 | if (session->mode == IPSET_LIST_XML) |
957 | safe_snprintf(session, "</member>\n"); | |
2885607f TO |
958 | else if (session->mode == IPSET_LIST_JSON) |
959 | safe_snprintf(session, "\n }"); | |
8e0608d3 JK |
960 | else |
961 | safe_snprintf(session, "\n"); | |
962 | ||
3713072d JK |
963 | if (session->sort) { |
964 | struct ipset_sorted *sorted; | |
965 | ||
966 | if (!list_empty(&session->pool)) { | |
967 | sorted = list_first_entry(&session->pool, | |
968 | struct ipset_sorted, list); | |
969 | list_del(&sorted->list); | |
970 | } else { | |
971 | sorted = calloc(1, sizeof(struct ipset_sorted)); | |
972 | if (!sorted) { | |
973 | ipset_err(session, | |
974 | "Could not allocate memory to print sorted!"); | |
975 | longjmp(printf_failure, 1); | |
976 | } | |
977 | } | |
978 | sorted->offset = offset; | |
979 | list_add_tail(&sorted->list, &session->sorted); | |
980 | } | |
8e0608d3 JK |
981 | return MNL_CB_OK; |
982 | } | |
983 | ||
3fd6b24a | 984 | #define FAMILY_TO_STR(f) \ |
541e3286 JE |
985 | ((f) == NFPROTO_IPV4 ? "inet" : \ |
986 | (f) == NFPROTO_IPV6 ? "inet6" : "any") | |
3fd6b24a | 987 | |
8e0608d3 JK |
988 | static int |
989 | list_create(struct ipset_session *session, struct nlattr *nla[]) | |
990 | { | |
404c4638 | 991 | const struct ipset_data *data = session->data; |
8e0608d3 JK |
992 | const struct ipset_type *type; |
993 | const struct ipset_arg *arg; | |
994 | uint8_t family; | |
995 | int i; | |
2885607f | 996 | static bool firstipset = true; |
8e0608d3 JK |
997 | |
998 | for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) | |
999 | if (nla[i]) { | |
1000 | D("add attr %u, opt %u", i, create_attrs[i].opt); | |
1001 | ATTR2DATA(session, nla, i, create_attrs); | |
1002 | } | |
1003 | ||
1004 | type = ipset_type_check(session); | |
1005 | if (type == NULL) | |
1006 | return MNL_CB_ERROR; | |
1007 | family = ipset_data_family(data); | |
1008 | ||
0b08f9f1 | 1009 | session->save_elem_prefix = strlen(ipset_data_setname(data)) + 5; |
8e0608d3 JK |
1010 | switch (session->mode) { |
1011 | case IPSET_LIST_SAVE: | |
f535c61d | 1012 | safe_snprintf(session, "create %s %s", |
8e0608d3 JK |
1013 | ipset_data_setname(data), |
1014 | type->name); | |
8e0608d3 JK |
1015 | break; |
1016 | case IPSET_LIST_PLAIN: | |
31ee222f | 1017 | safe_snprintf(session, "%sName: %s\n" |
f535c61d | 1018 | "Type: %s\nRevision: %u\nHeader:", |
31ee222f | 1019 | session->printed_set ? "\n" : "", |
8e0608d3 | 1020 | ipset_data_setname(data), |
d95c2f1e | 1021 | type->name, type->revision); |
8e0608d3 JK |
1022 | break; |
1023 | case IPSET_LIST_XML: | |
1024 | safe_snprintf(session, | |
1025 | "<ipset name=\"%s\">\n" | |
f535c61d JK |
1026 | "<type>%s</type>\n" |
1027 | "<revision>%u</revision>\n" | |
1028 | "<header>", | |
8e0608d3 | 1029 | ipset_data_setname(data), |
d95c2f1e | 1030 | type->name, type->revision); |
8e0608d3 | 1031 | break; |
2885607f TO |
1032 | case IPSET_LIST_JSON: |
1033 | if (!firstipset) | |
1034 | safe_snprintf(session, ",\n"); | |
1035 | firstipset = false; | |
1036 | safe_snprintf(session, | |
1037 | " \{\n" | |
1038 | " \"name\" : \"%s\",\n" | |
1039 | " \"type\" : \"%s\",\n" | |
1040 | " \"revision\" : %u,\n" | |
1041 | " \"header\" : \{\n", | |
1042 | ipset_data_setname(data), | |
1043 | type->name, type->revision); | |
1044 | break; | |
8e0608d3 JK |
1045 | default: |
1046 | break; | |
1047 | } | |
1048 | ||
d71dd935 JK |
1049 | D("type %s, rev %u", type->name, type->revision); |
1050 | for (i = 0; type->cmd[IPSET_CREATE].args[i] != IPSET_ARG_NONE; i++) { | |
1051 | arg = ipset_keyword(type->cmd[IPSET_CREATE].args[i]); | |
1052 | D("create print arg opt %u (%s) %s", arg->opt, | |
1053 | arg->name[0] ? arg->name[0] : "", | |
1054 | ipset_data_test(data, arg->opt) ? "(yes)" : "(missing)"); | |
3f83fda4 JK |
1055 | if (!arg->print || |
1056 | !ipset_data_test(data, arg->opt) || | |
1057 | (arg->opt == IPSET_OPT_FAMILY && | |
1058 | family == type->family)) | |
8e0608d3 JK |
1059 | continue; |
1060 | switch (session->mode) { | |
1061 | case IPSET_LIST_SAVE: | |
1062 | case IPSET_LIST_PLAIN: | |
f535c61d JK |
1063 | if (arg->has_arg == IPSET_NO_ARG) { |
1064 | safe_snprintf(session, " %s", arg->name[0]); | |
8e0608d3 | 1065 | break; |
f535c61d JK |
1066 | } |
1067 | safe_snprintf(session, " %s ", arg->name[0]); | |
8e0608d3 | 1068 | safe_dprintf(session, arg->print, arg->opt); |
8e0608d3 JK |
1069 | break; |
1070 | case IPSET_LIST_XML: | |
1071 | if (arg->has_arg == IPSET_NO_ARG) { | |
1072 | safe_snprintf(session, | |
f535c61d | 1073 | "<%s/>", arg->name[0]); |
8e0608d3 JK |
1074 | break; |
1075 | } | |
f535c61d | 1076 | safe_snprintf(session, "<%s>", arg->name[0]); |
8e0608d3 | 1077 | safe_dprintf(session, arg->print, arg->opt); |
f535c61d | 1078 | safe_snprintf(session, "</%s>", arg->name[0]); |
8e0608d3 | 1079 | break; |
2885607f TO |
1080 | case IPSET_LIST_JSON: |
1081 | if (arg->has_arg == IPSET_NO_ARG) { | |
1082 | safe_snprintf(session, | |
1083 | " \"%s\" : true,\n", arg->name[0]); | |
1084 | break; | |
1085 | } | |
1086 | if (arg->opt == IPSET_OPT_FAMILY) { | |
1087 | safe_snprintf(session, " \"%s\" : \"", arg->name[0]); | |
1088 | safe_dprintf(session, arg->print, arg->opt); | |
1089 | safe_snprintf(session, "\",\n", arg->name[0]); | |
1090 | break; | |
1091 | } | |
1092 | safe_snprintf(session, " \"%s\" : ", arg->name[0]); | |
1093 | safe_dprintf(session, arg->print, arg->opt); | |
1094 | safe_snprintf(session, ",\n", arg->name[0]); | |
1095 | break; | |
8e0608d3 JK |
1096 | default: |
1097 | break; | |
1098 | } | |
1099 | } | |
1100 | switch (session->mode) { | |
1101 | case IPSET_LIST_SAVE: | |
1102 | safe_snprintf(session, "\n"); | |
1103 | break; | |
1104 | case IPSET_LIST_PLAIN: | |
8e0608d3 JK |
1105 | safe_snprintf(session, "\nSize in memory: "); |
1106 | safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); | |
1107 | safe_snprintf(session, "\nReferences: "); | |
1108 | safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); | |
0c7e18ed | 1109 | if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { |
eb9af8fe EM |
1110 | safe_snprintf(session, "\nNumber of entries: "); |
1111 | safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); | |
1112 | } | |
bf8463fb JK |
1113 | safe_snprintf(session, |
1114 | session->envopts & IPSET_ENV_LIST_HEADER ? | |
1115 | "\n" : "\nMembers:\n"); | |
8e0608d3 JK |
1116 | break; |
1117 | case IPSET_LIST_XML: | |
f535c61d | 1118 | safe_snprintf(session, "\n<memsize>"); |
8e0608d3 | 1119 | safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); |
f535c61d | 1120 | safe_snprintf(session, "</memsize>\n<references>"); |
8e0608d3 | 1121 | safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); |
eb9af8fe | 1122 | safe_snprintf(session, "</references>\n"); |
0c7e18ed | 1123 | if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { |
eb9af8fe EM |
1124 | safe_snprintf(session, "<numentries>"); |
1125 | safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); | |
1126 | safe_snprintf(session, "</numentries>\n"); | |
1127 | } | |
31ee222f JK |
1128 | safe_snprintf(session, |
1129 | session->envopts & IPSET_ENV_LIST_HEADER ? | |
eb9af8fe EM |
1130 | "</header>\n" : |
1131 | "</header>\n<members>\n"); | |
8e0608d3 | 1132 | break; |
2885607f TO |
1133 | case IPSET_LIST_JSON: |
1134 | safe_snprintf(session, " \"memsize\" : "); | |
1135 | safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); | |
1136 | safe_snprintf(session, ",\n \"references\" : "); | |
1137 | safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); | |
1138 | if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { | |
1139 | safe_snprintf(session, ",\n \"numentries\" : "); | |
1140 | safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); | |
1141 | } | |
1142 | safe_snprintf(session, "\n"); | |
1143 | safe_snprintf(session, | |
1144 | session->envopts & IPSET_ENV_LIST_HEADER ? | |
1145 | " },\n" : | |
1146 | " },\n \"members\" : ["); | |
1147 | break; | |
8e0608d3 JK |
1148 | default: |
1149 | break; | |
1150 | } | |
31ee222f | 1151 | session->printed_set++; |
8e0608d3 | 1152 | |
3713072d JK |
1153 | session->sort = strncmp(type->name, "hash:", 5) == 0 && |
1154 | ipset_envopt_test(session, IPSET_ENV_SORTED); | |
1155 | ||
8e0608d3 JK |
1156 | return MNL_CB_OK; |
1157 | } | |
1158 | ||
0b08f9f1 JK |
1159 | /* "<member><elem>" */ |
1160 | #define XML_ELEM_PREFIX_LEN 14 | |
1161 | ||
1162 | /* Core should handle sorting more directly */ | |
3713072d JK |
1163 | static int |
1164 | bystrcmp(void *priv, struct list_head *a, struct list_head *b) | |
1165 | { | |
1166 | struct ipset_session *session = priv; | |
1167 | struct ipset_sorted *x = list_entry(a, struct ipset_sorted, list); | |
1168 | struct ipset_sorted *y = list_entry(b, struct ipset_sorted, list); | |
0b08f9f1 JK |
1169 | char *sep1, *x1 = session->outbuf + x->offset; |
1170 | char *sep2, *x2 = session->outbuf + y->offset; | |
1171 | long int n1, n2; | |
1172 | int neg = 1; | |
1173 | ||
1174 | if (session->envopts & IPSET_ENV_RESOLVE) | |
1175 | return strcmp(x1, x2); | |
1176 | ||
1177 | /* Skip prefix */ | |
1178 | switch (session->mode) { | |
1179 | case IPSET_LIST_SAVE: | |
1180 | /* "add setname " */ | |
1181 | x1 += session->save_elem_prefix; | |
1182 | x2 += session->save_elem_prefix; | |
1183 | break; | |
1184 | case IPSET_LIST_XML: | |
1185 | /* "<member><elem>" */ | |
1186 | x1 += XML_ELEM_PREFIX_LEN; | |
1187 | x2 += XML_ELEM_PREFIX_LEN; | |
1188 | break; | |
1189 | default: | |
1190 | break; | |
1191 | } | |
3713072d | 1192 | |
0b08f9f1 JK |
1193 | while (*x1 != '\0' && *x2 != '\0') { |
1194 | n1 = strtol(x1, &sep1, 16); | |
1195 | n2 = strtol(x2, &sep2, 16); | |
1196 | if (x1 == sep1 || x2 == sep2) { | |
1197 | /* No leading numbers: proto:port, iface, setname */ | |
1198 | n1 = strcspn(x1, ":,"); | |
1199 | n2 = strcspn(x2, ":,"); | |
1200 | if (n1 != 0 || n2 != 0) { | |
1201 | if (n1 == n2) { | |
1202 | n2 = strncmp(x1, x2, n1); | |
1203 | if (n2 == 0) { | |
1204 | x1 += n1 + 1; | |
1205 | x2 += n1 + 1; | |
1206 | neg = 1; | |
1207 | continue; | |
1208 | } | |
1209 | return n2; | |
1210 | } | |
1211 | return n1 < n2 ? -1 : 1; | |
1212 | } | |
1213 | /* Setname or iface */ | |
1214 | return strcmp(x1, x2); | |
1215 | } | |
1216 | /* Leading numbers found */ | |
1217 | if (n1 != n2) | |
1218 | return neg * (n1 - n2); | |
1219 | /* Check subnet separator */ | |
1220 | if (*sep1 == '/' || *sep2 == '/') { | |
1221 | if (*sep1 == *sep2) | |
1222 | neg = *sep1 == '/' ? -1 : 1; | |
1223 | else if (*sep1 == '/') | |
1224 | return 1; | |
1225 | else | |
1226 | return -1; | |
1227 | } | |
1228 | x1 = ++sep1; | |
1229 | x2 = ++sep2; | |
1230 | /* Handle IPv6 '::' case */ | |
1231 | if (*x1 == ':' || *x2 == ':') { | |
1232 | if (*x1 == *x2) { | |
1233 | x1++; | |
1234 | x2++; | |
1235 | } else if (*x1 == ':') | |
1236 | return -1; | |
1237 | else | |
1238 | return 1; | |
1239 | } | |
1240 | } | |
1241 | return *x1 != '\0' ? 1 : (*x2 != '\0' ? -1 : 0); | |
3713072d JK |
1242 | } |
1243 | ||
8e0608d3 | 1244 | static int |
f535c61d | 1245 | print_set_done(struct ipset_session *session, bool callback_done) |
8e0608d3 | 1246 | { |
3fd6b24a JK |
1247 | D("called for %s", session->saved_setname[0] == '\0' |
1248 | ? "NONE" : session->saved_setname); | |
3713072d JK |
1249 | if (session->sort) { |
1250 | struct ipset_sorted *pos; | |
1251 | int ret; | |
1252 | ||
1253 | /* Print set header */ | |
1254 | ret = call_outfn(session); | |
1255 | ||
1256 | if (ret) | |
1257 | return MNL_CB_ERROR; | |
1258 | ||
1259 | list_sort(session, &session->sorted, bystrcmp); | |
1260 | ||
1261 | list_for_each_entry(pos, &session->sorted, list) { | |
1262 | ret = session->print_outfn(session, session->p, | |
1263 | "%s", | |
1264 | session->outbuf + pos->offset); | |
1265 | if (ret < 0) | |
1266 | return MNL_CB_ERROR; | |
1267 | } | |
1268 | list_splice(&session->sorted, &session->pool); | |
1269 | INIT_LIST_HEAD(&session->sorted); | |
1270 | } | |
8e0608d3 JK |
1271 | switch (session->mode) { |
1272 | case IPSET_LIST_XML: | |
31ee222f JK |
1273 | if (session->envopts & IPSET_ENV_LIST_SETNAME) |
1274 | break; | |
1275 | if (session->envopts & IPSET_ENV_LIST_HEADER) { | |
1276 | if (session->saved_setname[0] != '\0') | |
1277 | safe_snprintf(session, "</ipset>\n"); | |
1278 | break; | |
1279 | } | |
1280 | if (session->saved_setname[0] != '\0') | |
f535c61d | 1281 | safe_snprintf(session, "</members>\n</ipset>\n"); |
8e0608d3 | 1282 | break; |
2885607f TO |
1283 | case IPSET_LIST_JSON: |
1284 | if (session->envopts & IPSET_ENV_LIST_SETNAME) | |
1285 | break; | |
1286 | if (session->envopts & IPSET_ENV_LIST_HEADER) { | |
1287 | if (session->saved_setname[0] != '\0') | |
1288 | safe_snprintf(session, " }"); | |
1289 | break; | |
1290 | } | |
1291 | if (session->saved_setname[0] != '\0') | |
1292 | safe_snprintf(session, "\n ]\n }"); | |
1293 | break; | |
8e0608d3 | 1294 | default: |
8e0608d3 JK |
1295 | break; |
1296 | } | |
f535c61d JK |
1297 | if (callback_done && session->mode == IPSET_LIST_XML) |
1298 | safe_snprintf(session, "</ipsets>\n"); | |
2885607f TO |
1299 | if (callback_done && session->mode == IPSET_LIST_JSON) |
1300 | safe_snprintf(session, "\n]\n"); | |
8e0608d3 JK |
1301 | return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_STOP; |
1302 | } | |
1303 | ||
8e0608d3 JK |
1304 | static int |
1305 | callback_list(struct ipset_session *session, struct nlattr *nla[], | |
1306 | enum ipset_cmd cmd) | |
1307 | { | |
1308 | struct ipset_data *data = session->data; | |
8b3824f9 | 1309 | static bool firstipset = true; |
8e0608d3 | 1310 | |
31ee222f JK |
1311 | if (setjmp(printf_failure)) { |
1312 | session->saved_setname[0] = '\0'; | |
1313 | session->printed_set = 0; | |
ad4d8027 | 1314 | return MNL_CB_ERROR; |
31ee222f | 1315 | } |
ad4d8027 | 1316 | |
8e0608d3 JK |
1317 | if (!nla[IPSET_ATTR_SETNAME]) |
1318 | FAILURE("Broken %s kernel message: missing setname!", | |
1319 | cmd2name[cmd]); | |
1320 | ||
1321 | ATTR2DATA(session, nla, IPSET_ATTR_SETNAME, cmd_attrs); | |
3fd6b24a | 1322 | D("setname %s", ipset_data_setname(data)); |
89644719 JK |
1323 | if (session->envopts & IPSET_ENV_LIST_SETNAME && |
1324 | session->mode != IPSET_LIST_SAVE) { | |
bf8463fb JK |
1325 | if (session->mode == IPSET_LIST_XML) |
1326 | safe_snprintf(session, "<ipset name=\"%s\"/>\n", | |
1327 | ipset_data_setname(data)); | |
8b3824f9 M |
1328 | else if (session->mode == IPSET_LIST_JSON) { |
1329 | if (!firstipset) | |
1330 | safe_snprintf(session, ",\n"); | |
1331 | firstipset = false; | |
1332 | safe_snprintf(session, " { \"name\" : \"%s\" }", | |
2885607f | 1333 | ipset_data_setname(data)); |
8b3824f9 | 1334 | } else |
bf8463fb JK |
1335 | safe_snprintf(session, "%s\n", |
1336 | ipset_data_setname(data)); | |
1337 | return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; | |
1338 | } | |
1339 | ||
8e0608d3 JK |
1340 | if (STREQ(ipset_data_setname(data), session->saved_setname)) { |
1341 | /* Header part already seen */ | |
3f83fda4 JK |
1342 | if (ipset_data_test(data, IPSET_OPT_TYPE) && |
1343 | nla[IPSET_ATTR_DATA] != NULL) | |
8e0608d3 JK |
1344 | FAILURE("Broken %s kernel message: " |
1345 | "extra DATA received!", cmd2name[cmd]); | |
1346 | } else { | |
1347 | if (nla[IPSET_ATTR_DATA] == NULL) | |
1348 | FAILURE("Broken %s kernel message: " | |
1349 | "missing DATA part!", cmd2name[cmd]); | |
3f83fda4 | 1350 | |
8e0608d3 JK |
1351 | /* Close previous set printing */ |
1352 | if (session->saved_setname[0] != '\0') | |
f535c61d | 1353 | print_set_done(session, false); |
8e0608d3 JK |
1354 | } |
1355 | ||
1356 | if (nla[IPSET_ATTR_DATA] != NULL) { | |
1357 | struct nlattr *cattr[IPSET_ATTR_CREATE_MAX+1] = {}; | |
1358 | ||
3f83fda4 JK |
1359 | if (!(nla[IPSET_ATTR_TYPENAME] && |
1360 | nla[IPSET_ATTR_FAMILY] && | |
1361 | nla[IPSET_ATTR_REVISION])) | |
8e0608d3 JK |
1362 | FAILURE("Broken %s kernel message: missing %s!", |
1363 | cmd2name[cmd], | |
3f83fda4 JK |
1364 | !nla[IPSET_ATTR_TYPENAME] ? "typename" : |
1365 | !nla[IPSET_ATTR_FAMILY] ? "family" : | |
1366 | "revision"); | |
8e0608d3 | 1367 | |
3fd6b24a JK |
1368 | /* Reset CREATE specific flags */ |
1369 | ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); | |
1370 | D("nla typename %s", | |
1371 | (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); | |
81242183 | 1372 | |
8e0608d3 JK |
1373 | ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); |
1374 | ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); | |
1375 | ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); | |
3fd6b24a JK |
1376 | D("head: family %u, typename %s", |
1377 | ipset_data_family(data), | |
8dd10256 | 1378 | (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); |
8e0608d3 JK |
1379 | if (mnl_attr_parse_nested(nla[IPSET_ATTR_DATA], |
1380 | create_attr_cb, cattr) < 0) | |
1381 | FAILURE("Broken %s kernel message: " | |
1382 | "cannot validate DATA attributes!", | |
1383 | cmd2name[cmd]); | |
1384 | if (list_create(session, cattr) != MNL_CB_OK) | |
1385 | return MNL_CB_ERROR; | |
1386 | strcpy(session->saved_setname, ipset_data_setname(data)); | |
1387 | } | |
3f83fda4 | 1388 | |
8e0608d3 JK |
1389 | if (nla[IPSET_ATTR_ADT] != NULL) { |
1390 | struct nlattr *tb, *adt[IPSET_ATTR_ADT_MAX+1]; | |
3fd6b24a | 1391 | |
8e0608d3 | 1392 | mnl_attr_for_each_nested(tb, nla[IPSET_ATTR_ADT]) { |
3fd6b24a | 1393 | D("ADT attributes for %s", ipset_data_setname(data)); |
8e0608d3 JK |
1394 | memset(adt, 0, sizeof(adt)); |
1395 | /* Reset ADT specific flags */ | |
1396 | ipset_data_flags_unset(data, IPSET_ADT_FLAGS); | |
1397 | if (mnl_attr_parse_nested(tb, adt_attr_cb, adt) < 0) | |
1398 | FAILURE("Broken %s kernel message: " | |
1399 | "cannot validate ADT attributes!", | |
1400 | cmd2name[cmd]); | |
1401 | if (list_adt(session, adt) != MNL_CB_OK) | |
1402 | return MNL_CB_ERROR; | |
1403 | } | |
3713072d JK |
1404 | if (session->sort) |
1405 | return MNL_CB_OK; | |
8e0608d3 JK |
1406 | } |
1407 | return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; | |
1408 | } | |
1409 | ||
1410 | #ifndef IPSET_PROTOCOL_MIN | |
1411 | #define IPSET_PROTOCOL_MIN IPSET_PROTOCOL | |
1412 | #endif | |
1413 | ||
1414 | #ifndef IPSET_PROTOCOL_MAX | |
1415 | #define IPSET_PROTOCOL_MAX IPSET_PROTOCOL | |
1416 | #endif | |
1417 | ||
1418 | static int | |
1419 | callback_version(struct ipset_session *session, struct nlattr *nla[]) | |
1420 | { | |
1421 | uint8_t min, max; | |
3f83fda4 | 1422 | |
8e0608d3 JK |
1423 | min = max = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); |
1424 | ||
1425 | if (nla[IPSET_ATTR_PROTOCOL_MIN]) { | |
1426 | min = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL_MIN]); | |
1427 | D("min: %u", min); | |
1428 | } | |
1429 | ||
1430 | if (min > IPSET_PROTOCOL_MAX || max < IPSET_PROTOCOL_MIN) | |
1431 | FAILURE("Cannot communicate with kernel: " | |
1432 | "Kernel support protocol versions %u-%u " | |
1433 | "while userspace supports protocol versions %u-%u", | |
1434 | min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); | |
1435 | ||
3f83fda4 JK |
1436 | if (!(session->envopts & IPSET_ENV_QUIET) && |
1437 | max != IPSET_PROTOCOL_MAX) | |
8e0608d3 JK |
1438 | ipset_warn(session, |
1439 | "Kernel support protocol versions %u-%u " | |
1440 | "while userspace supports protocol versions %u-%u", | |
1441 | min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); | |
1442 | ||
b934ebcd | 1443 | session->protocol = MIN(max, IPSET_PROTOCOL_MAX); |
8e0608d3 JK |
1444 | session->version_checked = true; |
1445 | ||
1446 | return MNL_CB_STOP; | |
1447 | } | |
1448 | ||
1449 | static int | |
1450 | callback_header(struct ipset_session *session, struct nlattr *nla[]) | |
1451 | { | |
1452 | const char *setname; | |
404c4638 | 1453 | const struct ipset_data *data = session->data; |
3f83fda4 | 1454 | |
8e0608d3 JK |
1455 | if (!nla[IPSET_ATTR_SETNAME]) |
1456 | FAILURE("Broken HEADER kernel message: missing setname!"); | |
1457 | ||
1458 | setname = mnl_attr_get_str(nla[IPSET_ATTR_SETNAME]); | |
1459 | if (!STREQ(setname, ipset_data_setname(data))) | |
1460 | FAILURE("Broken HEADER kernel message: sent setname `%s' " | |
1461 | "does not match with received one `%s'!", | |
1462 | ipset_data_setname(data), setname); | |
3f83fda4 JK |
1463 | |
1464 | if (!(nla[IPSET_ATTR_TYPENAME] && | |
1465 | nla[IPSET_ATTR_REVISION] && | |
1466 | nla[IPSET_ATTR_FAMILY])) | |
8e0608d3 JK |
1467 | FAILURE("Broken HEADER kernel message: " |
1468 | "missing attribute '%s'!", | |
1469 | !nla[IPSET_ATTR_TYPENAME] ? "typename" : | |
1470 | !nla[IPSET_ATTR_REVISION] ? "revision" : | |
1471 | "family"); | |
1472 | ||
1473 | ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); | |
1474 | ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); | |
1475 | ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); | |
3fd6b24a | 1476 | D("got family: %u", ipset_data_family(session->data)); |
8e0608d3 JK |
1477 | |
1478 | return MNL_CB_STOP; | |
1479 | } | |
1480 | ||
1481 | static int | |
1482 | callback_type(struct ipset_session *session, struct nlattr *nla[]) | |
1483 | { | |
404c4638 | 1484 | const struct ipset_data *data = session->data; |
8e0608d3 | 1485 | const char *typename, *orig; |
3f83fda4 JK |
1486 | |
1487 | if (!(nla[IPSET_ATTR_TYPENAME] && | |
1488 | nla[IPSET_ATTR_REVISION] && | |
1489 | nla[IPSET_ATTR_FAMILY])) | |
8e0608d3 JK |
1490 | FAILURE("Broken TYPE kernel message: " |
1491 | "missing attribute '%s'!", | |
1492 | !nla[IPSET_ATTR_TYPENAME] ? "typename" : | |
1493 | !nla[IPSET_ATTR_REVISION] ? "revision" : | |
1494 | "family"); | |
1495 | ||
1496 | typename = mnl_attr_get_str(nla[IPSET_ATTR_TYPENAME]); | |
1497 | orig = ipset_data_get(data, IPSET_OPT_TYPENAME); | |
1498 | if (!STREQ(typename, orig)) | |
1499 | FAILURE("Broken TYPE kernel message: sent typename `%s' " | |
1500 | "does not match with received one `%s'!", | |
1501 | orig, typename); | |
3f83fda4 | 1502 | |
8e0608d3 JK |
1503 | ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); |
1504 | ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); | |
1505 | ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); | |
1506 | if (nla[IPSET_ATTR_REVISION_MIN]) | |
1507 | ATTR2DATA(session, nla, IPSET_ATTR_REVISION_MIN, cmd_attrs); | |
1508 | ||
1509 | return MNL_CB_STOP; | |
1510 | } | |
1511 | ||
1512 | static int | |
1513 | cmd_attr_cb(const struct nlattr *attr, void *data) | |
1514 | { | |
1515 | return generic_data_attr_cb(attr, data, IPSET_ATTR_CMD_MAX, cmd_attrs); | |
1516 | } | |
1517 | ||
1518 | #if 0 | |
1519 | static int | |
1520 | mnl_attr_parse_dbg(const struct nlmsghdr *nlh, int offset, | |
1521 | mnl_attr_cb_t cb, void *data) | |
1522 | { | |
1523 | int ret = MNL_CB_OK; | |
1524 | struct nlattr *attr = mnl_nlmsg_get_payload_offset(nlh, offset); | |
1525 | int len = nlh->nlmsg_len - MNL_NLMSG_HDRLEN - MNL_ALIGN(offset); | |
3f83fda4 | 1526 | |
8e0608d3 JK |
1527 | while (mnl_attr_ok(attr, len)) { |
1528 | D("attr: type %u, attrlen %u, len %u", | |
1529 | mnl_attr_get_type(attr), attr->nla_len, len); | |
1530 | if (cb && (ret = cb(attr, data)) <= MNL_CB_STOP) | |
1531 | return ret; | |
1532 | attr = mnl_attr_next(attr, &len); | |
1533 | } | |
1534 | return ret; | |
1535 | } | |
1536 | #endif | |
1537 | ||
1538 | static int | |
1539 | callback_data(const struct nlmsghdr *nlh, void *data) | |
1540 | { | |
1541 | struct ipset_session *session = data; | |
1542 | struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; | |
1543 | uint8_t proto, cmd; | |
1544 | int ret = MNL_CB_OK, nfmsglen = MNL_ALIGN(sizeof(struct nfgenmsg)); | |
3f83fda4 | 1545 | |
8e0608d3 JK |
1546 | D("called, nlmsg_len %u", nlh->nlmsg_len); |
1547 | cmd = ipset_get_nlmsg_type(nlh); | |
1548 | if (cmd == IPSET_CMD_LIST && session->cmd == IPSET_CMD_SAVE) | |
1549 | /* Kernel always send IPSET_CMD_LIST */ | |
1550 | cmd = IPSET_CMD_SAVE; | |
1551 | ||
1552 | if (cmd != session->cmd) | |
1553 | FAILURE("Protocol error, we sent command %s " | |
1554 | "and received %s[%u]", | |
1555 | cmd2name[session->cmd], | |
1556 | cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); | |
1557 | ||
1558 | if (mnl_attr_parse(nlh, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) | |
1559 | FAILURE("Broken %s kernel message: " | |
1560 | "cannot validate and parse attributes", | |
1561 | cmd2name[cmd]); | |
1562 | ||
1563 | if (!nla[IPSET_ATTR_PROTOCOL]) | |
1564 | FAILURE("Sad, sad day: kernel message %s " | |
3f83fda4 JK |
1565 | "does not carry the protocol version.", |
1566 | cmd2name[cmd]); | |
8e0608d3 JK |
1567 | |
1568 | proto = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); | |
1569 | ||
1570 | /* Check protocol */ | |
b934ebcd | 1571 | if (cmd != IPSET_CMD_PROTOCOL && proto != session->protocol) |
8e0608d3 JK |
1572 | FAILURE("Giving up: kernel protocol version %u " |
1573 | "does not match our protocol version %u", | |
b934ebcd | 1574 | proto, session->protocol); |
8e0608d3 JK |
1575 | |
1576 | D("Message: %s", cmd2name[cmd]); | |
1577 | switch (cmd) { | |
1578 | case IPSET_CMD_LIST: | |
1579 | case IPSET_CMD_SAVE: | |
1580 | ret = callback_list(session, nla, cmd); | |
1581 | D("flag multi: %u", nlh->nlmsg_flags & NLM_F_MULTI); | |
1582 | if (ret >= MNL_CB_STOP && !(nlh->nlmsg_flags & NLM_F_MULTI)) | |
f535c61d | 1583 | ret = print_set_done(session, false); |
8e0608d3 JK |
1584 | break; |
1585 | case IPSET_CMD_PROTOCOL: | |
1586 | if (!session->version_checked) | |
1587 | ret = callback_version(session, nla); | |
1588 | break; | |
1589 | case IPSET_CMD_HEADER: | |
1590 | ret = callback_header(session, nla); | |
1591 | break; | |
1592 | case IPSET_CMD_TYPE: | |
1593 | ret = callback_type(session, nla); | |
1594 | break; | |
1595 | default: | |
1596 | FAILURE("Data message received when not expected at %s", | |
1597 | cmd2name[session->cmd]); | |
1598 | } | |
1599 | D("return code: %s", ret == MNL_CB_STOP ? "stop" : | |
1600 | ret == MNL_CB_OK ? "ok" : "error"); | |
1601 | return ret; | |
1602 | } | |
1603 | ||
1604 | static int | |
1605 | callback_done(const struct nlmsghdr *nlh UNUSED, void *data) | |
1606 | { | |
1607 | struct ipset_session *session = data; | |
1608 | ||
1609 | D(" called"); | |
1610 | if (session->cmd == IPSET_CMD_LIST || session->cmd == IPSET_CMD_SAVE) | |
f535c61d | 1611 | return print_set_done(session, true); |
3f83fda4 | 1612 | |
8e0608d3 JK |
1613 | FAILURE("Invalid message received in non LIST or SAVE state."); |
1614 | } | |
1615 | ||
1616 | static int | |
1617 | decode_errmsg(struct ipset_session *session, const struct nlmsghdr *nlh) | |
1618 | { | |
1619 | const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); | |
1620 | const struct nlmsghdr *msg = &err->msg; | |
1621 | struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; | |
1622 | enum ipset_cmd cmd; | |
1623 | int nfmsglen = MNL_ALIGN(sizeof(struct nfgenmsg)); | |
3f83fda4 JK |
1624 | |
1625 | if (nlh->nlmsg_len < (uint32_t) MNL_ALIGN(sizeof(struct nlmsgerr)) || | |
1626 | nlh->nlmsg_len < MNL_ALIGN(sizeof(struct nlmsgerr)) | |
1627 | + msg->nlmsg_len) | |
8e0608d3 JK |
1628 | FAILURE("Broken error report message received."); |
1629 | ||
1630 | cmd = ipset_get_nlmsg_type(msg); | |
1631 | D("nlsmg_len: %u", msg->nlmsg_len); | |
1632 | if (cmd != session->cmd) | |
1633 | FAILURE("Protocol error, we sent command %s " | |
1634 | "and received error report for %s[%u]", | |
1635 | cmd2name[session->cmd], | |
1636 | cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); | |
1637 | ||
1638 | if (mnl_attr_parse(msg, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) | |
1639 | FAILURE("Broken %s error report message: " | |
1640 | "cannot validate attributes", | |
1641 | cmd2name[cmd]); | |
1642 | ||
1643 | if (!nla[IPSET_ATTR_PROTOCOL]) | |
1644 | FAILURE("Broken %s error report message: " | |
1645 | "missing protocol attribute", | |
1646 | cmd2name[cmd]); | |
3f83fda4 | 1647 | |
8e0608d3 JK |
1648 | if (nla[IPSET_ATTR_LINENO]) { |
1649 | session->lineno = mnl_attr_get_u32(nla[IPSET_ATTR_LINENO]); | |
1650 | if (nla[IPSET_ATTR_LINENO]->nla_type & NLA_F_NET_BYTEORDER) | |
1651 | session->lineno = ntohl(session->lineno); | |
1652 | } | |
3f83fda4 | 1653 | |
8e0608d3 | 1654 | return ipset_errcode(session, cmd, -err->error); |
3f83fda4 | 1655 | } |
8e0608d3 JK |
1656 | |
1657 | static int | |
1658 | callback_error(const struct nlmsghdr *nlh, void *cbdata) | |
1659 | { | |
1660 | struct ipset_session *session = cbdata; | |
1661 | struct ipset_data *data = session->data; | |
1662 | const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); | |
1663 | int ret = MNL_CB_ERROR; | |
1664 | ||
1665 | D(" called, cmd %s", cmd2name[session->cmd]); | |
1666 | if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) | |
1667 | FAILURE("Broken error message received."); | |
1668 | ||
1669 | if (err->error == 0) { | |
1670 | /* ACK */ | |
1671 | ret = MNL_CB_STOP; | |
1672 | ||
1673 | switch (session->cmd) { | |
1674 | case IPSET_CMD_CREATE: | |
1675 | /* Add successfully created set to the cache */ | |
1676 | ipset_cache_add(ipset_data_setname(data), | |
3fd6b24a JK |
1677 | ipset_data_get(data, IPSET_OPT_TYPE), |
1678 | ipset_data_family(data)); | |
8e0608d3 JK |
1679 | break; |
1680 | case IPSET_CMD_DESTROY: | |
1681 | /* Delete destroyed sets from the cache */ | |
1682 | ipset_cache_del(ipset_data_setname(data)); | |
1683 | /* Fall through */ | |
1684 | case IPSET_CMD_FLUSH: | |
1685 | break; | |
1686 | case IPSET_CMD_RENAME: | |
1687 | ipset_cache_rename(ipset_data_setname(data), | |
3f83fda4 JK |
1688 | ipset_data_get(data, |
1689 | IPSET_OPT_SETNAME2)); | |
8e0608d3 JK |
1690 | break; |
1691 | case IPSET_CMD_SWAP: | |
1692 | ipset_cache_swap(ipset_data_setname(data), | |
3f83fda4 JK |
1693 | ipset_data_get(data, |
1694 | IPSET_OPT_SETNAME2)); | |
8e0608d3 JK |
1695 | break; |
1696 | case IPSET_CMD_TEST: | |
1697 | if (!(session->envopts & IPSET_ENV_QUIET)) { | |
3f83fda4 JK |
1698 | ipset_print_elem(session->report, |
1699 | IPSET_ERRORBUFLEN, | |
1700 | session->data, | |
1701 | IPSET_OPT_NONE, 0); | |
8e0608d3 JK |
1702 | ipset_warn(session, " is in set %s.", |
1703 | ipset_data_setname(data)); | |
1704 | } | |
1705 | /* Fall through */ | |
1706 | case IPSET_CMD_ADD: | |
1707 | case IPSET_CMD_DEL: | |
1708 | break; | |
1709 | case IPSET_CMD_LIST: | |
1710 | case IPSET_CMD_SAVE: | |
1711 | /* No set in kernel */ | |
f535c61d | 1712 | print_set_done(session, true); |
8e0608d3 JK |
1713 | break; |
1714 | default: | |
3f83fda4 JK |
1715 | FAILURE("ACK message received to command %s[%u], " |
1716 | "which is not expected", | |
8e0608d3 JK |
1717 | session->cmd < IPSET_MSG_MAX |
1718 | ? cmd2name[session->cmd] : "unknown", | |
1719 | session->cmd); | |
1720 | } | |
1721 | return ret; | |
1722 | } | |
1723 | D("nlmsgerr error: %u", -err->error); | |
1724 | ||
1725 | /* Error messages */ | |
3f83fda4 | 1726 | |
8e0608d3 | 1727 | /* Special case for IPSET_CMD_TEST */ |
3f83fda4 JK |
1728 | if (session->cmd == IPSET_CMD_TEST && |
1729 | err->error == -IPSET_ERR_EXIST) { | |
8e0608d3 JK |
1730 | if (!(session->envopts & IPSET_ENV_QUIET)) { |
1731 | ipset_print_elem(session->report, IPSET_ERRORBUFLEN, | |
1732 | session->data, IPSET_OPT_NONE, 0); | |
c387170f JK |
1733 | ipset_notice(session, " is NOT in set %s.", |
1734 | ipset_data_setname(data)); | |
8e0608d3 JK |
1735 | } |
1736 | return ret; | |
1737 | } | |
1738 | ||
1739 | decode_errmsg(session, nlh); | |
3f83fda4 | 1740 | |
8e0608d3 JK |
1741 | return ret; |
1742 | } | |
1743 | ||
1744 | static int | |
1745 | callback_noop(const struct nlmsghdr *nlh UNUSED, void *data UNUSED) | |
1746 | { | |
1747 | return MNL_CB_OK; | |
1748 | } | |
1749 | /* | |
1750 | * Build and send messages | |
1751 | */ | |
1752 | ||
d137c0bd | 1753 | static inline int |
8e0608d3 JK |
1754 | open_nested(struct ipset_session *session, struct nlmsghdr *nlh, int attr) |
1755 | { | |
d137c0bd JK |
1756 | if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > session->bufsize) |
1757 | return 1; | |
3566189c | 1758 | session->nested[session->nestid++] = mnl_attr_nest_start(nlh, attr); |
d137c0bd | 1759 | return 0; |
8e0608d3 JK |
1760 | } |
1761 | ||
1762 | static inline void | |
1763 | close_nested(struct ipset_session *session, struct nlmsghdr *nlh) | |
1764 | { | |
3566189c | 1765 | mnl_attr_nest_end(nlh, session->nested[session->nestid-1]); |
8e0608d3 JK |
1766 | session->nested[--session->nestid] = NULL; |
1767 | } | |
1768 | ||
1769 | static size_t | |
1770 | attr_len(const struct ipset_attr_policy *attr, uint8_t family, uint16_t *flags) | |
1771 | { | |
1772 | switch (attr->type) { | |
0d32c5c0 | 1773 | case MNL_TYPE_NESTED: |
8e0608d3 JK |
1774 | if (attr->len) |
1775 | return attr->len; | |
1776 | ||
1777 | *flags = NLA_F_NET_BYTEORDER; | |
541e3286 | 1778 | return family == NFPROTO_IPV4 ? sizeof(uint32_t) |
8e0608d3 | 1779 | : sizeof(struct in6_addr); |
5a602182 JK |
1780 | case MNL_TYPE_U64: |
1781 | *flags = NLA_F_NET_BYTEORDER; | |
1782 | return sizeof(uint64_t); | |
8e0608d3 JK |
1783 | case MNL_TYPE_U32: |
1784 | *flags = NLA_F_NET_BYTEORDER; | |
1785 | return sizeof(uint32_t); | |
1786 | case MNL_TYPE_U16: | |
1787 | *flags = NLA_F_NET_BYTEORDER; | |
1788 | return sizeof(uint16_t); | |
1789 | case MNL_TYPE_U8: | |
1790 | return sizeof(uint8_t); | |
1791 | default: | |
1792 | return attr->len; | |
1793 | } | |
1794 | } | |
1795 | ||
d137c0bd | 1796 | #define BUFFER_FULL(bufsize, nlmsg_len, nestlen, attrlen) \ |
3f83fda4 JK |
1797 | (nlmsg_len + nestlen + MNL_ATTR_HDRLEN + MNL_ALIGN(alen) + \ |
1798 | MNL_ALIGN(sizeof(struct nlmsgerr)) > bufsize) | |
d137c0bd | 1799 | |
8e0608d3 | 1800 | static int |
d137c0bd | 1801 | rawdata2attr(struct ipset_session *session, struct nlmsghdr *nlh, |
8e0608d3 JK |
1802 | const void *d, int type, uint8_t family, |
1803 | const struct ipset_attr_policy attrs[]) | |
1804 | { | |
1805 | const struct ipset_attr_policy *attr; | |
1806 | int alen; | |
1807 | uint16_t flags = 0; | |
1808 | ||
1809 | ||
1810 | attr = &attrs[type]; | |
d137c0bd | 1811 | if (attr->type == MNL_TYPE_NESTED) { |
0d32c5c0 | 1812 | /* IP addresses */ |
d137c0bd | 1813 | struct nlattr *nested; |
b50666c0 VP |
1814 | |
1815 | if (type == IPSET_ATTR_BITMASK) | |
1816 | family = ipset_data_family(session->data); | |
1817 | ||
541e3286 | 1818 | int atype = family == NFPROTO_IPV4 ? IPSET_ATTR_IPADDR_IPV4 |
0d32c5c0 JK |
1819 | : IPSET_ATTR_IPADDR_IPV6; |
1820 | ||
d137c0bd | 1821 | alen = attr_len(attr, family, &flags); |
3f83fda4 JK |
1822 | if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, |
1823 | MNL_ATTR_HDRLEN, alen)) | |
d137c0bd JK |
1824 | return 1; |
1825 | nested = mnl_attr_nest_start(nlh, type); | |
541e3286 JE |
1826 | D("family: %s", family == NFPROTO_IPV4 ? "INET" : |
1827 | family == NFPROTO_IPV6 ? "INET6" : "UNSPEC"); | |
0d32c5c0 JK |
1828 | mnl_attr_put(nlh, atype | flags, alen, d); |
1829 | mnl_attr_nest_end(nlh, nested); | |
3f83fda4 | 1830 | |
0d32c5c0 JK |
1831 | return 0; |
1832 | } | |
d137c0bd JK |
1833 | |
1834 | alen = attr_len(attr, family, &flags); | |
1835 | if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, 0, alen)) | |
1836 | return 1; | |
1837 | ||
1838 | switch (attr->type) { | |
5a602182 JK |
1839 | case MNL_TYPE_U64: { |
1840 | uint64_t value = htobe64(*(const uint64_t *)d); | |
1841 | ||
1842 | mnl_attr_put(nlh, type | flags, alen, &value); | |
1843 | return 0; | |
1844 | } | |
8e0608d3 | 1845 | case MNL_TYPE_U32: { |
8dd10256 | 1846 | uint32_t value = htonl(*(const uint32_t *)d); |
3f83fda4 | 1847 | |
a9dcf693 JK |
1848 | mnl_attr_put(nlh, type | flags, alen, &value); |
1849 | return 0; | |
8e0608d3 JK |
1850 | } |
1851 | case MNL_TYPE_U16: { | |
8dd10256 | 1852 | uint16_t value = htons(*(const uint16_t *)d); |
3f83fda4 | 1853 | |
a9dcf693 JK |
1854 | mnl_attr_put(nlh, type | flags, alen, &value); |
1855 | return 0; | |
8e0608d3 | 1856 | } |
a9dcf693 JK |
1857 | case MNL_TYPE_NUL_STRING: |
1858 | alen = strlen((const char *)d) + 1; | |
1859 | break; | |
8e0608d3 JK |
1860 | default: |
1861 | break; | |
1862 | } | |
3f83fda4 | 1863 | |
8e0608d3 JK |
1864 | mnl_attr_put(nlh, type | flags, alen, d); |
1865 | ||
1866 | return 0; | |
1867 | } | |
1868 | ||
1869 | static int | |
d137c0bd | 1870 | data2attr(struct ipset_session *session, struct nlmsghdr *nlh, |
8e0608d3 JK |
1871 | struct ipset_data *data, int type, uint8_t family, |
1872 | const struct ipset_attr_policy attrs[]) | |
1873 | { | |
1874 | const struct ipset_attr_policy *attr = &attrs[type]; | |
1875 | ||
d137c0bd | 1876 | return rawdata2attr(session, nlh, ipset_data_get(data, attr->opt), |
8e0608d3 JK |
1877 | type, family, attrs); |
1878 | } | |
1879 | ||
b934ebcd JK |
1880 | #define ADDATTR_PROTOCOL(nlh, protocol) \ |
1881 | mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, protocol) | |
8e0608d3 | 1882 | |
d137c0bd JK |
1883 | #define ADDATTR(session, nlh, data, type, family, attrs) \ |
1884 | data2attr(session, nlh, data, type, family, attrs) | |
8e0608d3 | 1885 | |
d137c0bd | 1886 | #define ADDATTR_SETNAME(session, nlh, data) \ |
3578220c JK |
1887 | data2attr(session, nlh, data, IPSET_ATTR_SETNAME, NFPROTO_IPV4, \ |
1888 | cmd_attrs) | |
8e0608d3 | 1889 | |
d137c0bd | 1890 | #define ADDATTR_IF(session, nlh, data, type, family, attrs) \ |
3f83fda4 | 1891 | ipset_data_test(data, attrs[type].opt) ? \ |
d137c0bd | 1892 | data2attr(session, nlh, data, type, family, attrs) : 0 |
8e0608d3 | 1893 | |
d137c0bd | 1894 | #define ADDATTR_RAW(session, nlh, data, type, attrs) \ |
541e3286 | 1895 | rawdata2attr(session, nlh, data, type, NFPROTO_IPV4, attrs) |
8e0608d3 JK |
1896 | |
1897 | static void | |
d137c0bd JK |
1898 | addattr_create(struct ipset_session *session, |
1899 | struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) | |
8e0608d3 JK |
1900 | { |
1901 | int i; | |
1902 | ||
1903 | for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) | |
d137c0bd | 1904 | ADDATTR_IF(session, nlh, data, i, family, create_attrs); |
8e0608d3 JK |
1905 | } |
1906 | ||
d137c0bd JK |
1907 | static int |
1908 | addattr_adt(struct ipset_session *session, | |
1909 | struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) | |
8e0608d3 JK |
1910 | { |
1911 | int i; | |
3f83fda4 | 1912 | |
8e0608d3 | 1913 | for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) |
d137c0bd JK |
1914 | if (ADDATTR_IF(session, nlh, data, i, family, adt_attrs)) |
1915 | return 1; | |
1916 | return 0; | |
8e0608d3 JK |
1917 | } |
1918 | ||
1919 | #define PRIVATE_MSG_BUFLEN 256 | |
1920 | ||
1921 | static int | |
1922 | build_send_private_msg(struct ipset_session *session, enum ipset_cmd cmd) | |
1923 | { | |
671b9320 | 1924 | char buffer[PRIVATE_MSG_BUFLEN] __attribute__ ((aligned)) = {}; |
8673a740 | 1925 | struct nlmsghdr *nlh = (void *)buffer; |
8e0608d3 JK |
1926 | struct ipset_data *data = session->data; |
1927 | int len = PRIVATE_MSG_BUFLEN, ret; | |
1928 | enum ipset_cmd saved = session->cmd; | |
1929 | ||
1930 | /* Initialize header */ | |
1931 | session->transport->fill_hdr(session->handle, cmd, buffer, len, 0); | |
3f83fda4 | 1932 | |
b934ebcd JK |
1933 | ADDATTR_PROTOCOL(nlh, |
1934 | cmd == IPSET_CMD_PROTOCOL ? IPSET_PROTOCOL : session->protocol); | |
8e0608d3 JK |
1935 | |
1936 | switch (cmd) { | |
1937 | case IPSET_CMD_PROTOCOL: | |
1938 | break; | |
1939 | case IPSET_CMD_HEADER: | |
1940 | if (!ipset_data_test(data, IPSET_SETNAME)) | |
1941 | return ipset_err(session, | |
1942 | "Invalid internal HEADER command: " | |
1943 | "missing setname"); | |
d137c0bd | 1944 | ADDATTR_SETNAME(session, nlh, data); |
8e0608d3 JK |
1945 | break; |
1946 | case IPSET_CMD_TYPE: | |
1947 | if (!ipset_data_test(data, IPSET_OPT_TYPENAME)) | |
1948 | return ipset_err(session, | |
1949 | "Invalid internal TYPE command: " | |
1950 | "missing settype"); | |
3f83fda4 | 1951 | ADDATTR(session, nlh, data, IPSET_ATTR_TYPENAME, |
541e3286 | 1952 | NFPROTO_IPV4, cmd_attrs); |
8e0608d3 | 1953 | if (ipset_data_test(data, IPSET_OPT_FAMILY)) |
3f83fda4 | 1954 | ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, |
541e3286 | 1955 | NFPROTO_IPV4, cmd_attrs); |
8e0608d3 JK |
1956 | else |
1957 | /* bitmap:port and list:set types */ | |
541e3286 | 1958 | mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, NFPROTO_UNSPEC); |
8e0608d3 JK |
1959 | break; |
1960 | default: | |
1961 | return ipset_err(session, "Internal error: " | |
1962 | "unknown private command %u", cmd); | |
1963 | } | |
1964 | ||
1965 | /* Backup, then restore real command */ | |
1966 | session->cmd = cmd; | |
1967 | ret = session->transport->query(session->handle, buffer, len); | |
1968 | session->cmd = saved; | |
1969 | ||
1970 | return ret; | |
1971 | } | |
1972 | ||
1973 | static inline bool | |
d794a110 | 1974 | may_aggregate_ad(struct ipset_session *session, enum ipset_cmd cmd) |
8e0608d3 | 1975 | { |
3f83fda4 JK |
1976 | return session->lineno != 0 && |
1977 | (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL) && | |
1978 | cmd == session->cmd && | |
1979 | STREQ(ipset_data_setname(session->data), session->saved_setname); | |
d137c0bd | 1980 | } |
8dd10256 | 1981 | |
d137c0bd | 1982 | static int |
8e0608d3 JK |
1983 | build_msg(struct ipset_session *session, bool aggregate) |
1984 | { | |
8673a740 | 1985 | struct nlmsghdr *nlh = session->buffer; |
8e0608d3 JK |
1986 | struct ipset_data *data = session->data; |
1987 | ||
1988 | /* Public commands */ | |
1989 | D("cmd %s, nlmsg_len: %u", cmd2name[session->cmd], nlh->nlmsg_len); | |
1990 | if (nlh->nlmsg_len == 0) { | |
1991 | /* Initialize header */ | |
1992 | aggregate = false; | |
1993 | session->transport->fill_hdr(session->handle, | |
1994 | session->cmd, | |
1995 | session->buffer, | |
1996 | session->bufsize, | |
1997 | session->envopts); | |
b934ebcd | 1998 | ADDATTR_PROTOCOL(nlh, session->protocol); |
8e0608d3 JK |
1999 | } |
2000 | D("Protocol added, aggregate %s", aggregate ? "yes" : "no"); | |
2001 | switch (session->cmd) { | |
2002 | case IPSET_CMD_CREATE: { | |
2003 | const struct ipset_type *type; | |
2004 | ||
2005 | /* Sanity checkings */ | |
2006 | if (!ipset_data_test(data, IPSET_SETNAME)) | |
2007 | return ipset_err(session, | |
2008 | "Invalid create command: missing setname"); | |
2009 | if (!ipset_data_test(data, IPSET_OPT_TYPE)) | |
2010 | return ipset_err(session, | |
2011 | "Invalid create command: missing settype"); | |
3f83fda4 | 2012 | |
8e0608d3 JK |
2013 | type = ipset_data_get(data, IPSET_OPT_TYPE); |
2014 | /* Core attributes: | |
2015 | * setname, typename, revision, family, flags (optional) */ | |
d137c0bd | 2016 | ADDATTR_SETNAME(session, nlh, data); |
3f83fda4 | 2017 | ADDATTR(session, nlh, data, IPSET_ATTR_TYPENAME, |
541e3286 | 2018 | NFPROTO_IPV4, cmd_attrs); |
d137c0bd | 2019 | ADDATTR_RAW(session, nlh, &type->revision, |
8e0608d3 | 2020 | IPSET_ATTR_REVISION, cmd_attrs); |
0d32c5c0 JK |
2021 | D("family: %u, type family %u", |
2022 | ipset_data_family(data), type->family); | |
b3858740 JK |
2023 | if (ipset_data_test(data, IPSET_OPT_FAMILY)) |
2024 | ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, | |
541e3286 | 2025 | NFPROTO_IPV4, cmd_attrs); |
b3858740 JK |
2026 | else |
2027 | /* bitmap:port and list:set types */ | |
541e3286 | 2028 | mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, NFPROTO_UNSPEC); |
8e0608d3 JK |
2029 | |
2030 | /* Type-specific create attributes */ | |
2031 | D("call open_nested"); | |
2032 | open_nested(session, nlh, IPSET_ATTR_DATA); | |
d137c0bd | 2033 | addattr_create(session, nlh, data, type->family); |
8e0608d3 JK |
2034 | D("call close_nested"); |
2035 | close_nested(session, nlh); | |
2036 | break; | |
2037 | } | |
2038 | case IPSET_CMD_DESTROY: | |
2039 | case IPSET_CMD_FLUSH: | |
8e0608d3 JK |
2040 | case IPSET_CMD_SAVE: |
2041 | if (ipset_data_test(data, IPSET_SETNAME)) | |
d137c0bd | 2042 | ADDATTR_SETNAME(session, nlh, data); |
8e0608d3 | 2043 | break; |
bf8463fb JK |
2044 | case IPSET_CMD_LIST: { |
2045 | uint32_t flags = 0; | |
3f83fda4 | 2046 | |
bf8463fb JK |
2047 | if (session->envopts & IPSET_ENV_LIST_SETNAME) |
2048 | flags |= IPSET_FLAG_LIST_SETNAME; | |
2049 | if (session->envopts & IPSET_ENV_LIST_HEADER) | |
2050 | flags |= IPSET_FLAG_LIST_HEADER; | |
2051 | if (ipset_data_test(data, IPSET_SETNAME)) | |
2052 | ADDATTR_SETNAME(session, nlh, data); | |
31ee222f | 2053 | if (flags && session->mode != IPSET_LIST_SAVE) { |
bf8463fb | 2054 | ipset_data_set(data, IPSET_OPT_FLAGS, &flags); |
3578220c JK |
2055 | ADDATTR(session, nlh, data, IPSET_ATTR_FLAGS, |
2056 | NFPROTO_IPV4, cmd_attrs); | |
bf8463fb JK |
2057 | } |
2058 | break; | |
2059 | } | |
8e0608d3 JK |
2060 | case IPSET_CMD_RENAME: |
2061 | case IPSET_CMD_SWAP: | |
2062 | if (!ipset_data_test(data, IPSET_SETNAME)) | |
2063 | return ipset_err(session, | |
2064 | "Invalid %s command: missing from-setname", | |
3f83fda4 JK |
2065 | session->cmd == IPSET_CMD_SWAP ? "swap" : |
2066 | "rename"); | |
8e0608d3 JK |
2067 | if (!ipset_data_test(data, IPSET_OPT_SETNAME2)) |
2068 | return ipset_err(session, | |
2069 | "Invalid %s command: missing to-setname", | |
3f83fda4 JK |
2070 | session->cmd == IPSET_CMD_SWAP ? "swap" : |
2071 | "rename"); | |
d137c0bd | 2072 | ADDATTR_SETNAME(session, nlh, data); |
3f83fda4 JK |
2073 | ADDATTR_RAW(session, nlh, |
2074 | ipset_data_get(data, IPSET_OPT_SETNAME2), | |
8e0608d3 JK |
2075 | IPSET_ATTR_SETNAME2, cmd_attrs); |
2076 | break; | |
2077 | case IPSET_CMD_ADD: | |
2078 | case IPSET_CMD_DEL: { | |
cd5ede17 | 2079 | DD(const struct ipset_type *type); |
8e0608d3 JK |
2080 | |
2081 | if (!aggregate) { | |
2082 | /* Setname, type not checked/added yet */ | |
2083 | if (!ipset_data_test(data, IPSET_SETNAME)) | |
2084 | return ipset_err(session, | |
2085 | "Invalid %s command: missing setname", | |
3f83fda4 JK |
2086 | session->cmd == IPSET_CMD_ADD ? "add" : |
2087 | "del"); | |
8e0608d3 JK |
2088 | |
2089 | if (!ipset_data_test(data, IPSET_OPT_TYPE)) | |
2090 | return ipset_err(session, | |
2091 | "Invalid %s command: missing settype", | |
3f83fda4 JK |
2092 | session->cmd == IPSET_CMD_ADD ? "add" : |
2093 | "del"); | |
8e0608d3 JK |
2094 | |
2095 | /* Core options: setname */ | |
d137c0bd | 2096 | ADDATTR_SETNAME(session, nlh, data); |
8e0608d3 JK |
2097 | if (session->lineno != 0) { |
2098 | /* Restore mode */ | |
d137c0bd | 2099 | ADDATTR_RAW(session, nlh, &session->lineno, |
8e0608d3 JK |
2100 | IPSET_ATTR_LINENO, cmd_attrs); |
2101 | open_nested(session, nlh, IPSET_ATTR_ADT); | |
2102 | } | |
2103 | } | |
cd5ede17 | 2104 | DD(type = ipset_data_get(data, IPSET_OPT_TYPE)); |
0d32c5c0 JK |
2105 | D("family: %u, type family %u", |
2106 | ipset_data_family(data), type->family); | |
d137c0bd JK |
2107 | if (open_nested(session, nlh, IPSET_ATTR_DATA)) { |
2108 | D("open_nested failed"); | |
2109 | return 1; | |
2110 | } | |
3f83fda4 JK |
2111 | if (addattr_adt(session, nlh, data, ipset_data_family(data)) || |
2112 | ADDATTR_RAW(session, nlh, &session->lineno, | |
2113 | IPSET_ATTR_LINENO, cmd_attrs)) { | |
d137c0bd | 2114 | /* Cancel last, unfinished nested attribute */ |
3f83fda4 JK |
2115 | mnl_attr_nest_cancel(nlh, |
2116 | session->nested[session->nestid-1]); | |
d137c0bd JK |
2117 | session->nested[--session->nestid] = NULL; |
2118 | return 1; | |
2119 | } | |
8e0608d3 JK |
2120 | close_nested(session, nlh); |
2121 | break; | |
2122 | } | |
2123 | case IPSET_CMD_TEST: { | |
cd5ede17 | 2124 | DD(const struct ipset_type *type); |
8e0608d3 JK |
2125 | /* Return codes are not aggregated, so tests cannot be either */ |
2126 | ||
2127 | /* Setname, type not checked/added yet */ | |
3f83fda4 | 2128 | |
8e0608d3 JK |
2129 | if (!ipset_data_test(data, IPSET_SETNAME)) |
2130 | return ipset_err(session, | |
2131 | "Invalid test command: missing setname"); | |
2132 | ||
2133 | if (!ipset_data_test(data, IPSET_OPT_TYPE)) | |
2134 | return ipset_err(session, | |
2135 | "Invalid test command: missing settype"); | |
3f83fda4 | 2136 | |
cd5ede17 | 2137 | DD(type = ipset_data_get(data, IPSET_OPT_TYPE)); |
0d32c5c0 JK |
2138 | D("family: %u, type family %u", |
2139 | ipset_data_family(data), type->family); | |
d137c0bd | 2140 | ADDATTR_SETNAME(session, nlh, data); |
8e0608d3 | 2141 | open_nested(session, nlh, IPSET_ATTR_DATA); |
d137c0bd | 2142 | addattr_adt(session, nlh, data, ipset_data_family(data)); |
8e0608d3 JK |
2143 | close_nested(session, nlh); |
2144 | break; | |
2145 | } | |
2146 | default: | |
2147 | return ipset_err(session, "Internal error: unknown command %u", | |
2148 | session->cmd); | |
2149 | } | |
2150 | return 0; | |
2151 | } | |
2152 | ||
2153 | /** | |
2154 | * ipset_commit - commit buffered commands | |
2155 | * @session: session structure | |
2156 | * | |
2157 | * Commit buffered commands, if there are any. | |
2158 | * | |
2159 | * Returns 0 on success or a negative error code. | |
2160 | */ | |
2161 | int | |
2162 | ipset_commit(struct ipset_session *session) | |
2163 | { | |
2164 | struct nlmsghdr *nlh; | |
2165 | int ret = 0, i; | |
2166 | ||
2167 | assert(session); | |
2168 | ||
8673a740 | 2169 | nlh = session->buffer; |
3f83fda4 JK |
2170 | D("send buffer: len %u, cmd %s", |
2171 | nlh->nlmsg_len, cmd2name[session->cmd]); | |
8e0608d3 JK |
2172 | if (nlh->nlmsg_len == 0) |
2173 | /* Nothing to do */ | |
2174 | return 0; | |
2175 | ||
2176 | /* Close nested data blocks */ | |
2177 | for (i = session->nestid - 1; i >= 0; i--) | |
2178 | close_nested(session, nlh); | |
2179 | ||
2180 | /* Send buffer */ | |
2181 | ret = session->transport->query(session->handle, | |
2182 | session->buffer, | |
2183 | session->bufsize); | |
2184 | ||
d794a110 JK |
2185 | /* Reset saved data and nested state */ |
2186 | session->saved_setname[0] = '\0'; | |
31ee222f | 2187 | session->printed_set = 0; |
8e0608d3 JK |
2188 | for (i = session->nestid - 1; i >= 0; i--) |
2189 | session->nested[i] = NULL; | |
2190 | session->nestid = 0; | |
2191 | nlh->nlmsg_len = 0; | |
2192 | ||
2193 | D("ret: %d", ret); | |
2194 | ||
2195 | if (ret < 0) { | |
2196 | if (session->report[0] != '\0') | |
2197 | return -1; | |
2198 | else | |
2199 | return ipset_err(session, | |
3f83fda4 | 2200 | "Internal protocol error"); |
8e0608d3 JK |
2201 | } |
2202 | return 0; | |
2203 | } | |
2204 | ||
2205 | static mnl_cb_t cb_ctl[] = { | |
2206 | [NLMSG_NOOP] = callback_noop, | |
2207 | [NLMSG_ERROR] = callback_error, | |
2208 | [NLMSG_DONE] = callback_done, | |
2209 | [NLMSG_OVERRUN] = callback_noop, | |
2210 | [NLMSG_MIN_TYPE] = callback_data, | |
2211 | }; | |
3f83fda4 | 2212 | |
8e0608d3 JK |
2213 | static inline struct ipset_handle * |
2214 | init_transport(struct ipset_session *session) | |
2215 | { | |
2216 | session->handle = session->transport->init(cb_ctl, session); | |
2217 | ||
2218 | return session->handle; | |
2219 | } | |
2220 | ||
2221 | /** | |
2222 | * ipset_cmd - execute a command | |
2223 | * @session: session structure | |
2224 | * @cmd: command to execute | |
2225 | * @lineno: command line number in restore mode | |
2226 | * | |
2227 | * Execute - or prepare/buffer in restore mode - a command. | |
2228 | * It is the caller responsibility that the data field be filled out | |
2229 | * with all required parameters for a successful execution. | |
2230 | * The data field is cleared after this function call for the public | |
2231 | * commands. | |
2232 | * | |
2233 | * Returns 0 on success or a negative error code. | |
2234 | */ | |
2235 | int | |
2236 | ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, uint32_t lineno) | |
2237 | { | |
2238 | struct ipset_data *data; | |
2239 | bool aggregate = false; | |
2240 | int ret = -1; | |
3f83fda4 | 2241 | |
8e0608d3 JK |
2242 | assert(session); |
2243 | ||
b934ebcd | 2244 | if (cmd < IPSET_CMD_NONE || cmd >= IPSET_MSG_MAX) |
8e0608d3 JK |
2245 | return 0; |
2246 | ||
2247 | /* Initialize transport method if not done yet */ | |
2248 | if (session->handle == NULL && init_transport(session) == NULL) | |
2249 | return ipset_err(session, | |
2250 | "Cannot open session to kernel."); | |
2251 | ||
2252 | data = session->data; | |
2253 | ||
2254 | /* Check protocol version once */ | |
2255 | if (!session->version_checked) { | |
2256 | if (build_send_private_msg(session, IPSET_CMD_PROTOCOL) < 0) | |
2257 | return -1; | |
b934ebcd JK |
2258 | if (ipset_session_report_type(session) == IPSET_WARNING && |
2259 | cmd != IPSET_CMD_NONE) | |
2260 | /* Suppress protocol warning */ | |
2261 | ipset_session_report_reset(session); | |
8e0608d3 | 2262 | } |
b934ebcd JK |
2263 | /* IPSET_CMD_NONE: check protocol version only */ |
2264 | if (cmd == IPSET_CMD_NONE) | |
2265 | return 0; | |
8e0608d3 JK |
2266 | |
2267 | /* Private commands */ | |
3f83fda4 | 2268 | if (cmd == IPSET_CMD_TYPE || cmd == IPSET_CMD_HEADER) |
8e0608d3 | 2269 | return build_send_private_msg(session, cmd); |
3f83fda4 | 2270 | |
d137c0bd | 2271 | /* Check aggregatable commands */ |
d794a110 JK |
2272 | aggregate = may_aggregate_ad(session, cmd); |
2273 | if (!aggregate) { | |
2274 | /* Flush possible aggregated commands */ | |
2275 | ret = ipset_commit(session); | |
2276 | if (ret < 0) | |
2277 | return ret; | |
2278 | } | |
8e0608d3 JK |
2279 | |
2280 | /* Real command: update lineno too */ | |
2281 | session->cmd = cmd; | |
2282 | session->lineno = lineno; | |
3f83fda4 | 2283 | |
ca62c0e1 JK |
2284 | if (cmd == IPSET_CMD_LIST || cmd == IPSET_CMD_SAVE) { |
2285 | /* Set default output mode */ | |
8e0608d3 JK |
2286 | if (session->mode == IPSET_LIST_NONE) |
2287 | session->mode = IPSET_LIST_PLAIN; | |
ca62c0e1 JK |
2288 | /* Reset just in case there are multiple modes in a session */ |
2289 | ipset_envopt_unset(session, IPSET_ENV_QUOTED); | |
2290 | switch (session->mode) { | |
2291 | case IPSET_LIST_XML: | |
2292 | /* Start the root element in XML mode */ | |
2293 | safe_snprintf(session, "<ipsets>\n"); | |
2294 | break; | |
2295 | case IPSET_LIST_JSON: | |
2296 | /* Start the root element in json mode */ | |
2297 | ipset_envopt_set(session, IPSET_ENV_QUOTED); | |
2298 | safe_snprintf(session, "[\n"); | |
2299 | break; | |
2300 | default: | |
2301 | break; | |
2302 | } | |
8e0608d3 | 2303 | } |
2885607f | 2304 | |
8e0608d3 JK |
2305 | D("next: build_msg"); |
2306 | /* Build new message or append buffered commands */ | |
d137c0bd JK |
2307 | ret = build_msg(session, aggregate); |
2308 | D("build_msg returned %u", ret); | |
2309 | if (ret > 0) { | |
2310 | /* Buffer is full, send buffered commands */ | |
d794a110 JK |
2311 | ret = ipset_commit(session); |
2312 | if (ret < 0) | |
d137c0bd JK |
2313 | goto cleanup; |
2314 | ret = build_msg(session, false); | |
2315 | D("build_msg 2 returned %u", ret); | |
2316 | } | |
2317 | if (ret < 0) | |
8e0608d3 JK |
2318 | goto cleanup; |
2319 | D("past: build_msg"); | |
2320 | ||
020936c8 JK |
2321 | /* We have to save the type for error handling */ |
2322 | session->saved_type = ipset_data_get(data, IPSET_OPT_TYPE); | |
3f83fda4 JK |
2323 | if (session->lineno != 0 && |
2324 | (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL)) { | |
d794a110 | 2325 | /* Save setname for the next possible aggregated restore line */ |
8e0608d3 | 2326 | strcpy(session->saved_setname, ipset_data_setname(data)); |
d794a110 | 2327 | ipset_data_reset(data); |
8e0608d3 | 2328 | /* Don't commit: we may aggregate next command */ |
d794a110 JK |
2329 | ret = 0; |
2330 | goto cleanup; | |
8e0608d3 | 2331 | } |
d794a110 | 2332 | |
3f83fda4 | 2333 | D("call commit"); |
d794a110 JK |
2334 | ret = ipset_commit(session); |
2335 | ||
8e0608d3 JK |
2336 | cleanup: |
2337 | D("reset data"); | |
2338 | ipset_data_reset(data); | |
2339 | return ret; | |
2340 | } | |
2341 | ||
55fdd96e JK |
2342 | static |
2343 | int __attribute__ ((format (printf, 3, 4))) | |
2344 | default_print_outfn(struct ipset_session *session, void *p UNUSED, | |
2345 | const char *fmt, ...) | |
2346 | { | |
2347 | int len; | |
2348 | va_list args; | |
2349 | ||
2350 | va_start(args, fmt); | |
2351 | len = vfprintf(session->ostream, fmt, args); | |
2352 | va_end(args); | |
2353 | ||
2354 | return len; | |
2355 | } | |
2356 | ||
0907c31d | 2357 | /** |
55fdd96e JK |
2358 | * ipset_session_print_outfn - set session output printing function |
2359 | * @session: session structure | |
2360 | * @outfn: output printing function | |
2361 | * @p: pointer to private area | |
0907c31d | 2362 | * |
55fdd96e JK |
2363 | * Set the session output printing function. If the @outfn is NULL, |
2364 | * then the default output function is configured. You can set | |
2365 | * the @p pointer to a private area: the output printing function | |
2366 | * is called with @p in one of its arguments. | |
0907c31d | 2367 | * |
55fdd96e | 2368 | * Returns 0 on success or a negative error code. |
0907c31d JK |
2369 | */ |
2370 | int | |
55fdd96e JK |
2371 | ipset_session_print_outfn(struct ipset_session *session, |
2372 | ipset_print_outfn outfn, | |
2373 | void *p) | |
0907c31d | 2374 | { |
55fdd96e JK |
2375 | session->print_outfn = outfn ? outfn : default_print_outfn; |
2376 | session->p = p; | |
0907c31d JK |
2377 | return 0; |
2378 | } | |
2379 | ||
8e0608d3 JK |
2380 | /** |
2381 | * ipset_session_init - initialize an ipset session | |
55fdd96e JK |
2382 | * @outfn: output printing function |
2383 | * @p: pointer to private area | |
8e0608d3 JK |
2384 | * |
2385 | * Initialize an ipset session by allocating a session structure | |
55fdd96e JK |
2386 | * and filling out with the initialization data. The function |
2387 | * calls ipset_session_print_outfn() to set @print_outfn, @p. | |
8e0608d3 JK |
2388 | * |
2389 | * Returns the created session sctructure on success or NULL. | |
2390 | */ | |
2391 | struct ipset_session * | |
55fdd96e | 2392 | ipset_session_init(ipset_print_outfn print_outfn, void *p) |
8e0608d3 JK |
2393 | { |
2394 | struct ipset_session *session; | |
2395 | size_t bufsize = getpagesize(); | |
8dd10256 | 2396 | |
8e0608d3 JK |
2397 | /* Create session object */ |
2398 | session = calloc(1, sizeof(struct ipset_session) + bufsize); | |
2399 | if (session == NULL) | |
2400 | return NULL; | |
3713072d JK |
2401 | session->outbuf = calloc(1, IPSET_OUTBUFLEN); |
2402 | if (session->outbuf == NULL) | |
2403 | goto free_session; | |
2404 | session->outbuflen = IPSET_OUTBUFLEN; | |
8e0608d3 | 2405 | session->bufsize = bufsize; |
8dd10256 | 2406 | session->buffer = session + 1; |
55fdd96e JK |
2407 | session->istream = stdin; |
2408 | session->ostream = stdout; | |
b934ebcd | 2409 | session->protocol = IPSET_PROTOCOL; |
3713072d JK |
2410 | INIT_LIST_HEAD(&session->sorted); |
2411 | INIT_LIST_HEAD(&session->pool); | |
8e0608d3 JK |
2412 | |
2413 | /* The single transport method yet */ | |
2414 | session->transport = &ipset_mnl_transport; | |
3f83fda4 | 2415 | |
8e0608d3 | 2416 | /* Output function */ |
55fdd96e | 2417 | ipset_session_print_outfn(session, print_outfn, p); |
3f83fda4 | 2418 | |
8e0608d3 JK |
2419 | /* Initialize data structures */ |
2420 | session->data = ipset_data_init(); | |
2421 | if (session->data == NULL) | |
3713072d | 2422 | goto free_outbuf; |
8e0608d3 | 2423 | |
3fd6b24a | 2424 | ipset_cache_init(); |
8e0608d3 JK |
2425 | return session; |
2426 | ||
3713072d JK |
2427 | free_outbuf: |
2428 | free(session->outbuf); | |
8e0608d3 | 2429 | free_session: |
3f83fda4 JK |
2430 | free(session); |
2431 | return NULL; | |
8e0608d3 JK |
2432 | } |
2433 | ||
55fdd96e JK |
2434 | /** |
2435 | * ipset_session_io_full - set full IO for the session | |
2436 | * @session: session structure | |
2437 | * @filename: filename | |
2438 | * @what: operate on input/output | |
2439 | * | |
2440 | * The normal "-file" CLI interface does not provide an interface | |
2441 | * to set both the input (restore) and output (list/save) for | |
2442 | * a session. This function makes it possible to configure those. | |
2443 | * | |
2444 | * When a filename for input is passed, then the file will be opened | |
2445 | * for reading. | |
2446 | * When a filename for output is passed, then the file will be opened | |
2447 | * for writing. | |
2448 | * Previously opened files are closed. | |
2449 | * If NULL is passed as filename, stdin/stdout is set. | |
2450 | * Input/output files can be set separatedly. | |
2451 | * The function returns error if the file cannot be opened or | |
2452 | * normal IO mode is already set. | |
2453 | * | |
2454 | * Returns 0 on success or a negative error code. | |
2455 | */ | |
2456 | int | |
2457 | ipset_session_io_full(struct ipset_session *session, const char *filename, | |
2458 | enum ipset_io_type what) | |
2459 | { | |
2460 | FILE *f; | |
2461 | ||
2462 | assert(session); | |
2463 | ||
2464 | if (session->normal_io) | |
2465 | return ipset_err(session, | |
2466 | "Normal IO is in use, full IO cannot be selected"); | |
2467 | ||
2468 | switch (what) { | |
2469 | case IPSET_IO_INPUT: | |
2470 | if (session->istream != stdin) | |
2471 | fclose(session->istream); | |
2472 | if (!filename) { | |
2473 | session->istream = stdin; | |
2474 | } else { | |
2475 | f = fopen(filename, "r"); | |
2476 | if (!f) | |
2477 | return ipset_err(session, | |
2478 | "Cannot open %s for reading: %s", | |
2479 | filename, strerror(errno)); | |
2480 | session->istream = f; | |
2481 | } | |
2482 | break; | |
2483 | case IPSET_IO_OUTPUT: | |
2484 | if (session->ostream != stdout) | |
2485 | fclose(session->ostream); | |
2486 | if (!filename) { | |
2487 | session->ostream = stdout; | |
2488 | } else { | |
2489 | f = fopen(filename, "w"); | |
2490 | if (!f) | |
2491 | return ipset_err(session, | |
2492 | "Cannot open %s for writing: %s", | |
2493 | filename, strerror(errno)); | |
2494 | session->ostream = f; | |
2495 | } | |
2496 | break; | |
2497 | default: | |
2498 | return ipset_err(session, | |
2499 | "Library error, invalid ipset_io_type"); | |
2500 | } | |
2501 | session->full_io = !(session->istream == stdin && | |
2502 | session->ostream == stdout); | |
2503 | return 0; | |
2504 | } | |
2505 | ||
2506 | /** | |
2507 | * ipset_session_io_normal - set normal IO for the session | |
2508 | * @session: session structure | |
2509 | * @filename: filename | |
2510 | * @what: operate on input/output | |
2511 | * | |
2512 | * The normal "-file" CLI interface to set either the input (restore) | |
2513 | * or output (list/save) for a session. This function does not make | |
2514 | * possible to set both independently. | |
2515 | * | |
2516 | * When a filename for input is passed, then the file will be opened | |
2517 | * for reading. | |
2518 | * When a filename for output is passed, then the file will be opened | |
2519 | * for writing. | |
2520 | * Previously opened files are closed. | |
2521 | * If NULL is passed as filename, stdin/stdout is set. | |
2522 | * Input/output files cannot be set separatedly. | |
2523 | * The function returns error if the file cannot be opened or | |
2524 | * full IO mode is already set. | |
2525 | * | |
2526 | * Returns 0 on success or a negative error code. | |
2527 | */ | |
2528 | int | |
2529 | ipset_session_io_normal(struct ipset_session *session, const char *filename, | |
2530 | enum ipset_io_type what) | |
2531 | { | |
2532 | FILE *f; | |
2533 | ||
2534 | assert(session); | |
2535 | assert(filename); | |
2536 | ||
2537 | if (session->full_io) | |
2538 | return ipset_err(session, | |
2539 | "Full IO is in use, normal IO cannot be selected"); | |
2540 | if (session->istream != stdin) { | |
2541 | fclose(session->istream); | |
2542 | session->istream = stdin; | |
2543 | } | |
2544 | if (session->ostream != stdout) { | |
2545 | fclose(session->ostream); | |
2546 | session->ostream = stdout; | |
2547 | } | |
2548 | switch (what) { | |
2549 | case IPSET_IO_INPUT: | |
2550 | f = fopen(filename, "r"); | |
2551 | if (!f) | |
2552 | return ipset_err(session, | |
2553 | "Cannot open %s for reading: %s", | |
2554 | filename, strerror(errno)); | |
2555 | session->istream = f; | |
2556 | break; | |
2557 | case IPSET_IO_OUTPUT: | |
2558 | f = fopen(filename, "w"); | |
2559 | if (!f) | |
2560 | return ipset_err(session, | |
2561 | "Cannot open %s for writing: %s", | |
2562 | filename, strerror(errno)); | |
2563 | session->ostream = f; | |
2564 | break; | |
2565 | default: | |
2566 | return ipset_err(session, | |
2567 | "Library error, invalid ipset_io_type"); | |
2568 | } | |
2569 | session->normal_io = !(session->istream == stdin && | |
2570 | session->ostream == stdout); | |
2571 | return 0; | |
2572 | } | |
2573 | ||
2574 | /** | |
2575 | * ipset_session_io_stream - returns the input or output stream | |
2576 | * @what: operate on input/output | |
2577 | * | |
2578 | * Returns the input or output stream of the session. | |
2579 | */ | |
2580 | FILE * | |
2581 | ipset_session_io_stream(struct ipset_session *session, | |
2582 | enum ipset_io_type what) | |
2583 | { | |
2584 | switch (what) { | |
2585 | case IPSET_IO_INPUT: | |
2586 | return session->istream; | |
2587 | case IPSET_IO_OUTPUT: | |
2588 | return session->ostream; | |
2589 | default: | |
2590 | return NULL; | |
2591 | } | |
2592 | } | |
2593 | ||
2594 | /** | |
2595 | * ipset_session_io_close - closes the input or output stream | |
2596 | * @what: operate on input/output | |
2597 | * | |
2598 | * Closes the input or output stream of the session. | |
2599 | * | |
2600 | * Returns 0 on success or a negative error code. | |
2601 | */ | |
2602 | int | |
2603 | ipset_session_io_close(struct ipset_session *session, | |
2604 | enum ipset_io_type what) | |
2605 | { | |
2606 | switch (what) { | |
2607 | case IPSET_IO_INPUT: | |
2608 | if (session->istream != stdin) { | |
2609 | fclose(session->istream); | |
2610 | session->istream = stdin; | |
2611 | } | |
2612 | break; | |
2613 | case IPSET_IO_OUTPUT: | |
2614 | if (session->ostream != stdout) { | |
2615 | fclose(session->ostream); | |
2616 | session->ostream = stdout; | |
2617 | } | |
2618 | break; | |
2619 | default: | |
2620 | break; | |
2621 | } | |
2622 | return 0; | |
2623 | } | |
2624 | ||
8e0608d3 JK |
2625 | /** |
2626 | * ipset_session_fini - destroy an ipset session | |
2627 | * @session: session structure | |
2628 | * | |
2629 | * Destroy an ipset session: release the created structures. | |
2630 | * | |
2631 | * Returns 0 on success or a negative error code. | |
2632 | */ | |
2633 | int | |
2634 | ipset_session_fini(struct ipset_session *session) | |
2635 | { | |
3713072d | 2636 | struct ipset_sorted *pos, *n; |
8e0608d3 JK |
2637 | assert(session); |
2638 | ||
2639 | if (session->handle) | |
2640 | session->transport->fini(session->handle); | |
2641 | if (session->data) | |
2642 | ipset_data_fini(session->data); | |
55fdd96e JK |
2643 | if (session->istream != stdin) |
2644 | fclose(session->istream); | |
2645 | if (session->ostream != stdout) | |
2646 | fclose(session->ostream); | |
8e0608d3 | 2647 | |
3fd6b24a | 2648 | ipset_cache_fini(); |
3713072d JK |
2649 | |
2650 | list_for_each_entry_safe(pos, n, &session->sorted, list) { | |
2651 | list_del(&pos->list); | |
2652 | free(pos); | |
2653 | } | |
2654 | list_for_each_entry_safe(pos, n, &session->pool, list) { | |
2655 | list_del(&pos->list); | |
2656 | free(pos); | |
2657 | } | |
2658 | free(session->outbuf); | |
8e0608d3 JK |
2659 | free(session); |
2660 | return 0; | |
2661 | } | |
eb77f0db JK |
2662 | |
2663 | #ifdef IPSET_DEBUG | |
2664 | #include "debug.c" | |
2665 | #endif |