7
7
import ssyslog
8
8
import sys
9
9
import os
10
+ import re
10
11
from helpers import log , debug1 , debug3 , islocal , Fatal , family_to_string , \
11
12
resolvconf_nameservers
13
+ from fcntl import ioctl
14
+ from ctypes import c_char , c_uint8 , c_uint16 , c_uint32 , Union , Structure , \
15
+ sizeof , addressof , memmove
16
+
12
17
13
18
# python doesn't have a definition for this
14
19
IPPROTO_DIVERT = 254
@@ -463,15 +468,23 @@ def do_wait():
463
468
return do_wait
464
469
465
470
466
- def pfctl (* args ):
467
- argv = ['pfctl' ] + list (args )
471
+ def pfctl (args , stdin = None ):
472
+ argv = ['pfctl' ] + list (args . split ( " " ) )
468
473
debug1 ('>> %s\n ' % ' ' .join (argv ))
469
- rv = ssubprocess .Popen (argv , stderr = ssubprocess .PIPE ).wait ()
470
- if rv :
471
- raise Fatal ('%r returned %d' % (argv , rv ))
472
474
475
+ p = ssubprocess .Popen (argv , stdin = ssubprocess .PIPE ,
476
+ stdout = ssubprocess .PIPE ,
477
+ stderr = ssubprocess .PIPE )
478
+ o = p .communicate (stdin )
479
+ if p .returncode :
480
+ raise Fatal ('%r returned %d' % (argv , p .returncode ))
481
+
482
+ return o
483
+
484
+ _pf_context = {'started_by_sshuttle' : False , 'Xtoken' :'' }
473
485
474
486
def do_pf (port , dnsport , family , subnets , udp ):
487
+ global _pf_started_by_sshuttle
475
488
tables = []
476
489
translating_rules = []
477
490
filtering_rules = []
@@ -494,14 +507,27 @@ def do_pf(port, dnsport, family, subnets, udp):
494
507
translating_rules .append ('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport )
495
508
filtering_rules .append ('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state' )
496
509
497
- pf_config_file = '/etc/pf-sshuttle.conf'
498
- with open (pf_config_file , 'w+' ) as f :
499
- f .write ('\n ' .join (tables + translating_rules + filtering_rules ) + '\n ' )
500
-
501
- pfctl ('-Ef' , pf_config_file )
502
- os .remove (pf_config_file )
510
+ rules = '\n ' .join (tables + translating_rules + filtering_rules ) + '\n '
511
+
512
+ pf_status = pfctl ('-s all' )[0 ]
513
+ if not '\n rdr-anchor "sshuttle" all\n ' in pf_status :
514
+ pf_add_anchor_rule (PF_RDR , "sshuttle" )
515
+ if not '\n anchor "sshuttle" all\n ' in pf_status :
516
+ pf_add_anchor_rule (PF_PASS , "sshuttle" )
517
+
518
+ pfctl ('-a sshuttle -f /dev/stdin' , rules )
519
+ if sys .platform == "darwin" :
520
+ o = pfctl ('-E' )
521
+ _pf_context ['Xtoken' ] = re .search (r'Token : (.+)' , o [1 ]).group (1 )
522
+ elif 'INFO:\n Status: Disabled' in pf_status :
523
+ pfctl ('-e' )
524
+ _pf_context ['started_by_sshuttle' ] = True
503
525
else :
504
- pfctl ('-dF' , 'all' )
526
+ pfctl ('-a sshuttle -F all' )
527
+ if sys .platform == "darwin" :
528
+ pfctl ('-X %s' % _pf_context ['Xtoken' ])
529
+ elif _pf_context ['started_by_sshuttle' ]:
530
+ pfctl ('-d' )
505
531
506
532
507
533
def program_exists (name ):
@@ -556,6 +582,106 @@ def restore_etc_hosts(port):
556
582
rewrite_etc_hosts (port )
557
583
558
584
585
+ # This are some classes and functions used to support pf in yosemite.
586
+ class pf_state_xport (Union ):
587
+ _fields_ = [("port" , c_uint16 ),
588
+ ("call_id" , c_uint16 ),
589
+ ("spi" , c_uint32 )]
590
+
591
+ class pf_addr (Structure ):
592
+ class _pfa (Union ):
593
+ _fields_ = [("v4" , c_uint32 ), # struct in_addr
594
+ ("v6" , c_uint32 * 4 ), # struct in6_addr
595
+ ("addr8" , c_uint8 * 16 ),
596
+ ("addr16" , c_uint16 * 8 ),
597
+ ("addr32" , c_uint32 * 4 )]
598
+
599
+ _fields_ = [("pfa" , _pfa )]
600
+ _anonymous_ = ("pfa" ,)
601
+
602
+ class pfioc_natlook (Structure ):
603
+ _fields_ = [("saddr" , pf_addr ),
604
+ ("daddr" , pf_addr ),
605
+ ("rsaddr" , pf_addr ),
606
+ ("rdaddr" , pf_addr ),
607
+ ("sxport" , pf_state_xport ),
608
+ ("dxport" , pf_state_xport ),
609
+ ("rsxport" , pf_state_xport ),
610
+ ("rdxport" , pf_state_xport ),
611
+ ("af" , c_uint8 ), # sa_family_t
612
+ ("proto" , c_uint8 ),
613
+ ("proto_variant" , c_uint8 ),
614
+ ("direction" , c_uint8 )]
615
+
616
+ pfioc_rule = c_char * 3104 # sizeof(struct pfioc_rule)
617
+
618
+ pfioc_pooladdr = c_char * 1136 # sizeof(struct pfioc_pooladdr)
619
+
620
+ MAXPATHLEN = 1024
621
+
622
+ DIOCNATLOOK = ((0x40000000L | 0x80000000L ) | ((sizeof (pfioc_natlook ) & 0x1fff ) << 16 ) | ((ord ('D' )) << 8 ) | (23 ))
623
+ DIOCCHANGERULE = ((0x40000000L | 0x80000000L ) | ((sizeof (pfioc_rule ) & 0x1fff ) << 16 ) | ((ord ('D' )) << 8 ) | (26 ))
624
+ DIOCBEGINADDRS = ((0x40000000L | 0x80000000L ) | ((sizeof (pfioc_pooladdr ) & 0x1fff ) << 16 ) | ((ord ('D' )) << 8 ) | (51 ))
625
+
626
+ PF_CHANGE_ADD_TAIL = 2
627
+ PF_CHANGE_GET_TICKET = 6
628
+
629
+ PF_PASS = 0
630
+ PF_RDR = 8
631
+
632
+ PF_OUT = 2
633
+
634
+ _pf_fd = None
635
+
636
+ def pf_get_dev ():
637
+ global _pf_fd
638
+ if _pf_fd == None :
639
+ _pf_fd = os .open ('/dev/pf' , os .O_RDWR )
640
+
641
+ return _pf_fd
642
+
643
+ def pf_query_nat (family , proto , src_ip , src_port , dst_ip , dst_port ):
644
+ [proto , family , src_port , dst_port ] = [int (v ) for v in [proto , family , src_port , dst_port ]]
645
+
646
+ length = 4 if family == socket .AF_INET else 16
647
+
648
+ pnl = pfioc_natlook ()
649
+ pnl .proto = proto
650
+ pnl .direction = PF_OUT
651
+ pnl .af = family
652
+ memmove (addressof (pnl .saddr ), socket .inet_pton (pnl .af , src_ip ), length )
653
+ pnl .sxport .port = socket .htons (src_port )
654
+ memmove (addressof (pnl .daddr ), socket .inet_pton (pnl .af , dst_ip ), length )
655
+ pnl .dxport .port = socket .htons (dst_port )
656
+
657
+ ioctl (pf_get_dev (), DIOCNATLOOK , (c_char * sizeof (pnl )).from_address (addressof (pnl )))
658
+
659
+ ip = socket .inet_ntop (pnl .af , (c_char * length ).from_address (addressof (pnl .rdaddr )))
660
+ port = socket .ntohs (pnl .rdxport .port )
661
+ return (ip , port )
662
+
663
+ def pf_add_anchor_rule (type , name ):
664
+ ACTION_OFFSET = 0
665
+ POOL_TICKET_OFFSET = 8
666
+ ANCHOR_CALL_OFFSET = 1040
667
+ RULE_ACTION_OFFSET = 3068
668
+
669
+ pr = pfioc_rule ()
670
+ ppa = pfioc_pooladdr ()
671
+
672
+ ioctl (pf_get_dev (), DIOCBEGINADDRS , ppa )
673
+
674
+ memmove (addressof (pr ) + POOL_TICKET_OFFSET , ppa [4 :8 ], 4 ) #pool_ticket
675
+ memmove (addressof (pr ) + ANCHOR_CALL_OFFSET , name , min (MAXPATHLEN , len (name ))) #anchor_call = name
676
+ memmove (addressof (pr ) + RULE_ACTION_OFFSET , struct .pack ('I' , type ), 4 ) #rule.action = type
677
+
678
+ memmove (addressof (pr ) + ACTION_OFFSET , struct .pack ('I' , PF_CHANGE_GET_TICKET ), 4 ) #action = PF_CHANGE_GET_TICKET
679
+ ioctl (pf_get_dev (), DIOCCHANGERULE , pr )
680
+
681
+ memmove (addressof (pr ) + ACTION_OFFSET , struct .pack ('I' , PF_CHANGE_ADD_TAIL ), 4 ) #action = PF_CHANGE_ADD_TAIL
682
+ ioctl (pf_get_dev (), DIOCCHANGERULE , pr )
683
+
684
+
559
685
# This is some voodoo for setting up the kernel's transparent
560
686
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
561
687
# otherwise we delete it, then make them from scratch.
@@ -682,6 +808,14 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
682
808
(name , ip ) = line [5 :].strip ().split (',' , 1 )
683
809
hostmap [name ] = ip
684
810
rewrite_etc_hosts (port_v6 or port_v4 )
811
+ elif line .startswith ('QUERY_PF_NAT ' ):
812
+ try :
813
+ dst = pf_query_nat (* (line [13 :].split (',' )))
814
+ sys .stdout .write ('QUERY_PF_NAT_SUCCESS %s,%r\n ' % dst )
815
+ except IOError , e :
816
+ sys .stdout .write ('QUERY_PF_NAT_FAILURE %s\n ' % e )
817
+
818
+ sys .stdout .flush ()
685
819
elif line :
686
820
raise Fatal ('expected EOF, got %r' % line )
687
821
else :
0 commit comments