1 /* $OpenBSD: pftable.c,v 1.18 2024/02/13 16:35:43 claudio Exp $ */
2
3 /*
4 * Copyright (c) 2004 Damien Miller <djm@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/socket.h>
22
23 #include <netinet/in.h>
24 #include <net/if.h>
25 #include <net/pfvar.h>
26
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <fcntl.h>
31
32 #include "log.h"
33
34 /* Namespace collision: these are defined in pfvar.h and bgpd.h */
35 #undef v4
36 #undef v6
37
38 #include "bgpd.h"
39
40 static int devpf = -1;
41
42 struct pf_table {
43 LIST_ENTRY(pf_table) entry;
44 char name[PFTABLE_LEN];
45 unsigned long what;
46 struct pfr_addr *worklist;
47 int naddrs;
48 int nalloc;
49 };
50
51 /* List of tables under management */
52 LIST_HEAD(, pf_table) tables = LIST_HEAD_INITIALIZER(tables);
53
54 static int
pftable_change(struct pf_table * pft)55 pftable_change(struct pf_table *pft)
56 {
57 struct pfioc_table tio;
58 int ret;
59
60 if (pft->naddrs == 0 || pft->what == 0)
61 return (0);
62
63 if (devpf == -1 && ((devpf = open("/dev/pf", O_RDWR|O_CLOEXEC)) == -1))
64 fatal("open(/dev/pf)");
65
66 memset(&tio, 0, sizeof(tio));
67 strlcpy(tio.pfrio_table.pfrt_name, pft->name,
68 sizeof(tio.pfrio_table.pfrt_name));
69 tio.pfrio_buffer = pft->worklist;
70 tio.pfrio_esize = sizeof(*pft->worklist);
71 tio.pfrio_size = pft->naddrs;
72
73 ret = ioctl(devpf, pft->what, &tio);
74
75 /* bad prefixes shouldn't cause us to die */
76 if (ret == -1) {
77 if (errno == EINVAL)
78 return (0);
79 log_warn("pftable_change ioctl");
80 }
81
82 return (ret);
83 }
84
85 static int
pftable_clear(const char * name)86 pftable_clear(const char *name)
87 {
88 struct pfioc_table tio;
89
90 if (devpf == -1 && ((devpf = open("/dev/pf", O_RDWR|O_CLOEXEC)) == -1))
91 fatal("open(/dev/pf)");
92
93 memset(&tio, 0, sizeof(tio));
94 strlcpy(tio.pfrio_table.pfrt_name, name,
95 sizeof(tio.pfrio_table.pfrt_name));
96
97 if (ioctl(devpf, DIOCRCLRADDRS, &tio) == -1) {
98 log_warn("pftable_clear ioctl");
99 return (-1);
100 }
101
102 return (0);
103 }
104
105 int
pftable_exists(const char * name)106 pftable_exists(const char *name)
107 {
108 struct pfioc_table tio;
109 struct pfr_astats dummy;
110
111 if (devpf == -1 && ((devpf = open("/dev/pf", O_RDWR|O_CLOEXEC)) == -1))
112 fatal("open(/dev/pf)");
113
114 memset(&tio, 0, sizeof(tio));
115 strlcpy(tio.pfrio_table.pfrt_name, name,
116 sizeof(tio.pfrio_table.pfrt_name));
117 tio.pfrio_buffer = &dummy;
118 tio.pfrio_esize = sizeof(dummy);
119 tio.pfrio_size = 1;
120
121 if (ioctl(devpf, DIOCRGETASTATS, &tio) == -1)
122 return (-1);
123
124 return (0);
125 }
126
127 int
pftable_add(const char * name)128 pftable_add(const char *name)
129 {
130 struct pf_table *pft;
131
132 /* Ignore duplicates */
133 LIST_FOREACH(pft, &tables, entry)
134 if (strcmp(pft->name, name) == 0)
135 return (0);
136
137 if ((pft = calloc(1, sizeof(*pft))) == NULL) {
138 log_warn("pftable malloc");
139 return (-1);
140 }
141
142 if (strlcpy(pft->name, name, sizeof(pft->name)) >= sizeof(pft->name)) {
143 log_warn("pf_table name too long");
144 free(pft);
145 return (-1);
146 }
147
148 LIST_INSERT_HEAD(&tables, pft, entry);
149
150 return (0);
151 }
152
153 int
pftable_clear_all(void)154 pftable_clear_all(void)
155 {
156 struct pf_table *pft;
157
158 LIST_FOREACH(pft, &tables, entry) {
159 if (pftable_clear(pft->name) != 0)
160 return (-1);
161 free(pft->worklist);
162 pft->worklist = NULL;
163 pft->nalloc = pft->naddrs = 0;
164 pft->what = 0;
165 }
166
167 return (0);
168 }
169
170 static int
pftable_add_work(const char * table,struct bgpd_addr * addr,uint8_t len,int del)171 pftable_add_work(const char *table, struct bgpd_addr *addr,
172 uint8_t len, int del)
173 {
174 struct pf_table *pft;
175 struct pfr_addr *pfa, *tmp;
176 unsigned long what;
177
178 if (*table == '\0' || len > 128)
179 fatal("pftable_add_work: insane");
180
181 /* Find table */
182 LIST_FOREACH(pft, &tables, entry)
183 if (strcmp(pft->name, table) == 0)
184 break;
185
186 if (pft == NULL) {
187 log_warn("pf table %s not found", table);
188 return (-1);
189 }
190
191 /*
192 * Only one type of work on the list at a time,
193 * commit pending work first before adding new work
194 */
195 what = del ? DIOCRDELADDRS : DIOCRADDADDRS;
196 if (pft->naddrs != 0 && pft->what != what)
197 pftable_commit();
198
199 if (pft->nalloc <= pft->naddrs)
200 pft->nalloc = pft->nalloc == 0 ? 1 : pft->nalloc * 2;
201 tmp = reallocarray(pft->worklist, pft->nalloc, sizeof(*tmp));
202 if (tmp == NULL) {
203 if (pft->worklist != NULL) {
204 log_warn("pftable_add_work: malloc");
205 free(pft->worklist);
206 pft->worklist = NULL;
207 }
208 pft->nalloc = pft->naddrs = 0;
209 pft->what = 0;
210 return (-1);
211 }
212 pft->worklist = tmp;
213 pfa = &pft->worklist[pft->naddrs];
214
215 memset(pfa, 0, sizeof(*pfa));
216 memcpy(&pfa->pfra_u, &addr->v6, (len + 7U) / 8);
217 pfa->pfra_af = aid2af(addr->aid);
218 pfa->pfra_net = len;
219
220 pft->naddrs++;
221 pft->what = what;
222
223 /* Don't let the list grow too large */
224 if (pft->naddrs >= 1024)
225 pftable_commit();
226
227 return (0);
228 }
229
230 /* imsg handlers */
231 int
pftable_addr_add(struct pftable_msg * m)232 pftable_addr_add(struct pftable_msg *m)
233 {
234 return (pftable_add_work(m->pftable, &m->addr, m->len, 0));
235 }
236
237 int
pftable_addr_remove(struct pftable_msg * m)238 pftable_addr_remove(struct pftable_msg *m)
239 {
240 return (pftable_add_work(m->pftable, &m->addr, m->len, 1));
241 }
242
243 int
pftable_commit(void)244 pftable_commit(void)
245 {
246 struct pf_table *pft;
247 int ret = 0;
248
249 LIST_FOREACH(pft, &tables, entry) {
250 if (pft->what != 0 && pftable_change(pft) != 0)
251 ret = -1;
252 free(pft->worklist);
253 pft->worklist = NULL;
254 pft->nalloc = pft->naddrs = 0;
255 pft->what = 0;
256 }
257
258 return (ret);
259 }
260