Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 1 | /* |
| 2 | * em_ipset.c IPset Ematch |
| 3 | * |
| 4 | * (C) 2012 Florian Westphal <fw@strlen.de> |
| 5 | * |
| 6 | * Parts taken from iptables libxt_set.h: |
| 7 | * Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu> |
| 8 | * Patrick Schaaf <bof@bof.de> |
| 9 | * Martin Josefsson <gandalf@wlug.westbo.se> |
| 10 | * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@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 { |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 55 | unsigned int op; |
| 56 | unsigned int version; |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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 { |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 65 | unsigned int op; |
| 66 | unsigned int version; |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 67 | }; |
| 68 | #endif /* IPSET_INVALID_ID */ |
| 69 | |
| 70 | extern struct ematch_util ipset_ematch_util; |
| 71 | |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 72 | static int get_version(unsigned int *version) |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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); |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 98 | |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 99 | sockfd = get_version(&req->version); |
| 100 | if (sockfd < 0) |
| 101 | return -1; |
| 102 | res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size); |
| 103 | if (res != 0) |
| 104 | perror("Problem when communicating with ipset"); |
| 105 | close(sockfd); |
| 106 | if (res != 0) |
| 107 | return -1; |
| 108 | |
| 109 | if (size != sizeof(struct ip_set_req_get_set)) { |
| 110 | fprintf(stderr, |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 111 | "Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n", |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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 | { |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 161 | char *saved = strdup(opt_arg); |
| 162 | char *ptr, *tmp = saved; |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 163 | |
| 164 | if (!tmp) { |
| 165 | perror("strdup"); |
| 166 | return -1; |
| 167 | } |
| 168 | |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 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); |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 176 | free(saved); |
| 177 | return -1; |
| 178 | } |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 179 | } |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 180 | |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 181 | if (tmp) |
| 182 | fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX); |
| 183 | free(saved); |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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...) \ |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 207 | em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT, ##ARGS) |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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; |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 241 | char setname[IPSET_MAXNAMELEN]; |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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 | |
Stephen Hemminger | 32a121c | 2016-03-21 11:48:36 -0700 | [diff] [blame] | 249 | if (get_set_byid(setname, set_info->index)) |
Florian Westphal | 8194411 | 2012-08-09 09:18:50 +0000 | [diff] [blame] | 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 | }; |