1/*
2 *
3 * Copyright (c) 2011 Apple Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <net/if.h>
19#include <System/net/pfvar.h>
20#include <string.h>
21#include <fcntl.h>
22#include <errno.h>
23#include <sys/ioctl.h>
24#include <unistd.h>
25#include <AssertMacros.h>
26#include "P2PPacketFilter.h"
27
28#define AIRDROP_ANCHOR_PATH "com.apple/200.AirDrop"
29#define MDNS_ANCHOR_NAME    "Bonjour"
30#define MDNS_ANCHOR_PATH    AIRDROP_ANCHOR_PATH "/" MDNS_ANCHOR_NAME
31
32#define PF_DEV_PATH "/dev/pf"
33#define BONJOUR_PORT 5353
34
35static int openPFDevice( int * outFD )
36{
37    int err;
38    int fd = open( PF_DEV_PATH, O_RDWR );
39
40    if( fd >= 0 )
41    {
42        err = 0;
43        *outFD = fd;
44    }
45    else
46    {
47        err = errno;
48    }
49
50    return err;
51}
52
53static int getTicket( int devFD, u_int32_t * outTicket, char * anchorPath )
54{
55    struct pfioc_trans_e trans_e;
56
57    trans_e.rs_num = PF_RULESET_FILTER;
58    strlcpy( trans_e.anchor, anchorPath, sizeof( trans_e.anchor ) );
59
60    struct pfioc_trans trans;
61
62    trans.size = 1;
63    trans.esize = sizeof( trans_e );
64    trans.array = &trans_e;
65
66    int result, ioctlError;
67
68    ioctlError = ioctl( devFD, DIOCXBEGIN, &trans );
69    if( ioctlError )
70    {
71        result = errno;
72    }
73    else
74    {
75        result = 0;
76        *outTicket = trans_e.ticket;
77    }
78
79    return result;
80}
81
82static int commitChange( int devFD, u_int32_t ticket, char * anchorPath )
83{
84    struct pfioc_trans_e trans_e;
85
86    trans_e.rs_num = PF_RULESET_FILTER;
87    strlcpy( trans_e.anchor, anchorPath, sizeof( trans_e.anchor ) );
88    trans_e.ticket = ticket;
89
90    struct pfioc_trans trans;
91
92    trans.size = 1;
93    trans.esize = sizeof( trans_e );
94    trans.array = &trans_e;
95
96    int result, ioctlError;
97
98    ioctlError = ioctl( devFD, DIOCXCOMMIT, &trans );
99    if( ioctlError )
100        result = errno;
101    else
102        result = 0;
103
104    return result;
105}
106
107static int getPoolTicket( int devFD, u_int32_t * outPoolTicket )
108{
109    struct pfioc_pooladdr pp;
110
111    int result, ioctlError;
112
113    ioctlError = ioctl( devFD, DIOCBEGINADDRS, &pp );
114    if( ioctlError )
115    {
116        result = errno;
117    }
118    else
119    {
120        result = 0;
121        *outPoolTicket = pp.ticket;
122    }
123
124    return result;
125}
126
127static int addRule( int devFD, struct pfioc_rule * pr )
128{
129    int result, ioctlResult;
130
131    ioctlResult = ioctl( devFD, DIOCADDRULE, pr );
132    if( ioctlResult )
133        result = errno;
134    else
135        result = 0;
136
137    return result;
138}
139
140static void initRuleHeader( struct pfioc_rule * pr,
141                            u_int32_t ticket,
142                            u_int32_t poolTicket,
143                            char * anchorPath )
144{
145    pr->action = PF_CHANGE_NONE;
146    pr->ticket = ticket;
147    pr->pool_ticket = poolTicket;
148    strlcpy( pr->anchor, anchorPath, sizeof( pr->anchor ) );
149}
150
151// allow inbound traffice on the Bonjour port (5353)
152static void initBonjourRule( struct pfioc_rule * pr,
153                             const char * interfaceName,
154                             u_int32_t ticket,
155                             u_int32_t poolTicket,
156                             char * anchorPath )
157{
158    memset( pr, 0, sizeof( *pr ) );
159
160    // Header
161    initRuleHeader( pr, ticket, poolTicket, anchorPath );
162
163    // Rule
164    pr->rule.dst.xport.range.port[0] = htons( BONJOUR_PORT );
165    pr->rule.dst.xport.range.op = PF_OP_EQ;
166
167    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
168
169    pr->rule.action = PF_PASS;
170    pr->rule.direction = PF_IN;
171    pr->rule.keep_state = 1;
172    pr->rule.af = AF_INET6;
173    pr->rule.proto = IPPROTO_UDP;
174    pr->rule.extfilter = PF_EXTFILTER_APD;
175}
176
177// allow outbound TCP connections and return traffic for those connections
178static void initOutboundTCPRule( struct pfioc_rule * pr,
179                                 const char * interfaceName,
180                                 u_int32_t ticket,
181                                 u_int32_t poolTicket,
182                                 char * anchorPath )
183{
184    memset( pr, 0, sizeof( *pr ) );
185
186    // Header
187    initRuleHeader( pr, ticket, poolTicket, anchorPath );
188
189    // Rule
190    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
191
192    pr->rule.action = PF_PASS;
193    pr->rule.direction = PF_OUT;
194    pr->rule.keep_state = 1;
195    pr->rule.proto = IPPROTO_TCP;
196}
197
198// allow inbound traffic on the specified port and protocol
199static void initPortRule( struct pfioc_rule * pr,
200                          const char * interfaceName,
201                          u_int32_t ticket,
202                          u_int32_t poolTicket,
203                          char * anchorPath,
204                          u_int16_t port,
205                          u_int16_t protocol )
206{
207    memset( pr, 0, sizeof( *pr ) );
208
209    // Header
210    initRuleHeader( pr, ticket, poolTicket, anchorPath );
211
212    // Rule
213    // mDNSResponder passes the port in Network Byte Order, so htons(port) is not required
214    pr->rule.dst.xport.range.port[0] = port;
215    pr->rule.dst.xport.range.op = PF_OP_EQ;
216
217    strlcpy( pr->rule.ifname, interfaceName, sizeof( pr->rule.ifname ) );
218
219    pr->rule.action = PF_PASS;
220    pr->rule.direction = PF_IN;
221    pr->rule.keep_state = 1;
222    pr->rule.af = AF_INET6;
223    pr->rule.proto = protocol;
224    pr->rule.extfilter = PF_EXTFILTER_APD;
225}
226
227// allow inbound traffic on the Bonjour port (5353) and the specified port and protocol sets
228int P2PPacketFilterAddBonjourRuleSet(const char * interfaceName, u_int32_t count, pfArray_t portArray, pfArray_t protocolArray )
229{
230    int result;
231    u_int32_t i, ticket, poolTicket;
232    int devFD = -1;
233    char * anchorPath = MDNS_ANCHOR_PATH;
234
235    result = openPFDevice( &devFD );
236    require( result == 0, exit );
237
238    result = getTicket( devFD, &ticket, anchorPath );
239    require( result == 0, exit );
240
241    result = getPoolTicket( devFD, &poolTicket );
242    require( result == 0, exit );
243
244    struct pfioc_rule pr;
245
246    // allow inbound Bonjour traffice to port 5353
247    initBonjourRule( &pr, interfaceName, ticket, poolTicket, anchorPath);
248
249    result = addRule( devFD, &pr );
250    require( result == 0, exit );
251
252    // open inbound port for each service
253    for (i = 0; i < count; i++) {
254        initPortRule( &pr, interfaceName, ticket, poolTicket, anchorPath, portArray[i], protocolArray[i] );
255        result = addRule( devFD, &pr );
256        require( result == 0, exit );
257    }
258
259    // allow outbound TCP connections and return traffic for those connections
260    initOutboundTCPRule( &pr, interfaceName, ticket, poolTicket, anchorPath);
261
262    result = addRule( devFD, &pr );
263    require( result == 0, exit );
264
265    result = commitChange( devFD, ticket, anchorPath );
266    require( result == 0, exit );
267
268exit:
269
270    if( devFD >= 0 )
271        close( devFD );
272
273    return result;
274}
275
276int P2PPacketFilterClearBonjourRules()
277{
278    int result;
279    int pfDev = -1;
280    u_int32_t ticket;
281    char * anchorPath = MDNS_ANCHOR_PATH;
282
283    result = openPFDevice( &pfDev );
284    require( result == 0, exit );
285
286    result = getTicket( pfDev, &ticket, anchorPath );
287    require( result == 0, exit );
288
289    result = commitChange( pfDev, ticket, anchorPath );
290
291exit:
292
293    if( pfDev >= 0 )
294        close( pfDev );
295
296    return result;
297}
298
299