Line | Count | Source (jump to first uncovered line) |
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 | | |
19 | | #if defined(HAVE_LINUX_IPSET) |
20 | | |
21 | | #include <string.h> |
22 | | #include <errno.h> |
23 | | #include <sys/types.h> |
24 | | #include <sys/socket.h> |
25 | | #include <arpa/inet.h> |
26 | | #include <linux/netlink.h> |
27 | | |
28 | | /* We want to be able to compile against old header files |
29 | | Kernel version is handled at run-time. */ |
30 | | |
31 | 0 | #define NFNL_SUBSYS_IPSET 6 |
32 | | |
33 | 0 | #define IPSET_ATTR_DATA 7 |
34 | 0 | #define IPSET_ATTR_IP 1 |
35 | 0 | #define IPSET_ATTR_IPADDR_IPV4 1 |
36 | 0 | #define IPSET_ATTR_IPADDR_IPV6 2 |
37 | 0 | #define IPSET_ATTR_PROTOCOL 1 |
38 | 0 | #define IPSET_ATTR_SETNAME 2 |
39 | 0 | #define IPSET_CMD_ADD 9 |
40 | 0 | #define IPSET_CMD_DEL 10 |
41 | 0 | #define IPSET_MAXNAMELEN 32 |
42 | 0 | #define IPSET_PROTOCOL 6 |
43 | | |
44 | | #ifndef NFNETLINK_V0 |
45 | 0 | #define NFNETLINK_V0 0 |
46 | | #endif |
47 | | |
48 | | #ifndef NLA_F_NESTED |
49 | | #define NLA_F_NESTED (1 << 15) |
50 | | #endif |
51 | | |
52 | | #ifndef NLA_F_NET_BYTEORDER |
53 | | #define NLA_F_NET_BYTEORDER (1 << 14) |
54 | | #endif |
55 | | |
56 | | struct my_nlattr { |
57 | | __u16 nla_len; |
58 | | __u16 nla_type; |
59 | | }; |
60 | | |
61 | | struct my_nfgenmsg { |
62 | | __u8 nfgen_family; /* AF_xxx */ |
63 | | __u8 version; /* nfnetlink version */ |
64 | | __be16 res_id; /* resource id */ |
65 | | }; |
66 | | |
67 | | |
68 | | /* data structure size in here is fixed */ |
69 | 0 | #define BUFF_SZ 256 |
70 | | |
71 | 0 | #define NL_ALIGN(len) (((len)+3) & ~(3)) |
72 | | static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; |
73 | | static int ipset_sock, old_kernel; |
74 | | static char *buffer; |
75 | | |
76 | | static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data) |
77 | 0 | { |
78 | 0 | struct my_nlattr *attr = (struct my_nlattr *)((u8 *)nlh + NL_ALIGN(nlh->nlmsg_len)); |
79 | 0 | uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len; |
80 | 0 | attr->nla_type = type; |
81 | 0 | attr->nla_len = payload_len; |
82 | 0 | memcpy((u8 *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len); |
83 | 0 | nlh->nlmsg_len += NL_ALIGN(payload_len); |
84 | 0 | } |
85 | | |
86 | | void ipset_init(void) |
87 | 0 | { |
88 | 0 | old_kernel = (daemon->kernel_version < KERNEL_VERSION(2,6,32)); |
89 | | |
90 | 0 | if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1) |
91 | 0 | return; |
92 | | |
93 | 0 | if (!old_kernel && |
94 | 0 | (buffer = safe_malloc(BUFF_SZ)) && |
95 | 0 | (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 && |
96 | 0 | (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1)) |
97 | 0 | return; |
98 | | |
99 | 0 | die (_("failed to create IPset control socket: %s"), NULL, EC_MISC); |
100 | 0 | } |
101 | | |
102 | | static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr, int af, int remove) |
103 | 0 | { |
104 | 0 | struct nlmsghdr *nlh; |
105 | 0 | struct my_nfgenmsg *nfg; |
106 | 0 | struct my_nlattr *nested[2]; |
107 | 0 | uint8_t proto; |
108 | 0 | int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ; |
109 | |
|
110 | 0 | if (strlen(setname) >= IPSET_MAXNAMELEN) |
111 | 0 | { |
112 | 0 | errno = ENAMETOOLONG; |
113 | 0 | return -1; |
114 | 0 | } |
115 | | |
116 | 0 | memset(buffer, 0, BUFF_SZ); |
117 | |
|
118 | 0 | nlh = (struct nlmsghdr *)buffer; |
119 | 0 | nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr)); |
120 | 0 | nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8); |
121 | 0 | nlh->nlmsg_flags = NLM_F_REQUEST; |
122 | | |
123 | 0 | nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len); |
124 | 0 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg)); |
125 | 0 | nfg->nfgen_family = af; |
126 | 0 | nfg->version = NFNETLINK_V0; |
127 | 0 | nfg->res_id = htons(0); |
128 | | |
129 | 0 | proto = IPSET_PROTOCOL; |
130 | 0 | add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto); |
131 | 0 | add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname); |
132 | 0 | nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); |
133 | 0 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); |
134 | 0 | nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA; |
135 | 0 | nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len)); |
136 | 0 | nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr)); |
137 | 0 | nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP; |
138 | 0 | add_attr(nlh, |
139 | 0 | (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, |
140 | 0 | addrsz, ipaddr); |
141 | 0 | nested[1]->nla_len = (u8 *)buffer + NL_ALIGN(nlh->nlmsg_len) - (u8 *)nested[1]; |
142 | 0 | nested[0]->nla_len = (u8 *)buffer + NL_ALIGN(nlh->nlmsg_len) - (u8 *)nested[0]; |
143 | | |
144 | 0 | while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0, |
145 | 0 | (struct sockaddr *)&snl, sizeof(snl)))); |
146 | | |
147 | 0 | return errno == 0 ? 0 : -1; |
148 | 0 | } |
149 | | |
150 | | |
151 | | static int old_add_to_ipset(const char *setname, const union all_addr *ipaddr, int remove) |
152 | 0 | { |
153 | 0 | socklen_t size; |
154 | 0 | struct ip_set_req_adt_get { |
155 | 0 | unsigned op; |
156 | 0 | unsigned version; |
157 | 0 | union { |
158 | 0 | char name[IPSET_MAXNAMELEN]; |
159 | 0 | uint16_t index; |
160 | 0 | } set; |
161 | 0 | char typename[IPSET_MAXNAMELEN]; |
162 | 0 | } req_adt_get; |
163 | 0 | struct ip_set_req_adt { |
164 | 0 | unsigned op; |
165 | 0 | uint16_t index; |
166 | 0 | uint32_t ip; |
167 | 0 | } req_adt; |
168 | | |
169 | 0 | if (strlen(setname) >= sizeof(req_adt_get.set.name)) |
170 | 0 | { |
171 | 0 | errno = ENAMETOOLONG; |
172 | 0 | return -1; |
173 | 0 | } |
174 | | |
175 | 0 | req_adt_get.op = 0x10; |
176 | 0 | req_adt_get.version = 3; |
177 | 0 | strcpy(req_adt_get.set.name, setname); |
178 | 0 | size = sizeof(req_adt_get); |
179 | 0 | if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0) |
180 | 0 | return -1; |
181 | 0 | req_adt.op = remove ? 0x102 : 0x101; |
182 | 0 | req_adt.index = req_adt_get.set.index; |
183 | 0 | req_adt.ip = ntohl(ipaddr->addr4.s_addr); |
184 | 0 | if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0) |
185 | 0 | return -1; |
186 | | |
187 | 0 | return 0; |
188 | 0 | } |
189 | | |
190 | | |
191 | | |
192 | | int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove) |
193 | 0 | { |
194 | 0 | int ret = 0, af = AF_INET; |
195 | |
|
196 | 0 | if (flags & F_IPV6) |
197 | 0 | { |
198 | 0 | af = AF_INET6; |
199 | | /* old method only supports IPv4 */ |
200 | 0 | if (old_kernel) |
201 | 0 | { |
202 | 0 | errno = EAFNOSUPPORT ; |
203 | 0 | ret = -1; |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 0 | if (ret != -1) |
208 | 0 | ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove); |
209 | |
|
210 | 0 | if (ret == -1) |
211 | 0 | my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno)); |
212 | |
|
213 | 0 | return ret; |
214 | 0 | } |
215 | | |
216 | | #endif |