def recv_udp(listener, bufsize): debug3('Accept UDP using socket_ext recvmsg.\n') srcip, data, adata, flags = listener.recvmsg( (bufsize,), socket.CMSG_SPACE(24)) dstip = None family = None for a in adata: if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_ORIGDSTADDR: family, port = struct.unpack('=HH', a.cmsg_data[0:4]) port = socket.htons(port) if family == socket.AF_INET: start = 4 length = 4 else: raise Fatal("Unsupported socket type '%s'" % family) ip = socket.inet_ntop( family, a.cmsg_data[start:start + length]) dstip = (ip, port) break elif a.cmsg_level == SOL_IPV6 and a.cmsg_type == IPV6_ORIGDSTADDR: family, port = struct.unpack('=HH', a.cmsg_data[0:4]) port = socket.htons(port) if family == socket.AF_INET6: start = 8 length = 16 else: raise Fatal("Unsupported socket type '%s'" % family) ip = socket.inet_ntop( family, a.cmsg_data[start:start + length]) dstip = (ip, port) break return (srcip, dstip, data[0])
def _check_nmb(hostname, is_workgroup, is_master): return global _nmb_ok if not _nmb_ok: return argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname] debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname)) try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) lines = p.stdout.readlines() rv = p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r\n' % (argv, e)) _nmb_ok = False return if rv: log('%r returned %d\n' % (argv, rv)) return for line in lines: m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line) if m: g = m.groups() (ip, name) = (g[0], g[1].lower()) debug3('< %s -> %s\n' % (name, ip)) if is_workgroup: _enqueue(_check_smb, ip) else: found_host(name, ip) check_host(name)
def _check_revdns(ip): debug2(' > rev: %s\n' % ip) try: r = socket.gethostbyaddr(ip) debug3('< %s\n' % r[0]) check_host(r[0]) found_host(r[0], ip) except socket.herror: pass
def _nb_clean(func, *args): try: return func(*args) except OSError as e: if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN): raise else: debug3('%s: err was: %s\n' % (func.__name__, e)) return None
def _check_dns(hostname): debug2(' > dns: %s\n' % hostname) try: ip = socket.gethostbyname(hostname) debug3('< %s\n' % ip) check_host(ip) found_host(hostname, ip) except socket.gaierror: pass
def _check_smb(hostname): return global _smb_ok if not _smb_ok: return argv = ['smbclient', '-U', '%', '-L', hostname] debug2(' > smb: %s\n' % hostname) try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) lines = p.stdout.readlines() p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r\n' % (argv, e)) _smb_ok = False return lines.reverse() # junk at top while lines: line = lines.pop().strip() if re.match(r'Server\s+', line): break # server list section: # Server Comment # ------ ------- while lines: line = lines.pop().strip() if not line or re.match(r'-+\s+-+', line): continue if re.match(r'Workgroup\s+Master', line): break words = line.split() hostname = words[0].lower() debug3('< %s\n' % hostname) check_host(hostname) # workgroup list section: # Workgroup Master # --------- ------ while lines: line = lines.pop().strip() if re.match(r'-+\s+', line): continue if not line: break words = line.split() (workgroup, hostname) = (words[0].lower(), words[1].lower()) debug3('< group(%s) -> %s\n' % (workgroup, hostname)) check_host(hostname) check_workgroup(workgroup) if lines: assert(0)
def _check_smb(hostname): return global _smb_ok if not _smb_ok: return argv = ["smbclient", "-U", "%", "-L", hostname] debug2(" > smb: %s\n" % hostname) try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) lines = p.stdout.readlines() p.wait() except OSError as e: log("%r failed: %r\n" % (argv, e)) _smb_ok = False return lines.reverse() # junk at top while lines: line = lines.pop().strip() if re.match(r"Server\s+", line): break # server list section: # Server Comment # ------ ------- while lines: line = lines.pop().strip() if not line or re.match(r"-+\s+-+", line): continue if re.match(r"Workgroup\s+Master", line): break words = line.split() hostname = words[0].lower() debug3("< %s\n" % hostname) check_host(hostname) # workgroup list section: # Workgroup Master # --------- ------ while lines: line = lines.pop().strip() if re.match(r"-+\s+", line): continue if not line: break words = line.split() (workgroup, hostname) = (words[0].lower(), words[1].lower()) debug3("< group(%s) -> %s\n" % (workgroup, hostname)) check_host(hostname) check_workgroup(workgroup) if lines: assert 0
def __init__(self, rsock, wsock, connect_to=None, peername=None): global _swcount _swcount += 1 debug3('creating new SockWrapper (%d now exist)\n' % _swcount) self.exc = None self.rsock = rsock self.wsock = wsock self.shut_read = self.shut_write = False self.buf = [] self.connect_to = connect_to self.peername = peername or _try_peername(self.rsock) self.try_connect()
def recv_udp(listener, bufsize): debug3('Accept UDP using socket_ext recvmsg.\n') srcip, data, adata, _ = listener.recvmsg((bufsize,), socket.CMSG_SPACE(4)) dstip = None for a in adata: if a.cmsg_level == socket.SOL_IP and a.cmsg_type == IP_RECVDSTADDR: port = 53 ip = socket.inet_ntop(socket.AF_INET, a.cmsg_data[0:4]) dstip = (ip, port) break return (srcip, dstip, data[0])
def recv_udp(listener, bufsize): debug3('Accept UDP python using recvmsg.\n') data, ancdata, _, srcip = listener.recvmsg(4096, socket.CMSG_SPACE(4)) dstip = None for cmsg_level, cmsg_type, cmsg_data in ancdata: if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVDSTADDR: port = 53 ip = socket.inet_ntop(socket.AF_INET, cmsg_data[0:4]) dstip = (ip, port) break return (srcip, dstip, data)
def __init__(self, rsock, wsock, connect_to=None, peername=None): global _swcount _swcount += 1 debug3('creating new SockWrapper (%d now exist)\n' % _swcount) self.exc = None self.rsock = rsock self.wsock = wsock self.shut_read = self.shut_write = False self.buf = [] self.connect_to = connect_to self.peername = peername or _try_peername(self.rsock) self.try_connect()
def get_auto_method(): debug3("Selecting a method automatically...") # Try these methods, in order: methods_to_try = ["nat", "nft", "pf", "ipfw"] for m in methods_to_try: method = get_method(m) if method.is_supported(): debug3("Method '%s' was automatically selected." % m) return method raise Fatal("Unable to automatically find a supported method. Check that " "the appropriate programs are in your PATH. We tried " "methods: %s" % str(methods_to_try))
def __del__(self): self.nowrite() if self.buf_total > 0: # If the wrapper is destroyed (e.g., by iperf3), might still have data in the buffer # Do not need to make sure buffer is flushed but do need to reduce the size of the global var global _global_mux_wrapper_buffer_size _global_mux_wrapper_buffer_size -= self.buf_total debug3( 'MuxWrapper on channel %d dead. Remove %d from global mux wrap buf size, which is now %d\n' % (self.channel, self.buf_total, _global_mux_wrapper_buffer_size)) SockWrapper.__del__(self)
def _check_etc_hosts(): debug2(' > hosts\n') for line in open('/etc/hosts'): line = re.sub(r'#.*', '', line) words = line.strip().split() if not words: continue ip = words[0] names = words[1:] if _is_ip(ip): debug3('< %s %r\n' % (ip, names)) for n in names: check_host(n) found_host(n, ip)
def _check_etc_hosts(): debug2(" > hosts\n") for line in open("/etc/hosts"): line = re.sub(r"#.*", "", line) words = line.strip().split() if not words: continue ip = words[0] names = words[1:] if _is_ip(ip): debug3("< %s %r\n" % (ip, names)) for n in names: check_host(n) found_host(n, ip)
def _check_etc_hosts(): debug2(' > hosts\n') for line in open('/etc/hosts'): line = re.sub(r'#.*', '', line) words = line.strip().split() if not words: continue ip = words[0] names = words[1:] if _is_ip(ip): debug3('< %s %r\n' % (ip, names)) for n in names: check_host(n) found_host(n, ip)
def _check_netstat(): debug2(' > netstat\n') argv = ['netstat', '-n'] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) content = p.stdout.read().decode("ASCII") p.wait() except OSError as e: log('%r failed: %r\n' % (argv, e)) return for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): debug3('< %s\n' % ip) check_host(ip)
def _check_netstat(): debug2(" > netstat\n") argv = ["netstat", "-n"] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) content = p.stdout.read() p.wait() except OSError as e: log("%r failed: %r\n" % (argv, e)) return for ip in re.findall(r"\d+\.\d+\.\d+\.\d+", content): debug3("< %s\n" % ip) check_host(ip)
def _check_netstat(): debug2(' > netstat\n') argv = ['netstat', '-n'] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null) content = p.stdout.read() p.wait() except OSError as e: log('%r failed: %r\n' % (argv, e)) return for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): debug3('< %s\n' % ip) check_host(ip)
def reload_acl_sources_file(self): global _allowed_sources if self.acl is not None: try: _new_allowed_sources = json.loads(self.acl, "utf-8") _allowed_sources = _new_allowed_sources except BaseException as e: debug3( "An exception has occurred while loading the sources data: {}\n\n" .format(e)) else: _allowed_sources = None debug3("Network Connection Sources ACL \n\n%s" % _allowed_sources)
def reload_acl_always_connected(self): global _acl_always_connected if self.acl is not None: try: _new_acl_always_connected = json.loads(self.acl, "utf-8") _acl_always_connected = _new_acl_always_connected except BaseException as e: debug3( "An exception occurred while loading the aclAlwaysConnected data: {}\n\n" .format(e)) else: _acl_always_connected = None debug3("Always Connected ACL: \n\n%s" % _acl_always_connected)
def handlePubSubEvent(self, item): acl_type = None if (item['channel'] == sshuttleAclEventsChannel and item['type'] == "message"): if (item['data'] == sshuttleAcl): acl_type = ALLOWED_ACL_TYPE elif (item['data'] == sshuttleAclSources): acl_type = ACL_SOURCES_TYPE elif (item['data'] == sshuttleAclExcluded): acl_type = ACL_EXCLUDED_SOURCES_TYPE else: debug3("Unsupported ACL type. Channel: %s, Data: %s\n" % (item['channel'], item['data'])) if acl_type is not None: AclHandler(self.redisClient, acl_type).reload_acl_file()
def maybe_resume(self, wrote): self.buf_total = self.buf_total - wrote global _global_mux_wrapper_buffer_size _global_mux_wrapper_buffer_size -= wrote debug3( 'Global mux wrap buf size: %d. Individual buf size: %d. On channel %s\n' % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel)) if self.isPaused and self.buf_total < INDIVIDUAL_BUFFER_RESUME_SIZE: self.mux.send(self.channel, CMD_RESUME, b('')) self.isPaused = False log('Global mux wrap buf size: %d. Individual buf size: %d (below threshold). Sent CMD_RESUME on channel %s\n' % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel))
def connection_is_allowed(dstip, dstport, srcip): ctime = time.time() if _excluded_sources and srcip in _excluded_sources and ( _excluded_sources[srcip] / 1000.0) >= ctime: debug1("Connection from a source excluded from the ACL\n") return True if not _allowed_sources or (srcip not in _allowed_sources) or ( srcip in _allowed_sources and (_allowed_sources[srcip] / 1000.0) < ctime): debug3("Connection not allowed - allowed sources exception\n") return False if matches_acl(dstip, dstport, _disallowed_targets): debug3("Connection not allowed - firewall ACL exception\n") return False elif matches_acl(dstip, dstport, _allowed_targets): return True
def _check_netstat(): debug2(' > netstat') argv = ['netstat', '-n'] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=get_env()) content = p.stdout.read().decode("ASCII") p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r' % (argv, e)) return for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): debug3('< %s' % ip) check_host(ip)
def reload_acl_targets_file(self): global _allowed_targets if self.acl is not None: try: _new_targets = json.loads(self.acl, "utf-8") _allowed_targets = _new_targets except BaseException as e: debug3( "An exception has occurred while loading the allowed targets (sshuttleAcl) data: {}\n\n" .format(e)) else: _allowed_targets = None if (not _allowed_targets): log("Allowed ACL list is empty. Restricting all access\n") else: log("Network Connection Allowed ACL \n\n%s" % _allowed_targets)
def handlePubSubEvent(self, item): acl_type = None channel = item['channel'].decode('utf-8') if (channel == sshuttleAclEventsChannel and item['type'] == "message"): data = item['data'].decode('utf-8') if (data == sshuttleAclTcp): acl_type = ALLOWED_TCP_ACL_TYPE elif (data == sshuttleAclUdp): acl_type = ALLOWED_UDP_ACL_TYPE elif (data == sshuttleAclSources): acl_type = ACL_SOURCES_TYPE elif (data == sshuttleAclExcluded): acl_type = ACL_EXCLUDED_SOURCES_TYPE else: debug3("Unsupported ACL type. Channel: %s, Data: %s\n" % (channel, data)) if acl_type is not None: AclHandler(self.redisClient, acl_type).reload_acl_file()
def _check_etc_hosts(): """If possible, read /etc/hosts to find hosts.""" filename = '/etc/hosts' debug2(' > Reading %s on remote host' % filename) try: for line in open(filename): line = re.sub(r'#.*', '', line) # remove comments words = line.strip().split() if not words: continue ip = words[0] if _is_ip(ip): names = words[1:] debug3('< %s %r' % (ip, names)) for n in names: check_host(n) found_host(n, ip) except (OSError, IOError): debug1("Failed to read %s on remote host" % filename)
def tcp_connection_is_allowed_conditional(dstip, dstport, srcip, check_acl, check_sources): if check_sources: ctime = time.time() if _excluded_sources and srcip in _excluded_sources and ( _excluded_sources[srcip] / 1000.0) >= ctime: debug3("Connection from a source excluded from the ACL\n") return True check_allowed_sources = True # the global roomFeature must be turned ON and the srcip must match a desktop that has Always Connected enabled if (_always_connected == ALWAYS_CONNECTED_ON) and (srcip in _acl_always_connected): debug3( "TCP source %r allowed because alwaysConnected mode is ON and srcip is in aclAlwaysConnected\n" % srcip) check_allowed_sources = False if check_allowed_sources: if not _allowed_sources: debug3( "Connection not allowed - allowed sources exception - not _allowed_sources\n" ) return False if (srcip not in _allowed_sources): debug3( "Connection not allowed - allowed sources exception - (srcip not in _allowed_sources)\n" ) return False if (srcip in _allowed_sources and (_allowed_sources[srcip] / 1000.0) < ctime): debug3( "Connection not allowed - allowed sources exception - (srcip in _allowed_sources and (_allowed_sources[srcip] / 1000.0) < ctime)\n" ) return False if check_acl: if matches_acl(dstip, dstport, _allowed_tcp_targets): return True else: return True
def maybe_pause(self, data): self.buf_total = self.buf_total + len(data) global _global_mux_wrapper_buffer_size _global_mux_wrapper_buffer_size += len(data) self.metrics.save_max_mux_wrapper_buffer_size( _global_mux_wrapper_buffer_size) debug3( 'Global mux wrap buf size: %d. Individual buf size: %d. On channel %s\n' % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel)) if not self.isPaused and _global_mux_wrapper_buffer_size > PAUSE_TRAFFIC_TO_EDGE_THRESHOLD and self.buf_total\ > INDIVIDUAL_BUFFER_PAUSE_SIZE: self.mux.send(self.channel, CMD_PAUSE, b('')) self.isPaused = True self.metrics.incr_paused_to_the_edge_count() log('Global mux wrap buf size: %d (above threshold). Individual buf size: %d (above threshold). Sent CMD_PAUSE on channel %s\n' % (_global_mux_wrapper_buffer_size, self.buf_total, self.channel))
def _check_netstat(): debug2(' > netstat\n') env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } argv = ['netstat', '-n'] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=env) content = p.stdout.read().decode("ASCII") p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r\n' % (argv, e)) return for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): debug3('< %s\n' % ip) check_host(ip)
def __init__(self, rsock, wsock, connect_to=None, peername=None, connection_is_allowed_callback=None): global _swcount _swcount += 1 debug3('creating new SockWrapper (%d now exist)\n' % _swcount) self.exc = None self.rsock = rsock self.wsock = wsock self.shut_read = self.shut_write = False self.buf = [] self.connect_to = connect_to self.peername = peername or _try_peername(self.rsock) self.connection_is_allowed_callback = connection_is_allowed_callback self.try_connect() self.isWrite = False self.isPaused = False # traffic to the edge is paused (always false in SockWrapper; may be true in MuxWrapper)
def expire_connections(now, mux): for chan, timeout in dnsreqs.items(): if timeout < now: debug3('expiring dnsreqs channel=%d\n' % chan) del mux.channels[chan] del dnsreqs[chan] debug3('Remaining DNS requests: %d\n' % len(dnsreqs)) for peer, (chan, timeout) in udp_by_src.items(): if timeout < now: debug3('expiring UDP channel channel=%d peer=%r\n' % (chan, peer)) mux.send(chan, ssnet.CMD_UDP_CLOSE, '') del mux.channels[chan] del udp_by_src[peer] debug3('Remaining UDP channels: %d\n' % len(udp_by_src))
def udp_connection_is_allowed(dstip, dstport, srcip): ctime = time.time() if _excluded_sources and srcip in _excluded_sources and ( _excluded_sources[srcip] / 1000.0) >= ctime: debug1("Connection from a source excluded from the ACL\n") return True if not _allowed_sources: debug3( "Connection not allowed - allowed sources exception - not _allowed_sources\n" ) return False if (srcip not in _allowed_sources): debug3( "Connection not allowed - allowed sources exception - (srcip not in _allowed_sources)\n" ) return False if (srcip in _allowed_sources and (_allowed_sources[srcip] / 1000.0) < ctime): debug3( "Connection not allowed - allowed sources exception - (srcip in _allowed_sources and (_allowed_sources[srcip] / 1000.0) < ctime)\n" ) return False if matches_acl(dstip, dstport, _allowed_udp_targets): return True
def _check_netstat(): debug2(' > netstat') argv = ['netstat', '-n'] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=get_env()) content = p.stdout.read().decode("ASCII") p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r' % (argv, e)) return # The same IPs may appear multiple times. Consolidate them so the # debug message doesn't print the same IP repeatedly. ip_list = [] for ip in re.findall(r'\d+\.\d+\.\d+\.\d+', content): if ip not in ip_list: ip_list.append(ip) for ip in sorted(ip_list): debug3('< %s' % ip) check_host(ip)
def _check_nmb(hostname, is_workgroup, is_master): return global _nmb_ok if not _nmb_ok: return debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=env) lines = p.stdout.readlines() rv = p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r\n' % (argv, e)) _nmb_ok = False return if rv: log('%r returned %d\n' % (argv, rv)) return for line in lines: m = re.match(r'(\d+\.\d+\.\d+\.\d+) (\w+)<\w\w>\n', line) if m: g = m.groups() (ip, name) = (g[0], g[1].lower()) debug3('< %s -> %s\n' % (name, ip)) if is_workgroup: _enqueue(_check_smb, ip) else: found_host(name, ip) check_host(name)
def expire_connections(now, mux): remove = [] for chan, timeout in dnsreqs.items(): if timeout < now: debug3('expiring dnsreqs channel=%d\n' % chan) remove.append(chan) del mux.channels[chan] for chan in remove: del dnsreqs[chan] debug3('Remaining DNS requests: %d\n' % len(dnsreqs)) remove = [] for peer, (chan, timeout) in udp_by_src.items(): if timeout < now: debug3('expiring UDP channel channel=%d peer=%r\n' % (chan, peer)) mux.send(chan, ssnet.CMD_UDP_CLOSE, b'') remove.append(peer) del mux.channels[chan] for peer in remove: del udp_by_src[peer] debug3('Remaining UDP channels: %d\n' % len(udp_by_src)) # we also want to close all TCP connections from sources that have expired their lease global tcp_conns new_tcp_conns = [] for (srcip, dstip, s, sock) in tcp_conns: if connection_is_allowed(dstip[0], str(dstip[1]), srcip[0]) and s.ok: new_tcp_conns.append((srcip, dstip, s, sock)) else: try: # remove from list of active tcp connections del active_tcp_conns[sock] # really make sure we kill everything while we can s.ok = False s.wrap1.noread() s.wrap1.nowrite() s.wrap2.noread() s.wrap2.nowrite() del mux.channels[s.wrap2.channel] sock.close() sock.shutdown(2) except: # we may hit an exception if the socket has already been closed...that is ok pass tcp_conns = new_tcp_conns
def acl_port_match(port, acl_port_rule): for port_entry in acl_port_rule: if '-' in port_entry: if (port_in_range(port_entry, port)): debug3('port: %s is in acl port range: %s' % (port, port_entry)) return True debug3('port: %s is NOT in acl port range: %s' % (port, port_entry)) elif int(port_entry) == int(port): debug3('port: %s is in acl port rule: %s' % (port, acl_port_rule)) return True return False
def try_connect(self): if self.connect_to and self.shut_write: self.noread() self.connect_to = None if not self.connect_to: return # already connected self.rsock.setblocking(False) debug3('%r: trying connect to %r\n' % (self, self.connect_to)) try: self.rsock.connect(self.connect_to) # connected successfully (Linux) self.connect_to = None except socket.error: _, e = sys.exc_info()[:2] debug3('%r: connect result: %s\n' % (self, e)) if e.args[0] == errno.EINVAL: # this is what happens when you call connect() on a socket # that is now connected but returned EINPROGRESS last time, # on BSD, on python pre-2.5.1. We need to use getsockopt() # to get the "real" error. Later pythons do this # automatically, so this code won't run. realerr = self.rsock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) e = socket.error(realerr, os.strerror(realerr)) debug3('%r: fixed connect result: %s\n' % (self, e)) if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]: pass # not connected yet elif e.args[0] == 0: # connected successfully (weird Linux bug?) # Sometimes Linux seems to return EINVAL when it isn't # invalid. This *may* be caused by a race condition # between connect() and getsockopt(SO_ERROR) (ie. it # finishes connecting in between the two, so there is no # longer an error). However, I'm not sure of that. # # I did get at least one report that the problem went away # when we added this, however. self.connect_to = None elif e.args[0] == errno.EISCONN: # connected successfully (BSD) self.connect_to = None elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]: # a "normal" kind of error self.connect_to = None self.seterr(e) else: raise # error we've never heard of?! barf completely.
def try_connect(self): if self.connect_to and self.shut_write: self.noread() self.connect_to = None if not self.connect_to: return # already connected self.rsock.setblocking(False) debug3('%r: trying connect to %r\n' % (self, self.connect_to)) try: self.rsock.connect(self.connect_to) # connected successfully (Linux) self.connect_to = None except socket.error: _, e = sys.exc_info()[:2] debug3('%r: connect result: %s\n' % (self, e)) if e.args[0] == errno.EINVAL: # this is what happens when you call connect() on a socket # that is now connected but returned EINPROGRESS last time, # on BSD, on python pre-2.5.1. We need to use getsockopt() # to get the "real" error. Later pythons do this # automatically, so this code won't run. realerr = self.rsock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) e = socket.error(realerr, os.strerror(realerr)) debug3('%r: fixed connect result: %s\n' % (self, e)) if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]: pass # not connected yet elif e.args[0] == 0: # connected successfully (weird Linux bug?) # Sometimes Linux seems to return EINVAL when it isn't # invalid. This *may* be caused by a race condition # between connect() and getsockopt(SO_ERROR) (ie. it # finishes connecting in between the two, so there is no # longer an error). However, I'm not sure of that. # # I did get at least one report that the problem went away # when we added this, however. self.connect_to = None elif e.args[0] == errno.EISCONN: # connected successfully (BSD) self.connect_to = None elif e.args[0] in NET_ERRS + [errno.EACCES, errno.EPERM]: # a "normal" kind of error self.connect_to = None self.seterr(e) else: raise # error we've never heard of?! barf completely.
def _handle_diversion(divertsock, dnsport): p, tag = divertsock.recvfrom(4096) src, dst = _udp_unpack(p) debug3('got diverted packet from %r to %r\n' % (src, dst)) if dst[1] == 53: # outgoing DNS debug3('...packet is a DNS request.\n') _real_dns_server[0] = dst dst = ('127.0.0.1', dnsport) elif src[1] == dnsport: if islocal(src[0], divertsock.family): debug3('...packet is a DNS response.\n') src = _real_dns_server[0] else: log('weird?! unexpected divert from %r to %r\n' % (src, dst)) assert (0) newp = _udp_repack(p, src, dst) divertsock.sendto(newp, tag)
def _handle_diversion(divertsock, dnsport): p, tag = divertsock.recvfrom(4096) src, dst = _udp_unpack(p) debug3('got diverted packet from %r to %r\n' % (src, dst)) if dst[1] == 53: # outgoing DNS debug3('...packet is a DNS request.\n') _real_dns_server[0] = dst dst = ('127.0.0.1', dnsport) elif src[1] == dnsport: if islocal(src[0], divertsock.family): debug3('...packet is a DNS response.\n') src = _real_dns_server[0] else: log('weird?! unexpected divert from %r to %r\n' % (src, dst)) assert(0) newp = _udp_repack(p, src, dst) divertsock.sendto(newp, tag)
def main(latency_control, auto_hosts): debug1('Starting server with Python version %s\n' % platform.python_version()) if helpers.verbose >= 1: helpers.logprefix = ' s: ' else: helpers.logprefix = 'server: ' debug1('latency control setting = %r\n' % latency_control) routes = list(list_routes()) debug1('available routes:\n') for r in routes: debug1(' %d/%s/%d\n' % r) # synchronization header sys.stdout.write('\0\0SSHUTTLE0001') sys.stdout.flush() handlers = [] mux = Mux( socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM), socket.fromfd(sys.stdout.fileno(), socket.AF_INET, socket.SOCK_STREAM)) handlers.append(mux) routepkt = '' for r in routes: routepkt += '%d,%s,%d\n' % r mux.send(0, ssnet.CMD_ROUTES, b(routepkt)) hw = Hostwatch() hw.leftover = b('') def hostwatch_ready(sock): assert (hw.pid) content = hw.sock.recv(4096) if content: lines = (hw.leftover + content).split(b('\n')) if lines[-1]: # no terminating newline: entry isn't complete yet! hw.leftover = lines.pop() lines.append(b('')) else: hw.leftover = b('') mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines)) else: raise Fatal('hostwatch process died') def got_host_req(data): if not hw.pid: (hw.pid, hw.sock) = start_hostwatch(data.strip().split(), auto_hosts) handlers.append(Handler(socks=[hw.sock], callback=hostwatch_ready)) mux.got_host_req = got_host_req def new_channel(channel, data): (family, dstip, dstport) = data.decode("ASCII").split(',', 2) family = int(family) # AF_INET is the same constant on Linux and BSD but AF_INET6 # is different. As the client and server can be running on # different platforms we can not just set the socket family # to what comes in the wire. if family != socket.AF_INET: family = socket.AF_INET6 dstport = int(dstport) outwrap = ssnet.connect_dst(family, dstip, dstport) handlers.append(Proxy(MuxWrapper(mux, channel), outwrap)) mux.new_channel = new_channel dnshandlers = {} def dns_req(channel, data): debug2('Incoming DNS request channel=%d.\n' % channel) h = DnsProxy(mux, channel, data) handlers.append(h) dnshandlers[channel] = h mux.got_dns_req = dns_req udphandlers = {} def udp_req(channel, cmd, data): debug2('Incoming UDP request channel=%d, cmd=%d\n' % (channel, cmd)) if cmd == ssnet.CMD_UDP_DATA: (dstip, dstport, data) = data.split(b(','), 2) dstport = int(dstport) debug2('is incoming UDP data. %r %d.\n' % (dstip, dstport)) h = udphandlers[channel] h.send((dstip, dstport), data) elif cmd == ssnet.CMD_UDP_CLOSE: debug2('is incoming UDP close\n') h = udphandlers[channel] h.ok = False del mux.channels[channel] def udp_open(channel, data): debug2('Incoming UDP open.\n') family = int(data) mux.channels[channel] = lambda cmd, data: udp_req(channel, cmd, data) if channel in udphandlers: raise Fatal('UDP connection channel %d already open' % channel) else: h = UdpProxy(mux, channel, family) handlers.append(h) udphandlers[channel] = h mux.got_udp_open = udp_open while mux.ok: if hw.pid: assert (hw.pid > 0) (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG) if rpid: raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv) ssnet.runonce(handlers, mux) if latency_control: mux.check_fullness() if dnshandlers: now = time.time() remove = [] for channel, h in dnshandlers.items(): if h.timeout < now or not h.ok: debug3('expiring dnsreqs channel=%d\n' % channel) remove.append(channel) h.ok = False for channel in remove: del dnshandlers[channel] if udphandlers: remove = [] for channel, h in udphandlers.items(): if not h.ok: debug3('expiring UDP channel=%d\n' % channel) remove.append(channel) h.ok = False for channel in remove: del udphandlers[channel]
def add_rules(self, anchor, rules): assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pfctl('-a %s -f /dev/stdin' % anchor, rules)
def add_rules(self, rules): assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pfctl('-a sshuttle -f /dev/stdin', rules)
def add_rules(anchor, rules): assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pfctl('-a %s -f /dev/stdin' % anchor, rules)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: includes = [] # If a given subnet is both included and excluded, list the # exclusion first; the table will ignore the second, opposite # definition for f, swidth, sexclude, snet in sorted( subnets, key=lambda s: (s[1], s[2]), reverse=True): includes.append(b"%s%s/%d" % (b"!" if sexclude else b"", snet.encode("ASCII"), swidth)) tables.append( b'table <forward_subnets> {%s}' % b','.join(includes)) translating_rules.append( b'rdr pass on lo0 proto tcp ' b'to <forward_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append( b'pass out route-to lo0 inet proto tcp ' b'to <forward_subnets> keep state') if len(nslist) > 0: tables.append( b'table <dns_servers> {%s}' % b','.join([ns[1].encode("ASCII") for ns in nslist])) translating_rules.append( b'rdr pass on lo0 proto udp to ' b'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append( b'pass out route-to lo0 inet proto udp to ' b'<dns_servers> port 53 keep state') rules = b'\n'.join(tables + translating_rules + filtering_rules) \ + b'\n' assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pf_status = pfctl('-s all')[0] if b'\nrdr-anchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle") if b'\nanchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if osdefs.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = \ re.search(b'Token : (.+)', o[1]).group(1) elif b'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True
def recv_udp(listener, bufsize): debug3('Accept UDP using recvfrom.\n') data, srcip = listener.recvfrom(bufsize) return (srcip, None, data)
def udp_done(chan, data, method, sock, dstip): (src, srcport, data) = data.split(b",", 2) srcip = (src, int(srcport)) debug3('doing send from %r to %r\n' % (srcip, dstip,)) method.send_udp(sock, srcip, dstip, data)
def dns_done(chan, data, method, sock, srcip, dstip, mux): debug3('dns_done: channel=%d src=%r dst=%r\n' % (chan, srcip, dstip)) del mux.channels[chan] del dnsreqs[chan] method.send_udp(sock, srcip, dstip, data)
def main(latency_control): debug1('Starting server with Python version %s\n' % platform.python_version()) if helpers.verbose >= 1: helpers.logprefix = ' s: ' else: helpers.logprefix = 'server: ' debug1('latency control setting = %r\n' % latency_control) routes = list(list_routes()) debug1('available routes:\n') for r in routes: debug1(' %d/%s/%d\n' % r) # synchronization header sys.stdout.write('\0\0SSHUTTLE0001') sys.stdout.flush() handlers = [] mux = Mux(socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM), socket.fromfd(sys.stdout.fileno(), socket.AF_INET, socket.SOCK_STREAM)) handlers.append(mux) routepkt = '' for r in routes: routepkt += '%d,%s,%d\n' % r mux.send(0, ssnet.CMD_ROUTES, b(routepkt)) hw = Hostwatch() hw.leftover = b('') def hostwatch_ready(sock): assert(hw.pid) content = hw.sock.recv(4096) if content: lines = (hw.leftover + content).split(b('\n')) if lines[-1]: # no terminating newline: entry isn't complete yet! hw.leftover = lines.pop() lines.append(b('')) else: hw.leftover = b('') mux.send(0, ssnet.CMD_HOST_LIST, b('\n').join(lines)) else: raise Fatal('hostwatch process died') def got_host_req(data): if not hw.pid: (hw.pid, hw.sock) = start_hostwatch(data.strip().split()) handlers.append(Handler(socks=[hw.sock], callback=hostwatch_ready)) mux.got_host_req = got_host_req def new_channel(channel, data): (family, dstip, dstport) = data.decode("ASCII").split(',', 2) family = int(family) dstport = int(dstport) outwrap = ssnet.connect_dst(family, dstip, dstport) handlers.append(Proxy(MuxWrapper(mux, channel), outwrap)) mux.new_channel = new_channel dnshandlers = {} def dns_req(channel, data): debug2('Incoming DNS request channel=%d.\n' % channel) h = DnsProxy(mux, channel, data) handlers.append(h) dnshandlers[channel] = h mux.got_dns_req = dns_req udphandlers = {} def udp_req(channel, cmd, data): debug2('Incoming UDP request channel=%d, cmd=%d\n' % (channel, cmd)) if cmd == ssnet.CMD_UDP_DATA: (dstip, dstport, data) = data.split(",", 2) dstport = int(dstport) debug2('is incoming UDP data. %r %d.\n' % (dstip, dstport)) h = udphandlers[channel] h.send((dstip, dstport), data) elif cmd == ssnet.CMD_UDP_CLOSE: debug2('is incoming UDP close\n') h = udphandlers[channel] h.ok = False del mux.channels[channel] def udp_open(channel, data): debug2('Incoming UDP open.\n') family = int(data) mux.channels[channel] = lambda cmd, data: udp_req(channel, cmd, data) if channel in udphandlers: raise Fatal('UDP connection channel %d already open' % channel) else: h = UdpProxy(mux, channel, family) handlers.append(h) udphandlers[channel] = h mux.got_udp_open = udp_open while mux.ok: if hw.pid: assert(hw.pid > 0) (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG) if rpid: raise Fatal( 'hostwatch exited unexpectedly: code 0x%04x\n' % rv) ssnet.runonce(handlers, mux) if latency_control: mux.check_fullness() if dnshandlers: now = time.time() remove = [] for channel, h in dnshandlers.items(): if h.timeout < now or not h.ok: debug3('expiring dnsreqs channel=%d\n' % channel) remove.append(channel) h.ok = False for channel in remove: del dnshandlers[channel] if udphandlers: remove = [] for channel, h in udphandlers.items(): if not h.ok: debug3('expiring UDP channel=%d\n' % channel) remove.append(channel) h.ok = False for channel in remove: del udphandlers[channel]