1/* 2 * TRLocalPacketFilter.m vi:ts=4:sw=4:expandtab: 3 * Interface to local OpenBSD /dev/pf 4 * 5 * Author: Landon Fuller <landonf@threerings.net> 6 * 7 * Copyright (c) 2006 - 2007 Three Rings Design, Inc. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the copyright holder nor the names of any contributors 19 * may be used to endorse or promote products derived from this 20 * software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35#import "TRLocalPacketFilter.h" 36 37#ifdef HAVE_PF 38 39#import <fcntl.h> 40#import <unistd.h> 41#import <stdlib.h> 42#import <string.h> 43#import <errno.h> 44#import <assert.h> 45 46#import "TRLog.h" 47#import "xmalloc.h" 48 49/* Private Methods */ 50 51@interface TRLocalPacketFilter (Private) 52+ (pferror_t) mapErrno; 53- (int) ioctl: (unsigned long) request withArgp: (void *) argp; 54- (BOOL) pfFromAddress: (TRPFAddress *) source pfaddr: (struct pfr_addr *) dest; 55- (TRPFAddress *) addressFromPF: (struct pfr_addr *) pfaddr; 56@end 57 58 59/** 60 * An interface to a local OpenBSD Packet Filter. 61 */ 62@implementation TRLocalPacketFilter 63 64/** 65 * Initialize a new instance. 66 */ 67- (id) init { 68 self = [super init]; 69 if (self == nil) 70 return self; 71 72 _fd = -1; 73 return self; 74} 75 76 77/** 78 * Open a reference to /dev/pf. Must be called before 79 * any other PF methods. 80 */ 81- (pferror_t) open { 82 /* Open a reference to /dev/pf */ 83 if ((_fd = open(PF_DEV_PATH, O_RDWR)) == -1) 84 return [TRLocalPacketFilter mapErrno]; 85 else 86 return PF_SUCCESS; 87} 88 89 90/** 91 * Close and release any open references to /dev/pf. 92 * This is called automatically when the object is released. 93 */ 94- (void) close { 95 if (_fd != -1) { 96 close(_fd); 97 _fd = -1; 98 } 99} 100 101 102- (void) dealloc { 103 [self close]; 104 [super dealloc]; 105} 106 107 108/** Return an array of table names */ 109- (pferror_t) tables: (TRArray **) result { 110 TRArray *tables = nil; 111 struct pfioc_table io; 112 struct pfr_table *table; 113 int size, i; 114 115 /* Initialize the io structure */ 116 memset(&io, 0, sizeof(io)); 117 io.pfrio_esize = sizeof(struct pfr_table); 118 119 /* First attempt with a reasonable buffer size - 32 tables */ 120 size = sizeof(struct pfr_table) * 32; 121 io.pfrio_buffer = xmalloc(size); 122 123 /* Loop until success. */ 124 while (1) { 125 io.pfrio_size = size; 126 if ([self ioctl: DIOCRGETTABLES withArgp: &io] == -1) { 127 pferror_t ret; 128 129 ret = [TRLocalPacketFilter mapErrno]; 130 free(io.pfrio_buffer); 131 *result = nil; 132 return ret; 133 } 134 135 /* Do we need a larger buffer? */ 136 if (io.pfrio_size > size) { 137 /* Allocate the suggested space */ 138 size = io.pfrio_size; 139 io.pfrio_buffer = xrealloc(io.pfrio_buffer, size); 140 } else { 141 /* Success! Exit the loop */ 142 break; 143 } 144 } 145 146 /* Iterate over the returned tables, building our array */ 147 tables = [[TRArray alloc] init]; 148 149 size = io.pfrio_size / sizeof(struct pfr_table); 150 table = (struct pfr_table *) io.pfrio_buffer; 151 for (i = 0; i < size; i++) { 152 TRString *name = [[TRString alloc] initWithCString: table->pfrt_name]; 153 [tables addObject: name]; 154 [name release]; 155 table++; 156 } 157 158 free(io.pfrio_buffer); 159 *result = [tables autorelease]; 160 return PF_SUCCESS; 161} 162 163 164/** 165 * Clear all addreses from the specified table. 166 */ 167- (pferror_t) flushTable: (TRString *) tableName { 168 struct pfioc_table io; 169 170 /* Validate name */ 171 if ([tableName length] > sizeof(io.pfrio_table.pfrt_name)) 172 return PF_ERROR_INVALID_NAME; 173 174 /* Initialize the io structure */ 175 memset(&io, 0, sizeof(io)); 176 177 /* Copy in the table name */ 178 strcpy(io.pfrio_table.pfrt_name, [tableName cString]); 179 180 /* Issue the ioctl */ 181 if ([self ioctl: DIOCRCLRADDRS withArgp: &io] == -1) { 182 return [TRLocalPacketFilter mapErrno]; 183 } 184 185 return PF_SUCCESS; 186} 187 188/** 189 * Add an address to the specified table. 190 */ 191- (pferror_t) addAddress: (TRPFAddress *) address toTable: (TRString *) tableName { 192 struct pfioc_table io; 193 struct pfr_addr addr; 194 195 /* Validate name */ 196 if ([tableName length] > sizeof(io.pfrio_table.pfrt_name)) 197 return PF_ERROR_INVALID_NAME; 198 199 /* Initialize the io structure */ 200 memset(&io, 0, sizeof(io)); 201 io.pfrio_esize = sizeof(struct pfr_addr); 202 203 /* Build the request */ 204 strcpy(io.pfrio_table.pfrt_name, [tableName cString]); 205 206 if ([self pfFromAddress: address pfaddr: &addr] != true) 207 return PF_ERROR_INTERNAL; 208 io.pfrio_buffer = &addr; 209 210 io.pfrio_size = 1; 211 212 /* Issue the ioctl */ 213 if ([self ioctl: DIOCRADDADDRS withArgp: &io] == -1) { 214 return [TRLocalPacketFilter mapErrno]; 215 } 216 217 if (io.pfrio_nadd != 1) { 218 return PF_ERROR_INTERNAL; 219 } 220 221 return PF_SUCCESS; 222} 223 224 225/** 226 * Delete an address from the specified table. 227 */ 228- (pferror_t) deleteAddress: (TRPFAddress *) address fromTable: (TRString *) tableName { 229 struct pfioc_table io; 230 struct pfr_addr addr; 231 232 /* Validate name */ 233 if ([tableName length] > sizeof(io.pfrio_table.pfrt_name)) 234 return PF_ERROR_INVALID_NAME; 235 236 /* Initialize the io structure */ 237 memset(&io, 0, sizeof(io)); 238 io.pfrio_esize = sizeof(struct pfr_addr); 239 240 /* Build the request */ 241 strcpy(io.pfrio_table.pfrt_name, [tableName cString]); 242 243 if ([self pfFromAddress: address pfaddr: &addr] != true) 244 return PF_ERROR_INTERNAL; 245 246 io.pfrio_buffer = &addr; 247 io.pfrio_size = 1; 248 249 /* Issue the ioctl */ 250 if ([self ioctl: DIOCRDELADDRS withArgp: &io] == -1) { 251 return [TRLocalPacketFilter mapErrno]; 252 } 253 254 if (io.pfrio_ndel != 1) { 255 return PF_ERROR_INTERNAL; 256 } 257 258 return PF_SUCCESS; 259} 260 261 262/** 263 * Return an array of all addresses from the specified table. 264 */ 265- (pferror_t) addressesFromTable: (TRString *) tableName withResult: (TRArray **) result { 266 TRArray *addresses = nil; 267 struct pfioc_table io; 268 struct pfr_addr *pfrAddr; 269 int size, i; 270 271 /* Validate name */ 272 if ([tableName length] > sizeof(io.pfrio_table.pfrt_name)) { 273 *result = nil; 274 return PF_ERROR_INVALID_NAME; 275 } 276 277 /* Initialize the io structure */ 278 memset(&io, 0, sizeof(io)); 279 io.pfrio_esize = sizeof(struct pfr_addr); 280 281 /* Copy in the table name */ 282 strcpy(io.pfrio_table.pfrt_name, [tableName cString]); 283 284 /* First attempt with a reasonable buffer size - 32 addresses */ 285 size = 32; 286 io.pfrio_buffer = xmalloc(size * sizeof(struct pfr_addr)); 287 288 /* Loop until success. */ 289 while (1) { 290 io.pfrio_size = size; 291 if ([self ioctl: DIOCRGETADDRS withArgp: &io] == -1) { 292 pferror_t ret; 293 294 ret = [TRLocalPacketFilter mapErrno]; 295 free(io.pfrio_buffer); 296 *result = nil; 297 return ret; 298 } 299 300 /* Do we need a larger buffer? */ 301 if (io.pfrio_size > size) { 302 /* Allocate the suggested space */ 303 size = io.pfrio_size; 304 io.pfrio_buffer = xrealloc(io.pfrio_buffer, size * sizeof(struct pfr_addr)); 305 } else { 306 /* Success! Exit the loop */ 307 break; 308 } 309 } 310 311 /* Iterate over the returned addresses, building our array */ 312 addresses = [[TRArray alloc] init]; 313 314 pfrAddr = (struct pfr_addr *) io.pfrio_buffer; 315 for (i = 0; i < io.pfrio_size; i++) { 316 TRPFAddress *address = [self addressFromPF: pfrAddr]; 317 [addresses addObject: address]; 318 [address release]; 319 pfrAddr++; 320 } 321 322 free(io.pfrio_buffer); 323 *result = [addresses autorelease]; 324 return PF_SUCCESS; 325} 326 327@end 328 329 330/* 331 * TRLocalPacketFilter Private Methods 332 */ 333@implementation TRLocalPacketFilter (Private) 334 335/** 336 * Map PF errno values to pferror_t values 337 */ 338+ (pferror_t) mapErrno { 339 switch (errno) { 340 case ESRCH: 341 /* Returned when a table, etc, is not found. 342 * "No such process" doesn't make much sense here. */ 343 return PF_ERROR_NOT_FOUND; 344 345 case EINVAL: 346 return PF_ERROR_INVALID_ARGUMENT; 347 348 case EPERM: 349 /* Returned when /dev/pf can't be opened, and? */ 350 return PF_ERROR_PERMISSION; 351 352 default: 353 return PF_ERROR_UNKNOWN; 354 break; 355 } 356} 357 358/* ioctl() with an extra seat-belt. */ 359- (int) ioctl: (unsigned long) request withArgp: (void *) argp { 360 assert(_fd >= 0); 361 return ioctl(_fd, request, argp); 362} 363 364 365/** 366 * Create a new TRPFAddress address with the provided pfr_addr structure. 367 */ 368- (TRPFAddress *) addressFromPF: (struct pfr_addr *) pfaddr { 369 TRPortableAddress addr; 370 371 /* Initialize the addr structure */ 372 memset(&addr, 0, sizeof(addr)); 373 addr.family = pfaddr->pfra_af; 374 addr.netmask = pfaddr->pfra_net; 375 376 switch (addr.family) { 377 case (AF_INET): 378 memcpy(&addr.ip4_addr, &pfaddr->pfra_ip4addr, sizeof(addr.ip4_addr)); 379 break; 380 case (AF_INET6): 381 memcpy(&addr.ip6_addr, &pfaddr->pfra_ip6addr, sizeof(addr.ip6_addr)); 382 break; 383 default: 384 [TRLog debug: "Unsupported address family: %d", addr.family]; 385 return nil; 386 } 387 388 return [[TRPFAddress alloc] initWithPortableAddress: &addr]; 389} 390 391 392/** 393 * Copies the address' struct pfr_addr representation 394 * to the provided destination pointer. 395 */ 396- (BOOL) pfFromAddress: (TRPFAddress *) source pfaddr: (struct pfr_addr *) dest { 397 TRPortableAddress addr; 398 399 [source address: &addr]; 400 401 memset(dest, 0, sizeof(*dest)); 402 dest->pfra_af = addr.family; 403 dest->pfra_net = addr.netmask; 404 405 switch (addr.family) { 406 case (AF_INET): 407 memcpy(&dest->pfra_ip4addr, &addr.ip4_addr, sizeof(dest->pfra_ip4addr)); 408 return true; 409 case (AF_INET6): 410 memcpy(&dest->pfra_ip6addr, &addr.ip6_addr, sizeof(dest->pfra_ip6addr)); 411 return true; 412 default: 413 /* Should be unreachable, as long as we're */ 414 [TRLog debug: "Unsupported address family: %d", addr.family]; 415 return false; 416 } 417 418 return false; 419} 420 421@end 422 423#endif /* HAVE_PF */ 424