GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: pf_ruleset.c,v 1.16 2017/09/05 22:15:32 sashan Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* Copyright (c) 2001 Daniel Hartmeier |
||
5 |
* Copyright (c) 2002,2003 Henning Brauer |
||
6 |
* All rights reserved. |
||
7 |
* |
||
8 |
* Redistribution and use in source and binary forms, with or without |
||
9 |
* modification, are permitted provided that the following conditions |
||
10 |
* are met: |
||
11 |
* |
||
12 |
* - Redistributions of source code must retain the above copyright |
||
13 |
* notice, this list of conditions and the following disclaimer. |
||
14 |
* - Redistributions in binary form must reproduce the above |
||
15 |
* copyright notice, this list of conditions and the following |
||
16 |
* disclaimer in the documentation and/or other materials provided |
||
17 |
* with the distribution. |
||
18 |
* |
||
19 |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
20 |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
21 |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||
22 |
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||
23 |
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||
24 |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||
25 |
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
26 |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||
27 |
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||
28 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||
29 |
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||
30 |
* POSSIBILITY OF SUCH DAMAGE. |
||
31 |
* |
||
32 |
* Effort sponsored in part by the Defense Advanced Research Projects |
||
33 |
* Agency (DARPA) and Air Force Research Laboratory, Air Force |
||
34 |
* Materiel Command, USAF, under agreement number F30602-01-2-0537. |
||
35 |
* |
||
36 |
*/ |
||
37 |
|||
38 |
#include <sys/param.h> |
||
39 |
#include <sys/socket.h> |
||
40 |
#ifdef _KERNEL |
||
41 |
#include <sys/systm.h> |
||
42 |
#include <sys/mbuf.h> |
||
43 |
#endif /* _KERNEL */ |
||
44 |
#include <sys/syslog.h> |
||
45 |
|||
46 |
#include <netinet/in.h> |
||
47 |
#include <netinet/ip.h> |
||
48 |
#include <netinet/tcp.h> |
||
49 |
|||
50 |
#include <net/if.h> |
||
51 |
#include <net/pfvar.h> |
||
52 |
|||
53 |
#ifdef INET6 |
||
54 |
#include <netinet/ip6.h> |
||
55 |
#endif /* INET6 */ |
||
56 |
|||
57 |
|||
58 |
#ifdef _KERNEL |
||
59 |
#define rs_malloc(x) malloc(x, M_TEMP, M_WAITOK|M_CANFAIL|M_ZERO) |
||
60 |
#define rs_free(x, siz) free(x, M_TEMP, siz) |
||
61 |
|||
62 |
#else /* !_KERNEL */ |
||
63 |
/* Userland equivalents so we can lend code to pfctl et al. */ |
||
64 |
|||
65 |
#include <arpa/inet.h> |
||
66 |
#include <errno.h> |
||
67 |
#include <stdio.h> |
||
68 |
#include <stdlib.h> |
||
69 |
#include <string.h> |
||
70 |
#define rs_malloc(x) calloc(1, x) |
||
71 |
#define rs_free(x, siz) freezero(x, siz) |
||
72 |
|||
73 |
#ifdef PFDEBUG |
||
74 |
#include <sys/stdarg.h> /* for DPFPRINTF() */ |
||
75 |
#endif /* PFDEBUG */ |
||
76 |
#endif /* _KERNEL */ |
||
77 |
|||
78 |
|||
79 |
struct pf_anchor_global pf_anchors; |
||
80 |
struct pf_anchor pf_main_anchor; |
||
81 |
|||
82 |
static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *); |
||
83 |
|||
84 |
✓✓✓✓ ✓✓✓✓ ✓✗✓✗ ✗✓✓✗ ✗✓✗✓ ✓✓✓✓ ✓✓✗✓ ✓✗✓✓ ✓✓✓✓ ✗✓✓✗ ✗✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✓✗✗ ✗✗✗✓ ✗✗✓✗ ✗✓✗✓ ✗✗✓✗ ✗✓✓✗ ✓✗✗✓ ✓✓✓✗ ✓✓✓✓ ✗✓✗✓ ✓✗✓✗ ✗✓✗✓ ✓✗✗✓ ✓✗✓✓ ✓✓✓✓ ✓✓✓✓ ✓✗✓✓ ✓✓✓✓ ✓✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ |
12109 |
RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare); |
85 |
✓✓✓✓ ✗✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✓✗✗ ✗✓✗✗ ✗✗✗✓ ✗✓✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✓✓✓✓ ✓✗✓✓ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ |
1001 |
RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare); |
86 |
|||
87 |
static __inline int |
||
88 |
pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b) |
||
89 |
{ |
||
90 |
3022 |
int c = strcmp(a->path, b->path); |
|
91 |
|||
92 |
✓✓ | 4461 |
return (c ? (c < 0 ? -1 : 1) : 0); |
93 |
} |
||
94 |
|||
95 |
void |
||
96 |
pf_init_ruleset(struct pf_ruleset *ruleset) |
||
97 |
{ |
||
98 |
2252 |
memset(ruleset, 0, sizeof(struct pf_ruleset)); |
|
99 |
1126 |
TAILQ_INIT(&ruleset->rules.queues[0]); |
|
100 |
1126 |
TAILQ_INIT(&ruleset->rules.queues[1]); |
|
101 |
1126 |
ruleset->rules.active.ptr = &ruleset->rules.queues[0]; |
|
102 |
1126 |
ruleset->rules.inactive.ptr = &ruleset->rules.queues[1]; |
|
103 |
1126 |
} |
|
104 |
|||
105 |
struct pf_anchor * |
||
106 |
pf_find_anchor(const char *path) |
||
107 |
{ |
||
108 |
struct pf_anchor *key, *found; |
||
109 |
|||
110 |
808 |
key = rs_malloc(sizeof(*key)); |
|
111 |
✗✓ | 404 |
if (key == NULL) |
112 |
return (NULL); |
||
113 |
404 |
strlcpy(key->path, path, sizeof(key->path)); |
|
114 |
404 |
found = RB_FIND(pf_anchor_global, &pf_anchors, key); |
|
115 |
404 |
rs_free(key, sizeof(*key)); |
|
116 |
404 |
return (found); |
|
117 |
404 |
} |
|
118 |
|||
119 |
struct pf_ruleset * |
||
120 |
pf_find_ruleset(const char *path) |
||
121 |
{ |
||
122 |
struct pf_anchor *anchor; |
||
123 |
|||
124 |
✗✓ | 1212 |
while (*path == '/') |
125 |
path++; |
||
126 |
✗✓ | 404 |
if (!*path) |
127 |
return (&pf_main_ruleset); |
||
128 |
404 |
anchor = pf_find_anchor(path); |
|
129 |
✓✓ | 404 |
if (anchor == NULL) |
130 |
332 |
return (NULL); |
|
131 |
else |
||
132 |
72 |
return (&anchor->ruleset); |
|
133 |
404 |
} |
|
134 |
|||
135 |
struct pf_ruleset * |
||
136 |
pf_get_leaf_ruleset(char *path, char **path_remainder) |
||
137 |
{ |
||
138 |
struct pf_ruleset *ruleset; |
||
139 |
char *leaf, *p; |
||
140 |
int i = 0; |
||
141 |
|||
142 |
p = path; |
||
143 |
✗✓ | 486 |
while (*p == '/') |
144 |
p++; |
||
145 |
|||
146 |
162 |
ruleset = pf_find_ruleset(p); |
|
147 |
leaf = p; |
||
148 |
✓✓ | 664 |
while (ruleset == NULL) { |
149 |
170 |
leaf = strrchr(p, '/'); |
|
150 |
✓✓ | 170 |
if (leaf != NULL) { |
151 |
76 |
*leaf = '\0'; |
|
152 |
76 |
i++; |
|
153 |
76 |
ruleset = pf_find_ruleset(p); |
|
154 |
76 |
} else { |
|
155 |
leaf = path; |
||
156 |
/* |
||
157 |
* if no path component exists, then main ruleset is |
||
158 |
* our parent. |
||
159 |
*/ |
||
160 |
ruleset = &pf_main_ruleset; |
||
161 |
} |
||
162 |
} |
||
163 |
|||
164 |
✓✗ | 162 |
if (path_remainder != NULL) |
165 |
162 |
*path_remainder = leaf; |
|
166 |
|||
167 |
/* restore slashes in path. */ |
||
168 |
✓✓ | 476 |
while (i != 0) { |
169 |
✓✓ | 266 |
while (*leaf != '\0') |
170 |
57 |
leaf++; |
|
171 |
76 |
*leaf = '/'; |
|
172 |
76 |
i--; |
|
173 |
} |
||
174 |
|||
175 |
162 |
return (ruleset); |
|
176 |
} |
||
177 |
|||
178 |
struct pf_anchor * |
||
179 |
pf_create_anchor(struct pf_anchor *parent, const char *aname) |
||
180 |
{ |
||
181 |
struct pf_anchor *anchor, *dup; |
||
182 |
|||
183 |
✓✗✓✗ ✗✓ |
586 |
if (!*aname || (strlen(aname) >= PF_ANCHOR_NAME_SIZE) || |
184 |
✓✓ | 246 |
((parent != NULL) && (strlen(parent->path) >= PF_ANCHOR_MAXPATH))) |
185 |
return (NULL); |
||
186 |
|||
187 |
170 |
anchor = rs_malloc(sizeof(*anchor)); |
|
188 |
✗✓ | 170 |
if (anchor == NULL) |
189 |
return (NULL); |
||
190 |
|||
191 |
170 |
RB_INIT(&anchor->children); |
|
192 |
170 |
strlcpy(anchor->name, aname, sizeof(anchor->name)); |
|
193 |
✓✓ | 170 |
if (parent != NULL) { |
194 |
/* |
||
195 |
* Make sure path for levels 2, 3, ... is terminated by '/': |
||
196 |
* 1/2/3/... |
||
197 |
*/ |
||
198 |
76 |
strlcpy(anchor->path, parent->path, sizeof(anchor->path)); |
|
199 |
76 |
strlcat(anchor->path, "/", sizeof(anchor->path)); |
|
200 |
76 |
} |
|
201 |
170 |
strlcat(anchor->path, anchor->name, sizeof(anchor->path)); |
|
202 |
|||
203 |
✗✓ | 170 |
if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != NULL) { |
204 |
DPFPRINTF(LOG_NOTICE, |
||
205 |
"%s: RB_INSERT to global '%s' '%s' collides with '%s' '%s'", |
||
206 |
__func__, anchor->path, anchor->name, dup->path, dup->name); |
||
207 |
rs_free(anchor, sizeof(*anchor)); |
||
208 |
return (NULL); |
||
209 |
} |
||
210 |
|||
211 |
✓✓ | 170 |
if (parent != NULL) { |
212 |
76 |
anchor->parent = parent; |
|
213 |
76 |
dup = RB_INSERT(pf_anchor_node, &parent->children, anchor); |
|
214 |
✗✓ | 76 |
if (dup != NULL) { |
215 |
DPFPRINTF(LOG_NOTICE, |
||
216 |
"%s: RB_INSERT to parent '%s' '%s' collides with " |
||
217 |
"'%s' '%s'", __func__, anchor->path, anchor->name, |
||
218 |
dup->path, dup->name); |
||
219 |
RB_REMOVE(pf_anchor_global, &pf_anchors, |
||
220 |
anchor); |
||
221 |
rs_free(anchor, sizeof(*anchor)); |
||
222 |
return (NULL); |
||
223 |
} |
||
224 |
} |
||
225 |
|||
226 |
170 |
pf_init_ruleset(&anchor->ruleset); |
|
227 |
170 |
anchor->ruleset.anchor = anchor; |
|
228 |
|||
229 |
170 |
return (anchor); |
|
230 |
170 |
} |
|
231 |
|||
232 |
struct pf_ruleset * |
||
233 |
pf_find_or_create_ruleset(const char *path) |
||
234 |
{ |
||
235 |
332 |
char *p, *aname, *r; |
|
236 |
struct pf_ruleset *ruleset; |
||
237 |
struct pf_anchor *anchor; |
||
238 |
|||
239 |
✗✓ | 166 |
if (path[0] == 0) |
240 |
return (&pf_main_ruleset); |
||
241 |
|||
242 |
✗✓ | 332 |
while (*path == '/') |
243 |
path++; |
||
244 |
|||
245 |
166 |
ruleset = pf_find_ruleset(path); |
|
246 |
✓✓ | 166 |
if (ruleset != NULL) |
247 |
4 |
return (ruleset); |
|
248 |
|||
249 |
162 |
p = rs_malloc(MAXPATHLEN); |
|
250 |
✗✓ | 162 |
if (p == NULL) |
251 |
return (NULL); |
||
252 |
162 |
strlcpy(p, path, MAXPATHLEN); |
|
253 |
|||
254 |
162 |
ruleset = pf_get_leaf_ruleset(p, &aname); |
|
255 |
162 |
anchor = ruleset->anchor; |
|
256 |
|||
257 |
✓✓ | 460 |
while (*aname == '/') |
258 |
68 |
aname++; |
|
259 |
/* |
||
260 |
* aname is a path remainder, which contains nodes we must create. We |
||
261 |
* process the aname path from left to right, effectively descending |
||
262 |
* from parents to children. |
||
263 |
*/ |
||
264 |
✓✓✓✗ |
502 |
while ((r = strchr(aname, '/')) != NULL || *aname) { |
265 |
✓✓ | 170 |
if (r != NULL) |
266 |
8 |
*r = 0; |
|
267 |
|||
268 |
170 |
anchor = pf_create_anchor(anchor, aname); |
|
269 |
✗✓ | 170 |
if (anchor == NULL) { |
270 |
rs_free(p, MAXPATHLEN); |
||
271 |
return (NULL); |
||
272 |
} |
||
273 |
|||
274 |
✓✓ | 170 |
if (r == NULL) |
275 |
break; |
||
276 |
else |
||
277 |
8 |
aname = r + 1; |
|
278 |
} |
||
279 |
|||
280 |
162 |
rs_free(p, MAXPATHLEN); |
|
281 |
162 |
return (&anchor->ruleset); |
|
282 |
166 |
} |
|
283 |
|||
284 |
void |
||
285 |
pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset) |
||
286 |
{ |
||
287 |
struct pf_anchor *parent; |
||
288 |
|||
289 |
✓✗ | 249 |
while (ruleset != NULL) { |
290 |
✓✗✓✗ ✗✓ |
197 |
if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL || |
291 |
✓✓ | 83 |
!RB_EMPTY(&ruleset->anchor->children) || |
292 |
✓✗✓✗ |
62 |
ruleset->anchor->refcnt > 0 || ruleset->tables > 0 || |
293 |
31 |
ruleset->topen) |
|
294 |
52 |
return; |
|
295 |
✓✗✗✓ |
62 |
if (!TAILQ_EMPTY(ruleset->rules.active.ptr) || |
296 |
✓✗ | 31 |
!TAILQ_EMPTY(ruleset->rules.inactive.ptr) || |
297 |
31 |
ruleset->rules.inactive.open) |
|
298 |
return; |
||
299 |
31 |
RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor); |
|
300 |
✗✓ | 31 |
if ((parent = ruleset->anchor->parent) != NULL) |
301 |
RB_REMOVE(pf_anchor_node, &parent->children, |
||
302 |
ruleset->anchor); |
||
303 |
31 |
rs_free(ruleset->anchor, sizeof(*(ruleset->anchor))); |
|
304 |
✓✗ | 31 |
if (parent == NULL) |
305 |
31 |
return; |
|
306 |
ruleset = &parent->ruleset; |
||
307 |
} |
||
308 |
83 |
} |
|
309 |
|||
310 |
int |
||
311 |
pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s, |
||
312 |
const char *name) |
||
313 |
{ |
||
314 |
char *p, *path; |
||
315 |
struct pf_ruleset *ruleset; |
||
316 |
|||
317 |
166 |
r->anchor = NULL; |
|
318 |
83 |
r->anchor_relative = 0; |
|
319 |
83 |
r->anchor_wildcard = 0; |
|
320 |
✗✓ | 83 |
if (!name[0]) |
321 |
return (0); |
||
322 |
83 |
path = rs_malloc(MAXPATHLEN); |
|
323 |
✗✓ | 83 |
if (path == NULL) |
324 |
return (1); |
||
325 |
✗✓ | 83 |
if (name[0] == '/') |
326 |
strlcpy(path, name + 1, MAXPATHLEN); |
||
327 |
else { |
||
328 |
/* relative path */ |
||
329 |
83 |
r->anchor_relative = 1; |
|
330 |
✓✗✓✓ |
166 |
if (s->anchor == NULL || !s->anchor->path[0]) |
331 |
8 |
path[0] = 0; |
|
332 |
else |
||
333 |
75 |
strlcpy(path, s->anchor->path, MAXPATHLEN); |
|
334 |
✗✓✗✗ ✗✓ |
249 |
while (name[0] == '.' && name[1] == '.' && name[2] == '/') { |
335 |
if (!path[0]) { |
||
336 |
DPFPRINTF(LOG_NOTICE, |
||
337 |
"pf_anchor_setup: .. beyond root"); |
||
338 |
rs_free(path, MAXPATHLEN); |
||
339 |
return (1); |
||
340 |
} |
||
341 |
if ((p = strrchr(path, '/')) != NULL) |
||
342 |
*p = 0; |
||
343 |
else |
||
344 |
path[0] = 0; |
||
345 |
r->anchor_relative++; |
||
346 |
name += 3; |
||
347 |
} |
||
348 |
✓✓ | 83 |
if (path[0]) |
349 |
75 |
strlcat(path, "/", MAXPATHLEN); |
|
350 |
83 |
strlcat(path, name, MAXPATHLEN); |
|
351 |
} |
||
352 |
✓✓✗✓ |
158 |
if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { |
353 |
r->anchor_wildcard = 1; |
||
354 |
*p = 0; |
||
355 |
} |
||
356 |
83 |
ruleset = pf_find_or_create_ruleset(path); |
|
357 |
83 |
rs_free(path, MAXPATHLEN); |
|
358 |
✓✗✗✓ |
166 |
if (ruleset == NULL || ruleset->anchor == NULL) { |
359 |
DPFPRINTF(LOG_NOTICE, |
||
360 |
"pf_anchor_setup: ruleset"); |
||
361 |
return (1); |
||
362 |
} |
||
363 |
83 |
r->anchor = ruleset->anchor; |
|
364 |
83 |
r->anchor->refcnt++; |
|
365 |
83 |
return (0); |
|
366 |
83 |
} |
|
367 |
|||
368 |
int |
||
369 |
pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r, |
||
370 |
struct pfioc_rule *pr) |
||
371 |
{ |
||
372 |
pr->anchor_call[0] = 0; |
||
373 |
if (r->anchor == NULL) |
||
374 |
return (0); |
||
375 |
if (!r->anchor_relative) { |
||
376 |
strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call)); |
||
377 |
strlcat(pr->anchor_call, r->anchor->path, |
||
378 |
sizeof(pr->anchor_call)); |
||
379 |
} else { |
||
380 |
char *a, *p; |
||
381 |
int i; |
||
382 |
|||
383 |
a = rs_malloc(MAXPATHLEN); |
||
384 |
if (a == NULL) |
||
385 |
return (1); |
||
386 |
if (rs->anchor == NULL) |
||
387 |
a[0] = 0; |
||
388 |
else |
||
389 |
strlcpy(a, rs->anchor->path, MAXPATHLEN); |
||
390 |
for (i = 1; i < r->anchor_relative; ++i) { |
||
391 |
if ((p = strrchr(a, '/')) == NULL) |
||
392 |
p = a; |
||
393 |
*p = 0; |
||
394 |
strlcat(pr->anchor_call, "../", |
||
395 |
sizeof(pr->anchor_call)); |
||
396 |
} |
||
397 |
if (strncmp(a, r->anchor->path, strlen(a))) { |
||
398 |
DPFPRINTF(LOG_NOTICE, |
||
399 |
"pf_anchor_copyout: '%s' '%s'", a, |
||
400 |
r->anchor->path); |
||
401 |
rs_free(a, MAXPATHLEN); |
||
402 |
return (1); |
||
403 |
} |
||
404 |
if (strlen(r->anchor->path) > strlen(a)) |
||
405 |
strlcat(pr->anchor_call, r->anchor->path + (a[0] ? |
||
406 |
strlen(a) + 1 : 0), sizeof(pr->anchor_call)); |
||
407 |
rs_free(a, MAXPATHLEN); |
||
408 |
} |
||
409 |
if (r->anchor_wildcard) |
||
410 |
strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*", |
||
411 |
sizeof(pr->anchor_call)); |
||
412 |
return (0); |
||
413 |
} |
||
414 |
|||
415 |
void |
||
416 |
pf_anchor_remove(struct pf_rule *r) |
||
417 |
{ |
||
418 |
if (r->anchor == NULL) |
||
419 |
return; |
||
420 |
if (r->anchor->refcnt <= 0) { |
||
421 |
DPFPRINTF(LOG_NOTICE, |
||
422 |
"pf_anchor_remove: broken refcount"); |
||
423 |
r->anchor = NULL; |
||
424 |
return; |
||
425 |
} |
||
426 |
if (!--r->anchor->refcnt) |
||
427 |
pf_remove_if_empty_ruleset(&r->anchor->ruleset); |
||
428 |
r->anchor = NULL; |
||
429 |
} |
Generated by: GCOVR (Version 3.3) |