cidr_match.c revision 1.3
1/* $NetBSD: cidr_match.c,v 1.3 2020/03/18 19:05:21 christos Exp $ */ 2 3/*++ 4/* NAME 5/* cidr_match 3 6/* SUMMARY 7/* CIDR-style pattern matching 8/* SYNOPSIS 9/* #include <cidr_match.h> 10/* 11/* VSTRING *cidr_match_parse(info, pattern, match, why) 12/* CIDR_MATCH *info; 13/* char *pattern; 14/* VSTRING *why; 15/* 16/* int cidr_match_execute(info, address) 17/* CIDR_MATCH *info; 18/* const char *address; 19/* AUXILIARY FUNCTIONS 20/* VSTRING *cidr_match_parse_if(info, pattern, match, why) 21/* CIDR_MATCH *info; 22/* char *pattern; 23/* VSTRING *why; 24/* 25/* void cidr_match_endif(info) 26/* CIDR_MATCH *info; 27/* DESCRIPTION 28/* This module parses address or address/length patterns and 29/* provides simple address matching. The implementation is 30/* such that parsing and execution can be done without dynamic 31/* memory allocation. The purpose is to minimize overhead when 32/* called by functions that parse and execute on the fly, such 33/* as match_hostaddr(). 34/* 35/* cidr_match_parse() parses an address or address/mask 36/* expression and stores the result into the info argument. 37/* A non-zero (or zero) match argument requests a positive (or 38/* negative) match. The symbolic constants CIDR_MATCH_TRUE and 39/* CIDR_MATCH_FALSE may help to improve code readability. 40/* The result is non-zero in case of problems: either the 41/* value of the why argument, or a newly allocated VSTRING 42/* (the caller should give the latter to vstring_free()). 43/* The pattern argument is destroyed. 44/* 45/* cidr_match_parse_if() parses the address that follows an IF 46/* token, and stores the result into the info argument. 47/* The arguments are the same as for cidr_match_parse(). 48/* 49/* cidr_match_endif() handles the occurrence of an ENDIF token, 50/* and updates the info argument. 51/* 52/* cidr_match_execute() matches the specified address against 53/* a list of parsed expressions, and returns the matching 54/* expression's data structure. 55/* SEE ALSO 56/* dict_cidr(3) CIDR-style lookup table 57/* AUTHOR(S) 58/* Wietse Venema 59/* IBM T.J. Watson Research 60/* P.O. Box 704 61/* Yorktown Heights, NY 10598, USA 62/* 63/* Wietse Venema 64/* Google, Inc. 65/* 111 8th Avenue 66/* New York, NY 10011, USA 67/*--*/ 68 69/* System library. */ 70 71#include <sys_defs.h> 72#include <stdlib.h> 73#include <unistd.h> 74#include <string.h> 75#include <ctype.h> 76#include <sys/socket.h> 77#include <netinet/in.h> 78#include <arpa/inet.h> 79 80/* Utility library. */ 81 82#include <msg.h> 83#include <vstring.h> 84#include <stringops.h> 85#include <split_at.h> 86#include <myaddrinfo.h> 87#include <mask_addr.h> 88#include <cidr_match.h> 89 90/* Application-specific. */ 91 92 /* 93 * This is how we figure out the address family, address bit count and 94 * address byte count for a CIDR_MATCH entry. 95 */ 96#ifdef HAS_IPV6 97#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET) 98#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ 99 ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \ 100 (f) == AF_INET ? MAI_V4ADDR_BITS : \ 101 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 102#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ 103 ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \ 104 (f) == AF_INET ? MAI_V4ADDR_BYTES : \ 105 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 106#else 107#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET) 108#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ 109 ((f) == AF_INET ? MAI_V4ADDR_BITS : \ 110 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 111#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ 112 ((f) == AF_INET ? MAI_V4ADDR_BYTES : \ 113 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 114#endif 115 116/* cidr_match_entry - match one entry */ 117 118static inline int cidr_match_entry(CIDR_MATCH *entry, 119 unsigned char *addr_bytes) 120{ 121 unsigned char *mp; 122 unsigned char *np; 123 unsigned char *ap; 124 125 /* Unoptimized case: netmask with some or all bits zero. */ 126 if (entry->mask_shift < entry->addr_bit_count) { 127 for (np = entry->net_bytes, mp = entry->mask_bytes, 128 ap = addr_bytes; /* void */ ; np++, mp++, ap++) { 129 if (ap >= addr_bytes + entry->addr_byte_count) 130 return (entry->match); 131 if ((*ap & *mp) != *np) 132 break; 133 } 134 } 135 /* Optimized case: all 1 netmask (i.e. no netmask specified). */ 136 else { 137 for (np = entry->net_bytes, 138 ap = addr_bytes; /* void */ ; np++, ap++) { 139 if (ap >= addr_bytes + entry->addr_byte_count) 140 return (entry->match); 141 if (*ap != *np) 142 break; 143 } 144 } 145 return (!entry->match); 146} 147 148/* cidr_match_execute - match address against compiled CIDR pattern list */ 149 150CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) 151{ 152 unsigned char addr_bytes[CIDR_MATCH_ABYTES]; 153 unsigned addr_family; 154 CIDR_MATCH *entry; 155 156 addr_family = CIDR_MATCH_ADDR_FAMILY(addr); 157 if (inet_pton(addr_family, addr, addr_bytes) != 1) 158 return (0); 159 160 for (entry = list; entry; entry = entry->next) { 161 162 switch (entry->op) { 163 164 case CIDR_MATCH_OP_MATCH: 165 if (entry->addr_family == addr_family) 166 if (cidr_match_entry(entry, addr_bytes)) 167 return (entry); 168 break; 169 170 case CIDR_MATCH_OP_IF: 171 if (entry->addr_family == addr_family) 172 if (cidr_match_entry(entry, addr_bytes)) 173 continue; 174 /* An IF without matching ENDIF has no end-of block entry. */ 175 if ((entry = entry->block_end) == 0) 176 return (0); 177 /* FALLTHROUGH */ 178 179 case CIDR_MATCH_OP_ENDIF: 180 continue; 181 } 182 } 183 return (0); 184} 185 186/* cidr_match_parse - parse CIDR pattern */ 187 188VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match, 189 VSTRING *why) 190{ 191 const char *myname = "cidr_match_parse"; 192 char *mask_search; 193 char *mask; 194 MAI_HOSTADDR_STR hostaddr; 195 unsigned char *np; 196 unsigned char *mp; 197 198 /* 199 * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR 200 * maps don't need [] to eliminate syntax ambiguity, but matchlists need 201 * it. While stripping [], figure out where we should start looking for 202 * /mask information. 203 */ 204 if (*pattern == '[') { 205 pattern++; 206 if ((mask_search = split_at(pattern, ']')) == 0) { 207 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 208 "missing ']' character after \"[%s\"", pattern); 209 return (why); 210 } else if (*mask_search != '/') { 211 if (*mask_search != 0) { 212 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 213 "garbage after \"[%s]\"", pattern); 214 return (why); 215 } 216 mask_search = pattern; 217 } 218 } else 219 mask_search = pattern; 220 221 /* 222 * Parse the pattern into network and mask, destroying the pattern. 223 */ 224 if ((mask = split_at(mask_search, '/')) != 0) { 225 ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); 226 ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); 227 ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); 228 if (!alldig(mask) 229 || (ip->mask_shift = atoi(mask)) > ip->addr_bit_count 230 || inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { 231 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 232 "bad net/mask pattern: \"%s/%s\"", pattern, mask); 233 return (why); 234 } 235 if (ip->mask_shift > 0) { 236 /* Allow for bytes > 8. */ 237 memset(ip->mask_bytes, ~0U, ip->addr_byte_count); 238 mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift); 239 } else 240 memset(ip->mask_bytes, 0, ip->addr_byte_count); 241 242 /* 243 * Sanity check: all host address bits must be zero. 244 */ 245 for (np = ip->net_bytes, mp = ip->mask_bytes; 246 np < ip->net_bytes + ip->addr_byte_count; np++, mp++) { 247 if (*np & ~(*mp)) { 248 mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift); 249 if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf, 250 sizeof(hostaddr.buf)) == 0) 251 msg_fatal("inet_ntop: %m"); 252 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 253 "non-null host address bits in \"%s/%s\", " 254 "perhaps you should use \"%s/%d\" instead", 255 pattern, mask, hostaddr.buf, ip->mask_shift); 256 return (why); 257 } 258 } 259 } 260 261 /* 262 * No /mask specified. Treat a bare network address as /allbits. 263 */ 264 else { 265 ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); 266 ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); 267 ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); 268 if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { 269 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 270 "bad address pattern: \"%s\"", pattern); 271 return (why); 272 } 273 ip->mask_shift = ip->addr_bit_count; 274 /* Allow for bytes > 8. */ 275 memset(ip->mask_bytes, ~0U, ip->addr_byte_count); 276 } 277 278 /* 279 * Wrap up the result. 280 */ 281 ip->op = CIDR_MATCH_OP_MATCH; 282 ip->match = match; 283 ip->next = 0; 284 ip->block_end = 0; 285 286 return (0); 287} 288 289/* cidr_match_parse_if - parse CIDR pattern after IF */ 290 291VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match, 292 VSTRING *why) 293{ 294 VSTRING *ret; 295 296 if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0) 297 ip->op = CIDR_MATCH_OP_IF; 298 return (ret); 299} 300 301/* cidr_match_endif - handle ENDIF pattern */ 302 303void cidr_match_endif(CIDR_MATCH *ip) 304{ 305 memset(ip, 0, sizeof(*ip)); 306 ip->op = CIDR_MATCH_OP_ENDIF; 307 ip->next = 0; /* maybe not all bits 0 */ 308 ip->block_end = 0; 309} 310