/* * em_ipset.c IPset Ematch * * (C) 2012 Florian Westphal * * Parts taken from iptables libxt_set.h: * Copyright (C) 2000-2002 Joakim Axelsson * Patrick Schaaf * Martin Josefsson * Copyright (C) 2003-2010 Jozsef Kadlecsik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #ifndef IPSET_INVALID_ID typedef __u16 ip_set_id_t; enum ip_set_dim { IPSET_DIM_ZERO = 0, IPSET_DIM_ONE, IPSET_DIM_TWO, IPSET_DIM_THREE, IPSET_DIM_MAX = 6, }; #endif /* IPSET_INVALID_ID */ #include #include "m_ematch.h" #ifndef IPSET_INVALID_ID #define IPSET_INVALID_ID 65535 #define SO_IP_SET 83 union ip_set_name_index { char name[IPSET_MAXNAMELEN]; __u16 index; }; #define IP_SET_OP_GET_BYNAME 0x00000006 /* Get set index by name */ struct ip_set_req_get_set { unsigned int op; unsigned int version; union ip_set_name_index set; }; #define IP_SET_OP_GET_BYINDEX 0x00000007 /* Get set name by index */ /* Uses ip_set_req_get_set */ #define IP_SET_OP_VERSION 0x00000100 /* Ask kernel version */ struct ip_set_req_version { unsigned int op; unsigned int version; }; #endif /* IPSET_INVALID_ID */ extern struct ematch_util ipset_ematch_util; static int get_version(unsigned int *version) { int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); struct ip_set_req_version req_version; socklen_t size = sizeof(req_version); if (sockfd < 0) { fputs("Can't open socket to ipset.\n", stderr); return -1; } req_version.op = IP_SET_OP_VERSION; res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size); if (res != 0) { perror("xt_set getsockopt"); return -1; } *version = req_version.version; return sockfd; } static int do_getsockopt(struct ip_set_req_get_set *req) { int sockfd, res; socklen_t size = sizeof(struct ip_set_req_get_set); sockfd = get_version(&req->version); if (sockfd < 0) return -1; res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size); if (res != 0) perror("Problem when communicating with ipset"); close(sockfd); if (res != 0) return -1; if (size != sizeof(struct ip_set_req_get_set)) { fprintf(stderr, "Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n", sizeof(struct ip_set_req_get_set), (size_t)size); return -1; } return res; } static int get_set_byid(char *setname, unsigned int idx) { struct ip_set_req_get_set req; int res; req.op = IP_SET_OP_GET_BYINDEX; req.set.index = idx; res = do_getsockopt(&req); if (res != 0) return -1; if (req.set.name[0] == '\0') { fprintf(stderr, "Set with index %i in kernel doesn't exist.\n", idx); return -1; } strncpy(setname, req.set.name, IPSET_MAXNAMELEN); return 0; } static int get_set_byname(const char *setname, struct xt_set_info *info) { struct ip_set_req_get_set req; int res; req.op = IP_SET_OP_GET_BYNAME; strncpy(req.set.name, setname, IPSET_MAXNAMELEN); req.set.name[IPSET_MAXNAMELEN - 1] = '\0'; res = do_getsockopt(&req); if (res != 0) return -1; if (req.set.index == IPSET_INVALID_ID) return -1; info->index = req.set.index; return 0; } static int parse_dirs(const char *opt_arg, struct xt_set_info *info) { char *saved = strdup(opt_arg); char *ptr, *tmp = saved; if (!tmp) { perror("strdup"); return -1; } while (info->dim < IPSET_DIM_MAX && tmp != NULL) { info->dim++; ptr = strsep(&tmp, ","); if (strncmp(ptr, "src", 3) == 0) info->flags |= (1 << info->dim); else if (strncmp(ptr, "dst", 3) != 0) { fputs("You must specify (the comma separated list of) 'src' or 'dst'\n", stderr); free(saved); return -1; } } if (tmp) fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX); free(saved); return tmp ? -1 : 0; } static void ipset_print_usage(FILE *fd) { fprintf(fd, "Usage: ipset(SETNAME FLAGS)\n" \ "where: SETNAME:= string\n" \ " FLAGS := { FLAG[,FLAGS] }\n" \ " FLAG := { src | dst }\n" \ "\n" \ "Example: 'ipset(bulk src,dst)'\n"); } static int ipset_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, struct bstr *args) { struct xt_set_info set_info = {}; int ret; #define PARSE_ERR(CARG, FMT, ARGS...) \ em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT, ##ARGS) if (args == NULL) return PARSE_ERR(args, "ipset: missing set name"); if (args->len >= IPSET_MAXNAMELEN) return PARSE_ERR(args, "ipset: set name too long (max %u)", IPSET_MAXNAMELEN - 1); ret = get_set_byname(args->data, &set_info); if (ret < 0) return PARSE_ERR(args, "ipset: unknown set name '%s'", args->data); if (args->next == NULL) return PARSE_ERR(args, "ipset: missing set flags"); args = bstr_next(args); if (parse_dirs(args->data, &set_info)) return PARSE_ERR(args, "ipset: error parsing set flags"); if (args->next) { args = bstr_next(args); return PARSE_ERR(args, "ipset: unknown parameter"); } addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); addraw_l(n, MAX_MSG, &set_info, sizeof(set_info)); #undef PARSE_ERR return 0; } static int ipset_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, int data_len) { int i; char setname[IPSET_MAXNAMELEN]; const struct xt_set_info *set_info = data; if (data_len != sizeof(*set_info)) { fprintf(stderr, "xt_set_info struct size mismatch\n"); return -1; } if (get_set_byid(setname, set_info->index)) return -1; fputs(setname, fd); for (i = 1; i <= set_info->dim; i++) { fprintf(fd, "%s%s", i == 1 ? " " : ",", set_info->flags & (1 << i) ? "src" : "dst"); } return 0; } struct ematch_util ipset_ematch_util = { .kind = "ipset", .kind_num = TCF_EM_IPSET, .parse_eopt = ipset_parse_eopt, .print_eopt = ipset_print_eopt, .print_usage = ipset_print_usage };