def send(self, channel, cmd, data): assert isinstance(data, bytes) assert len(data) <= 65535 p = struct.pack('!ccHHH', b('S'), b('S'), channel, cmd, len(data)) \ + data self.outbuf.append(p) debug2(' > channel=%d cmd=%s len=%d (fullness=%d)' % (channel, cmd_to_name.get(cmd, hex(cmd)), len(data), self.fullness)) self.fullness += len(data)
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 __init__(self, rfile, wfile): Handler.__init__(self, [rfile, wfile]) self.rfile = rfile self.wfile = wfile self.new_channel = self.got_dns_req = self.got_routes = None self.got_udp_open = self.got_udp_data = self.got_udp_close = None self.got_host_req = self.got_host_list = None self.channels = {} self.chani = 0 self.want = 0 self.inbuf = b('') self.outbuf = [] self.fullness = 0 self.too_full = False self.send(0, CMD_PING, b('chicken'))
def fill(self): if self.buf: return rb = self.uread() if rb: self.buf.append(rb) if rb == b(''): # empty string means EOF; None means temporarily empty self.noread()
def handle(self): self.fill() # log('inbuf is: (%d,%d) %r' # % (self.want, len(self.inbuf), self.inbuf)) while 1: if len(self.inbuf) >= (self.want or HDR_LEN): (s1, s2, channel, cmd, datalen) = \ struct.unpack('!ccHHH', self.inbuf[:HDR_LEN]) assert (s1 == b('S')) assert (s2 == b('S')) self.want = datalen + HDR_LEN if self.want and len(self.inbuf) >= self.want: data = self.inbuf[HDR_LEN:self.want] self.inbuf = self.inbuf[self.want:] self.want = 0 self.got_packet(channel, cmd, data) else: break
def callback(self, sock): try: data, peer = sock.recvfrom(4096) except socket.error: _, e = sys.exc_info()[:2] log('UDP recv from %r port %d: %s' % (peer[0], peer[1], e)) return debug2('UDP response: %d bytes' % len(data)) hdr = b("%s,%r," % (peer[0], peer[1])) self.mux.send(self.chan, ssnet.CMD_UDP_DATA, hdr + data)
def uread(self): if self.connect_to: return None # still connecting if self.shut_read: return self.rsock.setblocking(False) try: return _nb_clean(os.read, self.rsock.fileno(), 65536) except OSError: _, e = sys.exc_info()[:2] self.seterr('uread: %s' % e) return b('') # unexpected error... we'll call it EOF
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 fill(self): try: os.set_blocking(self.rfile.fileno(), False) except AttributeError: # python < 3.5 flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL) flags |= os.O_NONBLOCK flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags) try: # If LATENCY_BUFFER_SIZE is inappropriately large, we will # get a MemoryError here. Read no more than 1MiB. read = _nb_clean(os.read, self.rfile.fileno(), min(1048576, LATENCY_BUFFER_SIZE)) except OSError: _, e = sys.exc_info()[:2] raise Fatal('other end: %r' % e) # log('<<< %r' % b) if read == b(''): # EOF self.ok = False if read: self.inbuf += read
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 uread(self): if self.shut_read: return b('') # EOF else: return None # no data available right now
def nowrite(self): if not self.shut_write: self.mux.send(self.channel, CMD_TCP_EOF, b('')) self.setnowrite()
def noread(self): if not self.shut_read: self.mux.send(self.channel, CMD_TCP_STOP_SENDING, b('')) self.setnoread()
def check_fullness(self): if self.fullness > LATENCY_BUFFER_SIZE: if not self.too_full: self.send(0, CMD_PING, b('rttest')) self.too_full = True