blob: cdb86c4eeb7051bbe135e85b67427515731195ab [file] [log] [blame]
Marc Boucher5054e852002-01-19 10:59:12 +00001/* Shared library add-on to iptables for conntrack matching support.
2 * GPL (C) 2001 Marc Boucher (marc@mbsi.ca).
3 */
4
5#include <stdio.h>
6#include <netdb.h>
7#include <string.h>
8#include <stdlib.h>
9#include <getopt.h>
10#include <ctype.h>
11#include <iptables.h>
12#include <linux/netfilter_ipv4/ip_conntrack.h>
13#include <linux/netfilter_ipv4/ip_conntrack_tuple.h>
Martin Josefsson1da399c2004-05-26 15:50:57 +000014/* For 64bit kernel / 32bit userspace */
15#include "../include/linux/netfilter_ipv4/ipt_conntrack.h"
Marc Boucher5054e852002-01-19 10:59:12 +000016
Harald Welte4dc734c2003-10-07 18:55:13 +000017#ifndef IPT_CONNTRACK_STATE_UNTRACKED
18#define IPT_CONNTRACK_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 3))
19#endif
20
Marc Boucher5054e852002-01-19 10:59:12 +000021/* Function which prints out usage message. */
22static void
23help(void)
24{
25 printf(
26"conntrack match v%s options:\n"
Harald Welte4dc734c2003-10-07 18:55:13 +000027" [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000028" State(s) to match\n"
29" [!] --ctproto proto Protocol to match; by number or name, eg. `tcp'\n"
30" --ctorigsrc [!] address[/mask]\n"
31" Original source specification\n"
32" --ctorigdst [!] address[/mask]\n"
33" Original destination specification\n"
34" --ctreplsrc [!] address[/mask]\n"
35" Reply source specification\n"
36" --ctrepldst [!] address[/mask]\n"
37" Reply destination specification\n"
Harald Weltea643c3e2003-08-25 11:08:52 +000038" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000039" Status(es) to match\n"
40" [!] --ctexpire time[:time] Match remaining lifetime in seconds against\n"
41" value or range of values (inclusive)\n"
Harald Welte80fe35d2002-05-29 13:08:15 +000042"\n", IPTABLES_VERSION);
Marc Boucher5054e852002-01-19 10:59:12 +000043}
44
45
46
47static struct option opts[] = {
48 { "ctstate", 1, 0, '1' },
49 { "ctproto", 1, 0, '2' },
50 { "ctorigsrc", 1, 0, '3' },
51 { "ctorigdst", 1, 0, '4' },
52 { "ctreplsrc", 1, 0, '5' },
53 { "ctrepldst", 1, 0, '6' },
54 { "ctstatus", 1, 0, '7' },
55 { "ctexpire", 1, 0, '8' },
56 {0}
57};
58
Marc Boucher5054e852002-01-19 10:59:12 +000059static int
60parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
61{
62 if (strncasecmp(state, "INVALID", strlen) == 0)
63 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
64 else if (strncasecmp(state, "NEW", strlen) == 0)
65 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
66 else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
67 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
68 else if (strncasecmp(state, "RELATED", strlen) == 0)
69 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
Harald Welte4dc734c2003-10-07 18:55:13 +000070 else if (strncasecmp(state, "UNTRACKED", strlen) == 0)
71 sinfo->statemask |= IPT_CONNTRACK_STATE_UNTRACKED;
Marc Boucher5054e852002-01-19 10:59:12 +000072 else if (strncasecmp(state, "SNAT", strlen) == 0)
73 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
74 else if (strncasecmp(state, "DNAT", strlen) == 0)
75 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
76 else
77 return 0;
78 return 1;
79}
80
81static void
82parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
83{
84 const char *comma;
85
86 while ((comma = strchr(arg, ',')) != NULL) {
87 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
88 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
89 arg = comma+1;
90 }
91
92 if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
93 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
94}
95
96static int
97parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
98{
99 if (strncasecmp(status, "NONE", strlen) == 0)
100 sinfo->statusmask |= 0;
101 else if (strncasecmp(status, "EXPECTED", strlen) == 0)
102 sinfo->statusmask |= IPS_EXPECTED;
103 else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
104 sinfo->statusmask |= IPS_SEEN_REPLY;
105 else if (strncasecmp(status, "ASSURED", strlen) == 0)
106 sinfo->statusmask |= IPS_ASSURED;
Harald Weltea643c3e2003-08-25 11:08:52 +0000107#ifdef IPS_CONFIRMED
108 else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
109 sinfo->stausmask |= IPS_CONFIRMED;
110#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000111 else
112 return 0;
113 return 1;
114}
115
116static void
117parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
118{
119 const char *comma;
120
121 while ((comma = strchr(arg, ',')) != NULL) {
122 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
123 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
124 arg = comma+1;
125 }
126
127 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
128 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
129}
130
Martin Josefsson1da399c2004-05-26 15:50:57 +0000131#ifdef KERNEL_64_USERSPACE_32
132static unsigned long long
133parse_expire(const char *s)
134{
135 unsigned long long len;
136
137 if (string_to_number_ll(s, 0, 0, &len) == -1)
138 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
139 else
140 return len;
141}
142#else
Marc Boucher5054e852002-01-19 10:59:12 +0000143static unsigned long
144parse_expire(const char *s)
145{
146 unsigned int len;
147
Martin Josefsson1da399c2004-05-26 15:50:57 +0000148 if (string_to_number(s, 0, 0, &len) == -1)
Marc Boucher5054e852002-01-19 10:59:12 +0000149 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
150 else
151 return len;
152}
Martin Josefsson1da399c2004-05-26 15:50:57 +0000153#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000154
155/* If a single value is provided, min and max are both set to the value */
156static void
157parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
158{
159 char *buffer;
160 char *cp;
161
162 buffer = strdup(s);
163 if ((cp = strchr(buffer, ':')) == NULL)
164 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
165 else {
166 *cp = '\0';
167 cp++;
168
169 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
Martin Josefsson1da399c2004-05-26 15:50:57 +0000170 sinfo->expires_max = cp[0] ? parse_expire(cp) : -1;
Marc Boucher5054e852002-01-19 10:59:12 +0000171 }
172 free(buffer);
173
174 if (sinfo->expires_min > sinfo->expires_max)
175 exit_error(PARAMETER_PROBLEM,
Martin Josefsson1da399c2004-05-26 15:50:57 +0000176#ifdef KERNEL_64_USERSPACE_32
177 "expire min. range value `%llu' greater than max. "
178 "range value `%llu'", sinfo->expires_min, sinfo->expires_max);
179#else
Marc Boucher5054e852002-01-19 10:59:12 +0000180 "expire min. range value `%lu' greater than max. "
181 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
Martin Josefsson1da399c2004-05-26 15:50:57 +0000182#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000183}
184
185/* Function which parses command options; returns true if it
186 ate an option */
187static int
188parse(int c, char **argv, int invert, unsigned int *flags,
189 const struct ipt_entry *entry,
190 unsigned int *nfcache,
191 struct ipt_entry_match **match)
192{
193 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
194 char *protocol = NULL;
195 unsigned int naddrs = 0;
196 struct in_addr *addrs = NULL;
197
198
199 switch (c) {
200 case '1':
Harald Welteb77f1da2002-03-14 11:35:58 +0000201 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000202
203 parse_states(argv[optind-1], sinfo);
204 if (invert) {
205 sinfo->invflags |= IPT_CONNTRACK_STATE;
206 }
207 sinfo->flags |= IPT_CONNTRACK_STATE;
208 break;
209
210 case '2':
Harald Welte3c5bd602002-03-14 19:54:34 +0000211 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000212
213 if(invert)
214 sinfo->invflags |= IPT_CONNTRACK_PROTO;
215
216 /* Canonicalize into lower case */
217 for (protocol = argv[optind-1]; *protocol; protocol++)
218 *protocol = tolower(*protocol);
219
220 protocol = argv[optind-1];
221 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
222
223 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
224 && (sinfo->invflags & IPT_INV_PROTO))
225 exit_error(PARAMETER_PROBLEM,
226 "rule would never match protocol");
227
228 sinfo->flags |= IPT_CONNTRACK_PROTO;
229 break;
230
231 case '3':
Harald Welteb77f1da2002-03-14 11:35:58 +0000232 check_inverse(optarg, &invert, &optind, 9);
Marc Boucher5054e852002-01-19 10:59:12 +0000233
234 if (invert)
235 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
236
237 parse_hostnetworkmask(argv[optind-1], &addrs,
238 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
239 &naddrs);
240 if(naddrs > 1)
241 exit_error(PARAMETER_PROBLEM,
242 "multiple IP addresses not allowed");
243
244 if(naddrs == 1) {
245 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
246 }
247
248 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
249 break;
250
251 case '4':
Harald Welteb77f1da2002-03-14 11:35:58 +0000252 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000253
254 if (invert)
255 sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
256
257 parse_hostnetworkmask(argv[optind-1], &addrs,
258 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
259 &naddrs);
260 if(naddrs > 1)
261 exit_error(PARAMETER_PROBLEM,
262 "multiple IP addresses not allowed");
263
264 if(naddrs == 1) {
265 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
266 }
267
268 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
269 break;
270
271 case '5':
Harald Welteb77f1da2002-03-14 11:35:58 +0000272 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000273
274 if (invert)
275 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
276
277 parse_hostnetworkmask(argv[optind-1], &addrs,
278 &sinfo->sipmsk[IP_CT_DIR_REPLY],
279 &naddrs);
280 if(naddrs > 1)
281 exit_error(PARAMETER_PROBLEM,
282 "multiple IP addresses not allowed");
283
284 if(naddrs == 1) {
285 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
286 }
287
288 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
289 break;
290
291 case '6':
Harald Welteb77f1da2002-03-14 11:35:58 +0000292 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000293
294 if (invert)
295 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
296
297 parse_hostnetworkmask(argv[optind-1], &addrs,
298 &sinfo->dipmsk[IP_CT_DIR_REPLY],
299 &naddrs);
300 if(naddrs > 1)
301 exit_error(PARAMETER_PROBLEM,
302 "multiple IP addresses not allowed");
303
304 if(naddrs == 1) {
305 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
306 }
307
308 sinfo->flags |= IPT_CONNTRACK_REPLDST;
309 break;
310
311 case '7':
Harald Welteb77f1da2002-03-14 11:35:58 +0000312 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000313
314 parse_statuses(argv[optind-1], sinfo);
315 if (invert) {
316 sinfo->invflags |= IPT_CONNTRACK_STATUS;
317 }
318 sinfo->flags |= IPT_CONNTRACK_STATUS;
319 break;
320
321 case '8':
Harald Welteb77f1da2002-03-14 11:35:58 +0000322 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000323
324 parse_expires(argv[optind-1], sinfo);
325 if (invert) {
326 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
327 }
328 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
329 break;
330
331 default:
332 return 0;
333 }
334
335 *flags = sinfo->flags;
336 return 1;
337}
338
339static void
340final_check(unsigned int flags)
341{
342 if (!flags)
343 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
344}
345
346static void
347print_state(unsigned int statemask)
348{
349 const char *sep = "";
350
351 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
352 printf("%sINVALID", sep);
353 sep = ",";
354 }
355 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
356 printf("%sNEW", sep);
357 sep = ",";
358 }
359 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
360 printf("%sRELATED", sep);
361 sep = ",";
362 }
363 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
364 printf("%sESTABLISHED", sep);
365 sep = ",";
366 }
Harald Welte4dc734c2003-10-07 18:55:13 +0000367 if (statemask & IPT_CONNTRACK_STATE_UNTRACKED) {
368 printf("%sUNTRACKED", sep);
369 sep = ",";
370 }
Marc Boucher5054e852002-01-19 10:59:12 +0000371 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
372 printf("%sSNAT", sep);
373 sep = ",";
374 }
375 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
376 printf("%sDNAT", sep);
377 sep = ",";
378 }
379 printf(" ");
380}
381
382static void
383print_status(unsigned int statusmask)
384{
385 const char *sep = "";
386
387 if (statusmask & IPS_EXPECTED) {
388 printf("%sEXPECTED", sep);
389 sep = ",";
390 }
391 if (statusmask & IPS_SEEN_REPLY) {
392 printf("%sSEEN_REPLY", sep);
393 sep = ",";
394 }
395 if (statusmask & IPS_ASSURED) {
396 printf("%sASSURED", sep);
397 sep = ",";
398 }
Harald Weltea643c3e2003-08-25 11:08:52 +0000399#ifdef IPS_CONFIRMED
400 if (statusmask & IPS_CONFIRMED) {
401 printf("%sCONFIRMED", sep);
402 sep =",";
403 }
404#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000405 if (statusmask == 0) {
406 printf("%sNONE", sep);
407 sep = ",";
408 }
409 printf(" ");
410}
411
412static void
413print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
414{
415 char buf[BUFSIZ];
416
Tom Eastep55548fd2005-09-19 15:14:04 +0000417 if (inv)
418 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000419
420 if (mask->s_addr == 0L && !numeric)
421 printf("%s ", "anywhere");
422 else {
423 if (numeric)
424 sprintf(buf, "%s", addr_to_dotted(addr));
425 else
426 sprintf(buf, "%s", addr_to_anyname(addr));
427 strcat(buf, mask_to_dotted(mask));
428 printf("%s ", buf);
429 }
430}
431
432/* Saves the matchinfo in parsable form to stdout. */
433static void
434matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
435{
436 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
437
438 if(sinfo->flags & IPT_CONNTRACK_STATE) {
439 printf("%sctstate ", optpfx);
440 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000441 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000442 print_state(sinfo->statemask);
443 }
444
Phil Oester5a4892b2005-11-17 13:34:51 +0000445 if(sinfo->flags & IPT_CONNTRACK_PROTO) {
446 printf("%sctproto ", optpfx);
447 if (sinfo->invflags & IPT_CONNTRACK_PROTO)
448 printf("! ");
449 printf("%u ", sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum);
450 }
451
Marc Boucher5054e852002-01-19 10:59:12 +0000452 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
453 printf("%sctorigsrc ", optpfx);
454
455 print_addr(
456 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
457 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
458 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
459 numeric);
460 }
461
462 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
463 printf("%sctorigdst ", optpfx);
464
465 print_addr(
466 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
467 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
468 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
469 numeric);
470 }
471
472 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000473 printf("%sctreplsrc ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000474
475 print_addr(
476 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
477 &sinfo->sipmsk[IP_CT_DIR_REPLY],
478 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
479 numeric);
480 }
481
482 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000483 printf("%sctrepldst ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000484
485 print_addr(
486 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
487 &sinfo->dipmsk[IP_CT_DIR_REPLY],
488 sinfo->invflags & IPT_CONNTRACK_REPLDST,
489 numeric);
490 }
491
492 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
493 printf("%sctstatus ", optpfx);
Phil Oester811b0402004-08-23 18:41:44 +0000494 if (sinfo->invflags & IPT_CONNTRACK_STATUS)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000495 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000496 print_status(sinfo->statusmask);
497 }
498
499 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
500 printf("%sctexpire ", optpfx);
501 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000502 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000503
Martin Josefsson1da399c2004-05-26 15:50:57 +0000504#ifdef KERNEL_64_USERSPACE_32
505 if (sinfo->expires_max == sinfo->expires_min)
506 printf("%llu ", sinfo->expires_min);
507 else
508 printf("%llu:%llu ", sinfo->expires_min, sinfo->expires_max);
509#else
Marc Boucher5054e852002-01-19 10:59:12 +0000510 if (sinfo->expires_max == sinfo->expires_min)
511 printf("%lu ", sinfo->expires_min);
512 else
513 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
Martin Josefsson1da399c2004-05-26 15:50:57 +0000514#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000515 }
516}
517
518/* Prints out the matchinfo. */
519static void
520print(const struct ipt_ip *ip,
521 const struct ipt_entry_match *match,
522 int numeric)
523{
524 matchinfo_print(ip, match, numeric, "");
525}
526
527/* Saves the matchinfo in parsable form to stdout. */
528static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
529{
Joszef Kadlecsikdb503f92004-05-05 10:10:33 +0000530 matchinfo_print(ip, match, 1, "--");
Marc Boucher5054e852002-01-19 10:59:12 +0000531}
532
Pablo Neira8caee8b2004-12-28 13:11:59 +0000533static struct iptables_match conntrack = {
534 .next = NULL,
535 .name = "conntrack",
536 .version = IPTABLES_VERSION,
537 .size = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
538 .userspacesize = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
539 .help = &help,
Pablo Neira8caee8b2004-12-28 13:11:59 +0000540 .parse = &parse,
541 .final_check = &final_check,
542 .print = &print,
543 .save = &save,
544 .extra_opts = opts
Marc Boucher5054e852002-01-19 10:59:12 +0000545};
546
547void _init(void)
548{
549 register_match(&conntrack);
550}