]>
Commit | Line | Data |
---|---|---|
13d86c73 JD |
1 | /* ipset.c is Copyright (c) 2013 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. |
2 | ||
3 | This program is free software; you can redistribute it and/or modify | |
4 | it under the terms of the GNU General Public License as published by | |
5 | the Free Software Foundation; version 2 dated June, 1991, or | |
6 | (at your option) version 3 dated 29 June, 2007. | |
7 | ||
8 | This program is distributed in the hope that it will be useful, | |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | GNU General Public License for more details. | |
12 | ||
13 | You should have received a copy of the GNU General Public License | |
14 | along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | */ | |
16 | ||
17 | #include "dnsmasq.h" | |
18 | ||
c4a09376 | 19 | #if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK) |
13d86c73 JD |
20 | |
21 | #include <string.h> | |
22 | #include <errno.h> | |
23 | #include <sys/types.h> | |
24 | #include <sys/socket.h> | |
25 | #include <sys/utsname.h> | |
26 | #include <arpa/inet.h> | |
27 | #include <linux/version.h> | |
28 | #include <linux/netlink.h> | |
d5052fb2 SK |
29 | |
30 | /* We want to be able to compile against old header files | |
31 | Kernel version is handled at run-time. */ | |
32 | ||
13d86c73 | 33 | #define NFNL_SUBSYS_IPSET 6 |
d5052fb2 | 34 | |
13d86c73 JD |
35 | #define IPSET_ATTR_DATA 7 |
36 | #define IPSET_ATTR_IP 1 | |
37 | #define IPSET_ATTR_IPADDR_IPV4 1 | |
38 | #define IPSET_ATTR_IPADDR_IPV6 2 | |
39 | #define IPSET_ATTR_PROTOCOL 1 | |
40 | #define IPSET_ATTR_SETNAME 2 | |
41 | #define IPSET_CMD_ADD 9 | |
42 | #define IPSET_CMD_DEL 10 | |
43 | #define IPSET_MAXNAMELEN 32 | |
44 | #define IPSET_PROTOCOL 6 | |
d5052fb2 SK |
45 | |
46 | #ifndef NFNETLINK_V0 | |
47 | #define NFNETLINK_V0 0 | |
48 | #endif | |
49 | ||
50 | #ifndef NLA_F_NESTED | |
51 | #define NLA_F_NESTED (1 << 15) | |
13d86c73 JD |
52 | #endif |
53 | ||
d5052fb2 SK |
54 | #ifndef NLA_F_NET_BYTEORDER |
55 | #define NLA_F_NET_BYTEORDER (1 << 14) | |
56 | #endif | |
57 | ||
58 | struct my_nlattr { | |
59 | __u16 nla_len; | |
60 | __u16 nla_type; | |
61 | }; | |
62 | ||
63 | struct my_nfgenmsg { | |
64 | __u8 nfgen_family; /* AF_xxx */ | |
65 | __u8 version; /* nfnetlink version */ | |
66 | __be16 res_id; /* resource id */ | |
67 | }; | |
68 | ||
69 | ||
13d86c73 JD |
70 | /* data structure size in here is fixed */ |
71 | #define BUFF_SZ 256 | |
72 | ||
73 | #define NL_ALIGN(len) (((len)+3) & ~(3)) | |
74 | static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; | |
75 | static int ipset_sock, old_kernel; | |
76 | static char *buffer; | |
77 | ||
78 | static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data) | |
79 | { | |
d5052fb2 SK |
80 | struct my_nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len); |
81 | uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len; | |
13d86c73 JD |
82 | attr->nla_type = type; |
83 | attr->nla_len = payload_len; | |
d5052fb2 | 84 | memcpy((void *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len); |
13d86c73 JD |
85 | nlh->nlmsg_len += NL_ALIGN(payload_len); |
86 | } | |
87 | ||
88 | void ipset_init(void) | |
89 | { | |
90 | struct utsname utsname; | |
91 | int version; | |
92 | char *split; | |
93 | ||
94 | if (uname(&utsname) < 0) | |
95 | die(_("failed to find kernel version: %s"), NULL, EC_MISC); | |
96 | ||
97 | split = strtok(utsname.release, "."); | |
98 | version = (split ? atoi(split) : 0); | |
99 | split = strtok(NULL, "."); | |
100 | version = version * 256 + (split ? atoi(split) : 0); | |
101 | split = strtok(NULL, "."); | |
102 | version = version * 256 + (split ? atoi(split) : 0); | |
103 | old_kernel = (version < KERNEL_VERSION(2,6,32)); | |
104 | ||
105 | if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1) | |
106 | return; | |
107 | ||
108 | if (!old_kernel && | |
109 | (buffer = safe_malloc(BUFF_SZ)) && | |
110 | (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 && | |
111 | (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1)) | |
112 | return; | |
113 | ||
114 | die (_("failed to create IPset control socket: %s"), NULL, EC_MISC); | |
115 | } | |
116 | ||
117 | static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int af, int remove) | |
118 | { | |
119 | struct nlmsghdr *nlh; | |
d5052fb2 SK |
120 | struct my_nfgenmsg *nfg; |
121 | struct my_nlattr *nested[2]; | |
13d86c73 JD |
122 | uint8_t proto; |
123 | int addrsz = INADDRSZ; | |
ab6ede7e | 124 | |
13d86c73 JD |
125 | #ifdef HAVE_IPV6 |
126 | if (af == AF_INET6) | |
127 | addrsz = IN6ADDRSZ; | |
128 | #endif | |
129 | ||
130 | if (strlen(setname) >= IPSET_MAXNAMELEN) | |
131 | { | |
132 | errno = ENAMETOOLONG; | |
133 | return -1; | |
134 | } | |
135 | ||
4582c0ef | 136 | memset(buffer, 0, BUFF_SZ); |
13d86c73 JD |
137 | |
138 | nlh = (struct nlmsghdr *)buffer; | |
139 | nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr)); | |
140 | nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8); | |
141 | nlh->nlmsg_flags = NLM_F_REQUEST; | |
142 | ||
d5052fb2 SK |
143 | nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len); |
144 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg)); | |
13d86c73 JD |
145 | nfg->nfgen_family = af; |
146 | nfg->version = NFNETLINK_V0; | |
147 | nfg->res_id = htons(0); | |
148 | ||
149 | proto = IPSET_PROTOCOL; | |
150 | add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto); | |
151 | add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname); | |
d5052fb2 SK |
152 | nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); |
153 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); | |
13d86c73 | 154 | nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA; |
d5052fb2 SK |
155 | nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); |
156 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); | |
13d86c73 JD |
157 | nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP; |
158 | add_attr(nlh, | |
159 | (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, | |
160 | addrsz, &ipaddr->addr); | |
161 | nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1]; | |
162 | nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0]; | |
163 | ||
ff841ebf SK |
164 | while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0, |
165 | (struct sockaddr *)&snl, sizeof(snl)))); | |
166 | ||
167 | return errno == 0 ? 0 : -1; | |
13d86c73 JD |
168 | } |
169 | ||
170 | ||
171 | static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int remove) | |
172 | { | |
173 | socklen_t size; | |
174 | struct ip_set_req_adt_get { | |
175 | unsigned op; | |
176 | unsigned version; | |
177 | union { | |
178 | char name[IPSET_MAXNAMELEN]; | |
179 | uint16_t index; | |
180 | } set; | |
181 | char typename[IPSET_MAXNAMELEN]; | |
182 | } req_adt_get; | |
183 | struct ip_set_req_adt { | |
184 | unsigned op; | |
185 | uint16_t index; | |
186 | uint32_t ip; | |
187 | } req_adt; | |
188 | ||
189 | if (strlen(setname) >= sizeof(req_adt_get.set.name)) | |
190 | { | |
191 | errno = ENAMETOOLONG; | |
192 | return -1; | |
193 | } | |
194 | ||
195 | req_adt_get.op = 0x10; | |
196 | req_adt_get.version = 3; | |
197 | strcpy(req_adt_get.set.name, setname); | |
198 | size = sizeof(req_adt_get); | |
199 | if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0) | |
200 | return -1; | |
201 | req_adt.op = remove ? 0x102 : 0x101; | |
202 | req_adt.index = req_adt_get.set.index; | |
203 | req_adt.ip = ntohl(ipaddr->addr.addr4.s_addr); | |
204 | if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0) | |
205 | return -1; | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | ||
211 | ||
212 | int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove) | |
213 | { | |
214 | int af = AF_INET; | |
215 | ||
216 | #ifdef HAVE_IPV6 | |
217 | if (flags & F_IPV6) | |
218 | { | |
219 | af = AF_INET6; | |
220 | /* old method only supports IPv4 */ | |
221 | if (old_kernel) | |
222 | return -1; | |
223 | } | |
224 | #endif | |
225 | ||
226 | return old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); | |
227 | } | |
228 | ||
229 | #endif |