cidr_match.c revision 1.2
1/* $NetBSD: cidr_match.c,v 1.2 2017/02/14 01:16:49 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, 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/* DESCRIPTION 20/* This module parses address or address/length patterns and 21/* provides simple address matching. The implementation is 22/* such that parsing and execution can be done without dynamic 23/* memory allocation. The purpose is to minimize overhead when 24/* called by functions that parse and execute on the fly, such 25/* as match_hostaddr(). 26/* 27/* cidr_match_parse() parses an address or address/mask 28/* expression and stores the result into the info argument. 29/* The result is non-zero in case of problems: either the 30/* value of the why argument, or a newly allocated VSTRING 31/* (the caller should give the latter to vstring_free()). 32/* The pattern argument is destroyed. 33/* 34/* cidr_match_execute() matches the specified address against 35/* a list of parsed expressions, and returns the matching 36/* expression's data structure. 37/* SEE ALSO 38/* dict_cidr(3) CIDR-style lookup table 39/* AUTHOR(S) 40/* Wietse Venema 41/* IBM T.J. Watson Research 42/* P.O. Box 704 43/* Yorktown Heights, NY 10598, USA 44/*--*/ 45 46/* System library. */ 47 48#include <sys_defs.h> 49#include <stdlib.h> 50#include <unistd.h> 51#include <string.h> 52#include <ctype.h> 53#include <sys/socket.h> 54#include <netinet/in.h> 55#include <arpa/inet.h> 56 57/* Utility library. */ 58 59#include <msg.h> 60#include <vstring.h> 61#include <stringops.h> 62#include <split_at.h> 63#include <myaddrinfo.h> 64#include <mask_addr.h> 65#include <cidr_match.h> 66 67/* Application-specific. */ 68 69 /* 70 * This is how we figure out the address family, address bit count and 71 * address byte count for a CIDR_MATCH entry. 72 */ 73#ifdef HAS_IPV6 74#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET) 75#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ 76 ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \ 77 (f) == AF_INET ? MAI_V4ADDR_BITS : \ 78 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 79#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ 80 ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \ 81 (f) == AF_INET ? MAI_V4ADDR_BYTES : \ 82 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 83#else 84#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET) 85#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ 86 ((f) == AF_INET ? MAI_V4ADDR_BITS : \ 87 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 88#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ 89 ((f) == AF_INET ? MAI_V4ADDR_BYTES : \ 90 (msg_panic("%s: bad address family %d", myname, (f)), 0)) 91#endif 92 93/* cidr_match_execute - match address against compiled CIDR pattern list */ 94 95CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) 96{ 97 unsigned char addr_bytes[CIDR_MATCH_ABYTES]; 98 unsigned addr_family; 99 unsigned char *mp; 100 unsigned char *np; 101 unsigned char *ap; 102 CIDR_MATCH *entry; 103 104 addr_family = CIDR_MATCH_ADDR_FAMILY(addr); 105 if (inet_pton(addr_family, addr, addr_bytes) != 1) 106 return (0); 107 108 for (entry = list; entry; entry = entry->next) { 109 if (entry->addr_family == addr_family) { 110 /* Unoptimized case: netmask with some or all bits zero. */ 111 if (entry->mask_shift < entry->addr_bit_count) { 112 for (np = entry->net_bytes, mp = entry->mask_bytes, 113 ap = addr_bytes; /* void */ ; np++, mp++, ap++) { 114 if (ap >= addr_bytes + entry->addr_byte_count) 115 return (entry); 116 if ((*ap & *mp) != *np) 117 break; 118 } 119 } 120 /* Optimized case: all 1 netmask (i.e. no netmask specified). */ 121 else { 122 for (np = entry->net_bytes, 123 ap = addr_bytes; /* void */ ; np++, ap++) { 124 if (ap >= addr_bytes + entry->addr_byte_count) 125 return (entry); 126 if (*ap != *np) 127 break; 128 } 129 } 130 } 131 } 132 return (0); 133} 134 135/* cidr_match_parse - parse CIDR pattern */ 136 137VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, VSTRING *why) 138{ 139 const char *myname = "cidr_match_parse"; 140 char *mask_search; 141 char *mask; 142 MAI_HOSTADDR_STR hostaddr; 143 unsigned char *np; 144 unsigned char *mp; 145 146 /* 147 * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR 148 * maps don't need [] to eliminate syntax ambiguity, but matchlists need 149 * it. While stripping [], figure out where we should start looking for 150 * /mask information. 151 */ 152 if (*pattern == '[') { 153 pattern++; 154 if ((mask_search = split_at(pattern, ']')) == 0) { 155 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 156 "missing ']' character after \"[%s\"", pattern); 157 return (why); 158 } else if (*mask_search != '/') { 159 if (*mask_search != 0) { 160 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 161 "garbage after \"[%s]\"", pattern); 162 return (why); 163 } 164 mask_search = pattern; 165 } 166 } else 167 mask_search = pattern; 168 169 /* 170 * Parse the pattern into network and mask, destroying the pattern. 171 */ 172 if ((mask = split_at(mask_search, '/')) != 0) { 173 ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); 174 ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); 175 ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); 176 if (!alldig(mask) 177 || (ip->mask_shift = atoi(mask)) > ip->addr_bit_count 178 || inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { 179 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 180 "bad net/mask pattern: \"%s/%s\"", pattern, mask); 181 return (why); 182 } 183 if (ip->mask_shift > 0) { 184 /* Allow for bytes > 8. */ 185 memset(ip->mask_bytes, ~0U, ip->addr_byte_count); 186 mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift); 187 } else 188 memset(ip->mask_bytes, 0, ip->addr_byte_count); 189 190 /* 191 * Sanity check: all host address bits must be zero. 192 */ 193 for (np = ip->net_bytes, mp = ip->mask_bytes; 194 np < ip->net_bytes + ip->addr_byte_count; np++, mp++) { 195 if (*np & ~(*mp)) { 196 mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift); 197 if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf, 198 sizeof(hostaddr.buf)) == 0) 199 msg_fatal("inet_ntop: %m"); 200 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 201 "non-null host address bits in \"%s/%s\", " 202 "perhaps you should use \"%s/%d\" instead", 203 pattern, mask, hostaddr.buf, ip->mask_shift); 204 return (why); 205 } 206 } 207 } 208 209 /* 210 * No /mask specified. Treat a bare network address as /allbits. 211 */ 212 else { 213 ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); 214 ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); 215 ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); 216 if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { 217 vstring_sprintf(why ? why : (why = vstring_alloc(20)), 218 "bad address pattern: \"%s\"", pattern); 219 return (why); 220 } 221 ip->mask_shift = ip->addr_bit_count; 222 /* Allow for bytes > 8. */ 223 memset(ip->mask_bytes, ~0U, ip->addr_byte_count); 224 } 225 226 /* 227 * Wrap up the result. 228 */ 229 ip->next = 0; 230 231 return (0); 232} 233