P2PPacketFilter.c   [plain text]


/*
 *
 * Copyright (c) 2011 Apple Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <net/if.h>
#include <System/net/pfvar.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <AssertMacros.h>
#include "P2PPacketFilter.h"

#define AIRDROP_ANCHOR_PATH	"com.apple/200.AirDrop"
#define MDNS_ANCHOR_NAME	"Bonjour"
#define MDNS_ANCHOR_PATH	AIRDROP_ANCHOR_PATH "/" MDNS_ANCHOR_NAME

#define PF_DEV_PATH "/dev/pf"
#define BONJOUR_PORT 5353

static int openPFDevice( int * outFD )
{
    int err;
    int fd = open( PF_DEV_PATH, O_RDWR );
    
    if( fd >= 0 )
    {
        err = 0;
        *outFD = fd;
    }
    else
    {
        err = errno;
    }
    
    return err;
}

static int getTicket( int devFD, u_int32_t * outTicket, char * anchorPath )
{
    struct pfioc_trans_e trans_e;
    
    trans_e.rs_num = PF_RULESET_FILTER;
    strlcpy( trans_e.anchor, anchorPath, sizeof( trans_e.anchor ) );
    
    struct pfioc_trans trans;
    
    trans.size = 1;
    trans.esize = sizeof( trans_e );
    trans.array = &trans_e;
    
    int result, ioctlError;
    
    ioctlError = ioctl( devFD, DIOCXBEGIN, &trans );
    if( ioctlError )
    {
        result = errno;
    }
    else
    {
        result = 0;
        *outTicket = trans_e.ticket;
    }
    
    return result;
}

static int commitChange( int devFD, u_int32_t ticket, char * anchorPath )
{
    struct pfioc_trans_e trans_e;
    
    trans_e.rs_num = PF_RULESET_FILTER;
    strlcpy( trans_e.anchor, anchorPath, sizeof( trans_e.anchor ) );
    trans_e.ticket = ticket;
    
    struct pfioc_trans trans;
    
    trans.size = 1;
    trans.esize = sizeof( trans_e );
    trans.array = &trans_e;
    
    int result, ioctlError;
    
    ioctlError = ioctl( devFD, DIOCXCOMMIT, &trans );
    if( ioctlError )
        result = errno;
    else
        result = 0;
    
    return result;
}

static int getPoolTicket( int devFD, u_int32_t * outPoolTicket )
{
    struct pfioc_pooladdr pp;
    
    int result, ioctlError;
    
    ioctlError = ioctl( devFD, DIOCBEGINADDRS, &pp );
    if( ioctlError )
    {
        result = errno;
    }
    else
    {
        result = 0;
        *outPoolTicket = pp.ticket;
    }
    
    return result;
}

static int addRule( int devFD, struct pfioc_rule * pr )
{
    int result, ioctlResult;
    
    ioctlResult = ioctl( devFD, DIOCADDRULE, pr );
    if( ioctlResult )
        result = errno;
    else
        result = 0;
    
    return result;
}

static void initRuleHeader( struct pfioc_rule * pr,
                             u_int32_t ticket,
                             u_int32_t poolTicket,
							 char * anchorPath )
{
    pr->action = PF_CHANGE_NONE;
    pr->ticket = ticket;
    pr->pool_ticket = poolTicket;
    strlcpy( pr->anchor, anchorPath, sizeof( pr->anchor ) );
}

// allow inbound traffice on the Bonjour port (5353) 
static void initBonjourRule( struct pfioc_rule * pr,
                              const char * interfaceName,
                              u_int32_t ticket,
                              u_int32_t poolTicket, 
							  char * anchorPath )
{
    memset( pr, 0, sizeof( *pr ) );
    
    // Header
    initRuleHeader( pr, ticket, poolTicket, anchorPath );
    
    // Rule
    pr->rule.dst.xport.range.port[0] = htons( BONJOUR_PORT );
    pr->rule.dst.xport.range.op = PF_OP_EQ;
    
    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
    
    pr->rule.action = PF_PASS;
    pr->rule.direction = PF_IN;
    pr->rule.keep_state = 1;
    pr->rule.af = AF_INET6;
    pr->rule.proto = IPPROTO_UDP;
    pr->rule.extfilter = PF_EXTFILTER_APD;
}

// allow outbound TCP connections and return traffic for those connections
static void initOutboundTCPRule( struct pfioc_rule * pr,
                             const char * interfaceName,
                             u_int32_t ticket,
                             u_int32_t poolTicket, 
							 char * anchorPath )
{
    memset( pr, 0, sizeof( *pr ) );
    
    // Header
    initRuleHeader( pr, ticket, poolTicket, anchorPath );
    
    // Rule
    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
    
    pr->rule.action = PF_PASS;
    pr->rule.direction = PF_OUT;
    pr->rule.keep_state = 1;
    pr->rule.proto = IPPROTO_TCP;
}

// allow inbound traffic on the specified port and protocol
static void initPortRule( struct pfioc_rule * pr,
                              const char * interfaceName,
                              u_int32_t ticket,
                              u_int32_t poolTicket,
							  char * anchorPath,
							  u_int16_t port,
							  u_int16_t protocol )
{
    memset( pr, 0, sizeof( *pr ) );
    
    // Header
    initRuleHeader( pr, ticket, poolTicket, anchorPath );
    
    // Rule
	// mDNSResponder passes the port in Network Byte Order, so htons(port) is not required 
    pr->rule.dst.xport.range.port[0] = port;
    pr->rule.dst.xport.range.op = PF_OP_EQ;
    
    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
    
    pr->rule.action = PF_PASS;
    pr->rule.direction = PF_IN;
    pr->rule.keep_state = 1;
    pr->rule.af = AF_INET6;
    pr->rule.proto = protocol;
    pr->rule.extfilter = PF_EXTFILTER_APD;
}

// allow inbound traffice on the Bonjour port (5353) and the specified port and protocol
int P2PPacketFilterAddBonjourRuleSet(const char * interfaceName, u_int16_t port, u_int16_t protocol )
{
    int result;
    u_int32_t ticket, poolTicket;
    int devFD = -1;
	char * anchorPath = MDNS_ANCHOR_PATH;
	
    result = openPFDevice( &devFD );
    require( result == 0, exit );
    
    result = getTicket( devFD, &ticket, anchorPath );
    require( result == 0, exit );
    
    result = getPoolTicket( devFD, &poolTicket );
    require( result == 0, exit );
    
    struct pfioc_rule pr;
    
    // allow inbound Bonjour traffice to port 5353 
    initBonjourRule( &pr, interfaceName, ticket, poolTicket, anchorPath);
    
    result = addRule( devFD, &pr );
    require( result == 0, exit );
    
    // open inbound port for service
    initPortRule( &pr, interfaceName, ticket, poolTicket, anchorPath, port, protocol );
    
    result = addRule( devFD, &pr );
    require( result == 0, exit );
    
    // allow outbound TCP connections and return traffic for those connections
    initOutboundTCPRule( &pr, interfaceName, ticket, poolTicket, anchorPath);
    
    result = addRule( devFD, &pr );
    require( result == 0, exit );
    
    result = commitChange( devFD, ticket, anchorPath );
    require( result == 0, exit );
    
exit:
    
    if( devFD >= 0 )
        close( devFD );
    
    return result;
}

int P2PPacketFilterClearBonjourRules()
{
	int result;
	int pfDev = -1;
	u_int32_t ticket;
	char * anchorPath = MDNS_ANCHOR_PATH;
	
	result = openPFDevice( &pfDev );
	require( result == 0, exit );
	
	result = getTicket( pfDev, &ticket, anchorPath );
	require( result == 0, exit );
	
	result = commitChange( pfDev, ticket, anchorPath );
	
exit:
	
	if( pfDev >= 0 )
		close( pfDev );

	return result;
}