def _check_nmb(hostname, is_workgroup, is_master): return global _nmb_ok if not _nmb_ok: return debug2(' > n%d%d: %s' % (is_workgroup, is_master, hostname)) argv = ['nmblookup'] + ['-M'] * is_master + ['--', hostname] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=get_env) lines = p.stdout.readlines() rv = p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r' % (argv, e)) _nmb_ok = False return if rv: log('%r returned %d' % (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' % (name, ip)) if is_workgroup: _enqueue(_check_smb, ip) else: found_host(name, ip) check_host(name)
def recv_udp(listener, bufsize): debug3('Accept UDP using socket_ext recvmsg.') srcip, data, adata, _ = 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 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' % ( srcip, dstip, )) method.send_udp(sock, srcip, dstip, data)
def _check_dns(hostname): debug2(' > dns: %s' % hostname) try: ip = socket.gethostbyname(hostname) debug3('< %s' % ip) check_host(ip) found_host(hostname, ip) except (socket.gaierror, UnicodeError): pass
def _check_smb(hostname): return global _smb_ok if not _smb_ok: return debug2(' > smb: %s' % hostname) argv = ['smbclient', '-U', '%', '-L', hostname] try: p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null, env=get_env()) lines = p.stdout.readlines() p.wait() except OSError: _, e = sys.exc_info()[:2] log('%r failed: %r' % (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' % 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' % (workgroup, hostname)) check_host(hostname) check_workgroup(workgroup) if lines: assert(0)
def _check_revdns(ip): debug2(' > rev: %s' % ip) try: r = socket.gethostbyaddr(ip) debug3('< %s' % r[0]) check_host(r[0]) found_host(r[0], ip) except (socket.herror, UnicodeError): pass
def _nb_clean(func, *args): try: return func(*args) except OSError: _, e = sys.exc_info()[:2] if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN): raise else: debug3('%s: err was: %s' % (func.__name__, e)) return None
def recv_udp(listener, bufsize): debug3('Accept UDP python using recvmsg.') 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 recv_udp(listener, bufsize): debug3('Accept UDP using socket_ext recvmsg.') 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 __init__(self, rsock, wsock, connect_to=None, peername=None): global _swcount _swcount += 1 debug3('creating new SockWrapper (%d now exist)' % _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 _check_etc_hosts(): debug2(' > hosts') 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' % (ip, names)) for n in names: check_host(n) found_host(n, ip)
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 expire_connections(now, mux): remove = [] for chan, timeout in dnsreqs.items(): if timeout < now: debug3('expiring dnsreqs channel=%d' % chan) remove.append(chan) del mux.channels[chan] for chan in remove: del dnsreqs[chan] debug3('Remaining DNS requests: %d' % len(dnsreqs)) remove = [] for peer, (chan, timeout) in udp_by_src.items(): if timeout < now: debug3('expiring UDP channel channel=%d peer=%r' % (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' % len(udp_by_src))
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' % (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' % (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' % (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 recv_udp(listener, bufsize): debug3('Accept UDP using recvfrom.') data, srcip = listener.recvfrom(bufsize) return (srcip, None, data)
def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver, auto_nets): try: helpers.logprefix = ' s: ' debug1('Starting server with Python version %s' % platform.python_version()) debug1('latency control setting = %r' % latency_control) if latency_buffer_size: import tshuttle.ssnet as ssnet ssnet.LATENCY_BUFFER_SIZE = latency_buffer_size # synchronization header sys.stdout.write('\0\0SSHUTTLE0001') sys.stdout.flush() handlers = [] mux = Mux(sys.stdin, sys.stdout) handlers.append(mux) debug1('auto-nets:' + str(auto_nets)) if auto_nets: routes = list(list_routes()) debug1('available routes:') for r in routes: debug1(' %d/%s/%d' % r) else: routes = [] 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.decode("ASCII").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.' % channel) h = DnsProxy(mux, channel, data, to_nameserver) 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' % (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.' % (dstip, dstport)) h = udphandlers[channel] h.send((dstip, dstport), data) elif cmd == ssnet.CMD_UDP_CLOSE: debug2('is incoming UDP close') h = udphandlers[channel] h.ok = False del mux.channels[channel] def udp_open(channel, data): debug2('Incoming UDP open.') 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' % 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' % 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' % channel) remove.append(channel) h.ok = False for channel in remove: del udphandlers[channel] except Fatal as e: log('fatal: %s' % e) sys.exit(99)
def add_rules(anchor, rules): assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pfctl('-a %s -f /dev/stdin' % anchor, rules)
def dns_done(chan, data, method, sock, srcip, dstip, mux): debug3('dns_done: channel=%d src=%r dst=%r' % (chan, srcip, dstip)) del mux.channels[chan] del dnsreqs[chan] method.send_udp(sock, srcip, dstip, data)