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