77import ssyslog
88import sys
99import os
10+ import re
1011from helpers import log , debug1 , debug3 , islocal , Fatal , family_to_string , \
1112 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+
1217
1318# python doesn't have a definition for this
1419IPPROTO_DIVERT = 254
@@ -463,15 +468,23 @@ def do_wait():
463468 return do_wait
464469
465470
466- def pfctl (* args ):
467- argv = ['pfctl' ] + list (args )
471+ def pfctl (args , stdin = None ):
472+ argv = ['pfctl' ] + list (args . split ( " " ) )
468473 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 ))
472474
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' :'' }
473485
474486def do_pf (port , dnsport , family , subnets , udp ):
487+ global _pf_started_by_sshuttle
475488 tables = []
476489 translating_rules = []
477490 filtering_rules = []
@@ -494,14 +507,27 @@ def do_pf(port, dnsport, family, subnets, udp):
494507 translating_rules .append ('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport )
495508 filtering_rules .append ('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state' )
496509
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
503525 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' )
505531
506532
507533def program_exists (name ):
@@ -556,6 +582,106 @@ def restore_etc_hosts(port):
556582 rewrite_etc_hosts (port )
557583
558584
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+
559685# This is some voodoo for setting up the kernel's transparent
560686# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
561687# 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):
682808 (name , ip ) = line [5 :].strip ().split (',' , 1 )
683809 hostmap [name ] = ip
684810 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 ()
685819 elif line :
686820 raise Fatal ('expected EOF, got %r' % line )
687821 else :
0 commit comments