def ipfw(*args): argv = ['ipfw', '-q'] + list(args) debug1('>> %s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) # No env: No output. (Or error that won't be parsed.) if rv: raise Fatal('%r returned %d' % (argv, rv))
def _notify(message): """Send a notification message to systemd.""" addr = os.environ.get("NOTIFY_SOCKET", None) if not addr or len(addr) == 1 or addr[0] not in ('/', '@'): return False addr = '\0' + addr[1:] if addr[0] == '@' else addr try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) except (OSError, IOError) as e: debug1("Error creating socket to notify systemd: %s" % e) return False if not message: return False assert isinstance(message, bytes) try: return (sock.sendto(message, addr) > 0) except (OSError, IOError) as e: debug1("Error notifying systemd: %s" % e) return False
def recv_udp(self, udp_listener, bufsize): srcip, dstip, data = recv_udp(udp_listener, bufsize) if not dstip: debug1("-- ignored UDP from %r: " "couldn't determine destination IP address" % (srcip, )) return None return srcip, dstip, data
def nft(family, table, action, *args): if family in (socket.AF_INET, socket.AF_INET6): argv = ['nft', action, 'inet', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
def print_listening(self, what): assert (self.bind_called) if self.v6: listenip = self.v6.getsockname() debug1('%s listening on %r.' % (what, listenip)) debug2('%s listening with %r.' % (what, self.v6)) if self.v4: listenip = self.v4.getsockname() debug1('%s listening on %r.' % (what, listenip)) debug2('%s listening with %r.' % (what, self.v4))
def send_udp(self, sock, srcip, dstip, data): if not srcip: debug1("-- ignored UDP to %r: " "couldn't determine source IP address\n" % (dstip, )) return sender = socket.socket(sock.family, socket.SOCK_DGRAM) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sender.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1) sender.bind(srcip) sender.sendto(data, dstip) sender.close()
def feature_status(label, enabled, available): msg = label + ": " if enabled: msg += "on" else: msg += "off " if available: msg += "(available)" else: msg += "(not available with %s method)" % fw.method.name debug1(msg)
def ipt(family, table, *args): if family == socket.AF_INET6: argv = ['ip6tables', '-t', table] + list(args) elif family == socket.AF_INET: argv = ['iptables', '-t', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
def pfctl(args, stdin=None): argv = ['pfctl'] + shlex.split(args) debug1('>> %s' % ' '.join(argv)) p = ssubprocess.Popen(argv, stdin=ssubprocess.PIPE, stdout=ssubprocess.PIPE, stderr=ssubprocess.PIPE, env=get_env()) o = p.communicate(stdin) if p.returncode: raise Fatal('%r returned %d' % (argv, p.returncode)) return o
def send_udp(self, sock, srcip, dstip, data): if not srcip: debug1("-- ignored UDP to %r: " "couldn't determine source IP address" % (dstip, )) return # debug3('Sending SRC: %r DST: %r' % (srcip, dstip)) sender = socket.socket(sock.family, socket.SOCK_DGRAM) sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63) sender.bind(srcip) sender.sendto(data, dstip) sender.close()
def uwrite(self, buf): if self.connect_to: return 0 # still connecting self.wsock.setblocking(False) try: return _nb_clean(os.write, self.wsock.fileno(), buf) except OSError: _, e = sys.exc_info()[:2] if e.errno == errno.EPIPE: debug1('%r: uwrite: got EPIPE' % self) self.nowrite() return 0 else: # unexpected error... stream is dead self.seterr('uwrite: %s' % e) return 0
def found_host(name, ip): hostname = re.sub(r'\..*', '', name) hostname = re.sub(r'[^-\w\.]', '_', hostname) if (ip.startswith('127.') or ip.startswith('255.') or hostname == 'localhost'): return if hostname != name: found_host(hostname, ip) oldip = hostnames.get(name) if oldip != ip: hostnames[name] = ip debug1('Found: %s: %s' % (name, ip)) sys.stdout.write('%s,%s\n' % (name, ip)) write_host_cache()
def save_config(content, file_name): process = Popen([ '/usr/bin/sudo', spawn.find_executable('sudoers-add'), file_name, ], stdout=PIPE, stdin=PIPE) process.stdin.write(content.encode()) streamdata = process.communicate()[0] returncode = process.returncode if returncode: log('Failed updating sudoers file.') debug1(streamdata) exit(returncode) else: log('Success, sudoers file update.') exit(0)
def ondns(listener, method, mux, handlers): now = time.time() t = method.recv_udp(listener, 4096) if t is None: return srcip, dstip, data = t # dstip is None if we are using a method where we can't determine # the destination IP of the DNS request that we captured from the client. if dstip is None: debug1('DNS request from %r: %d bytes' % (srcip, len(data))) else: debug1('DNS request from %r to %r: %d bytes' % (srcip, dstip, len(data))) chan = mux.next_channel() dnsreqs[chan] = now + 30 mux.send(chan, ssnet.CMD_DNS_REQ, data) mux.channels[chan] = lambda cmd, data: dns_done( chan, data, method, listener, srcip=dstip, dstip=srcip, mux=mux) expire_connections(now, mux)
def sysctl_set(name, val, permanent=False): PREFIX = 'net.inet.ip' assert (name.startswith(PREFIX + '.')) val = str(val) if not _oldctls: _fill_oldctls(PREFIX) if not (name in _oldctls): debug1('>> No such sysctl: %r' % name) return False oldval = _oldctls[name] if val != oldval: rv = _sysctl_set(name, val) if rv == 0 and permanent: debug1('>> ...saving permanently in /etc/sysctl.conf') f = open('/etc/sysctl.conf', 'a') f.write('\n' '# Added by tshuttle\n' '%s=%s\n' % (name, val)) f.close() else: _changedctls.append(name) return True
def onaccept_tcp(listener, method, mux, handlers): global _extra_fd try: sock, srcip = listener.accept() except socket.error as e: if e.args[0] in [errno.EMFILE, errno.ENFILE]: debug1('Rejected incoming connection: too many open files!') # free up an fd so we can eat the connection os.close(_extra_fd) try: sock, srcip = listener.accept() sock.close() finally: _extra_fd = os.open(os.devnull, os.O_RDONLY) return else: raise dstip = method.get_tcp_dstip(sock) debug1('Accept TCP: %s:%r -> %s:%r.' % (srcip[0], srcip[1], dstip[0], dstip[1])) if dstip[1] == sock.getsockname()[1] and islocal(dstip[0], sock.family): debug1("-- ignored: that's my address!") sock.close() return chan = mux.next_channel() if not chan: log('warning: too many open channels. Discarded connection.') sock.close() return mux.send(chan, ssnet.CMD_TCP_CONNECT, b'%d,%s,%d' % (sock.family, dstip[0].encode("ASCII"), dstip[1])) outwrap = MuxWrapper(mux, chan) handlers.append(Proxy(SockWrapper(sock, sock), outwrap)) expire_connections(time.time(), mux)
def hw_main(seed_hosts, auto_hosts): helpers.logprefix = 'HH: ' debug1('Starting hostwatch with Python version %s' % platform.python_version()) for h in seed_hosts: check_host(h) if auto_hosts: read_host_cache() _enqueue(_check_etc_hosts) _enqueue(_check_netstat) check_host('localhost') check_host(socket.gethostname()) check_workgroup('workgroup') check_workgroup('-') while 1: now = time.time() for t, last_polled in list(queue.items()): (op, args) = t if not _stdin_still_ok(0): break maxtime = POLL_TIME if op == _check_netstat: maxtime = NETSTAT_POLL_TIME if now - last_polled > maxtime: queue[t] = time.time() op(*args) try: sys.stdout.flush() except IOError: break # FIXME: use a smarter timeout based on oldest last_polled if not _stdin_still_ok(1): break
def onaccept_udp(listener, method, mux, handlers): now = time.time() t = method.recv_udp(listener, 4096) if t is None: return srcip, dstip, data = t debug1('Accept UDP: %r -> %r.' % ( srcip, dstip, )) if srcip in udp_by_src: chan, _ = udp_by_src[srcip] else: chan = mux.next_channel() mux.channels[chan] = lambda cmd, data: udp_done( chan, data, method, listener, dstip=srcip) mux.send(chan, ssnet.CMD_UDP_OPEN, b"%d" % listener.family) udp_by_src[srcip] = chan, now + 30 hdr = b"%s,%d," % (dstip[0].encode("ASCII"), dstip[1]) mux.send(chan, ssnet.CMD_UDP_DATA, hdr + data) expire_connections(now, mux)
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, latency_buffer_size, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver): helpers.logprefix = 'c : ' debug1('Starting client with Python version %s' % platform.python_version()) method = fw.method handlers = [] debug1('Connecting to server...') try: (serverproc, serversock) = ssh.connect( ssh_cmd, remotename, python, stderr=ssyslog._p and ssyslog._p.stdin, options=dict(latency_control=latency_control, latency_buffer_size=latency_buffer_size, auto_hosts=auto_hosts, to_nameserver=to_nameserver, auto_nets=auto_nets)) except socket.error as e: if e.args[0] == errno.EPIPE: raise Fatal("failed to establish ssh session (1)") else: raise mux = Mux(serversock.makefile("rb"), serversock.makefile("wb")) handlers.append(mux) expected = b'SSHUTTLE0001' try: v = 'x' while v and v != b'\0': v = serversock.recv(1) v = 'x' while v and v != b'\0': v = serversock.recv(1) initstring = serversock.recv(len(expected)) except socket.error as e: if e.args[0] == errno.ECONNRESET: raise Fatal("failed to establish ssh session (2)") else: raise # Returns None if process is still running (or returns exit code) rv = serverproc.poll() if rv is not None: errmsg = "server died with error code %d\n" % rv # Our fatal exceptions return exit code 99 if rv == 99: errmsg += "This error code likely means that python started and " \ "the tshuttle server started. However, the tshuttle server " \ "may have raised a 'Fatal' exception after it started." elif rv == 98: errmsg += "This error code likely means that we were able to " \ "run python on the server, but that the program continued " \ "to the line after we call python's exec() to execute " \ "tshuttle's server code. Try specifying the python " \ "executable to user on the server by passing --python " \ "to tshuttle." # This error should only be possible when --python is not specified. elif rv == 97 and not python: errmsg += "This error code likely means that either we " \ "couldn't find python3 or python in the PATH on the " \ "server or that we do not have permission to run 'exec' in " \ "the /bin/sh shell on the server. Try specifying the " \ "python executable to use on the server by passing " \ "--python to tshuttle." # POSIX sh standards says error code 127 is used when you try # to execute a program that does not exist. See section 2.8.2 # of # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08 elif rv == 127: if python: errmsg += "This error code likely means that we were not " \ "able to execute the python executable that specified " \ "with --python. You specified '%s'.\n" % python if python.startswith("/"): errmsg += "\nTip for users in a restricted shell on the " \ "server: The server may refuse to run programs " \ "specified with an absolute path. Try specifying " \ "just the name of the python executable. However, " \ "if python is not in your PATH and you cannot " \ "run programs specified with an absolute path, " \ "it is possible that tshuttle will not work." else: errmsg += "This error code likely means that we were unable " \ "to execute /bin/sh on the remote server. This can " \ "happen if /bin/sh does not exist on the server or if " \ "you are in a restricted shell that does not allow you " \ "to run programs specified with an absolute path. " \ "Try rerunning tshuttle with the --python parameter." # When the redirected subnet includes the remote ssh host, the # firewall rules can interrupt the ssh connection to the # remote machine. This issue impacts some Linux machines. The # user sees that the server dies with a broken pipe error and # code 255. # # The solution to this problem is to exclude the remote # server. # # There are many github issues from users encountering this # problem. Most of the discussion on the topic is here: # https://github.com/tshuttle/tshuttle/issues/191 elif rv == 255: errmsg += "It might be possible to resolve this error by " \ "excluding the server that you are ssh'ing to. For example, " \ "if you are running 'tshuttle -v -r example.com 0/0' to " \ "redirect all traffic through example.com, then try " \ "'tshuttle -v -r example.com -x example.com 0/0' to " \ "exclude redirecting the connection to example.com itself " \ "(i.e., tshuttle's firewall rules may be breaking the " \ "ssh connection that it previously established). " \ "Alternatively, you may be able to use 'tshuttle -v -r " \ "example.com -x example.com:22 0/0' to redirect " \ "everything except ssh connections between your machine " \ "and example.com." raise Fatal(errmsg) if initstring != expected: raise Fatal('expected server init string %r; got %r' % (expected, initstring)) log('Connected to server.') sys.stdout.flush() if daemon: daemonize() log('daemonizing (%s).' % _pidname) def onroutes(routestr): if auto_nets: for line in routestr.strip().split(b'\n'): if not line: continue (family, ip, width) = line.split(b',', 2) family = int(family) width = int(width) ip = ip.decode("ASCII") if family == socket.AF_INET6 and tcp_listener.v6 is None: debug2("Ignored auto net %d/%s/%d" % (family, ip, width)) if family == socket.AF_INET and tcp_listener.v4 is None: debug2("Ignored auto net %d/%s/%d" % (family, ip, width)) else: debug2("Adding auto net %d/%s/%d" % (family, ip, width)) fw.auto_nets.append((family, ip, width, 0, 0)) # we definitely want to do this *after* starting ssh, or we might end # up intercepting the ssh connection! # # Moreover, now that we have the --auto-nets option, we have to wait # for the server to send us that message anyway. Even if we haven't # set --auto-nets, we might as well wait for the message first, then # ignore its contents. mux.got_routes = None serverready() mux.got_routes = onroutes def serverready(): fw.start() sdnotify.send(sdnotify.ready(), sdnotify.status('Connected')) def onhostlist(hostlist): debug2('got host list: %r' % hostlist) for line in hostlist.strip().split(): if line: name, ip = line.split(b',', 1) fw.sethostip(name, ip) mux.got_host_list = onhostlist tcp_listener.add_handler(handlers, onaccept_tcp, method, mux) if udp_listener: udp_listener.add_handler(handlers, onaccept_udp, method, mux) if dns_listener: dns_listener.add_handler(handlers, ondns, method, mux) if seed_hosts is not None: debug1('seed_hosts: %r' % seed_hosts) mux.send(0, ssnet.CMD_HOST_REQ, str.encode('\n'.join(seed_hosts))) def check_ssh_alive(): if daemon: # poll() won't tell us when process exited since the # process is no longer our child (it returns 0 all the # time). if not psutil.pid_exists(serverproc.pid): raise Fatal('ssh connection to server (pid %d) exited.' % serverproc.pid) else: rv = serverproc.poll() # poll returns None if process hasn't exited. if rv is not None: raise Fatal('ssh connection to server (pid %d) exited ' 'with returncode %d' % (serverproc.pid, rv)) while 1: check_ssh_alive() ssnet.runonce(handlers, mux) if latency_control: mux.check_fullness()
def ipfw_noexit(*args): argv = ['ipfw', '-q'] + list(args) debug1('>> %s' % ' '.join(argv)) ssubprocess.call(argv, env=get_env())
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 main(method_name, syslog): helpers.logprefix = 'fw: ' stdin, stdout = setup_daemon() hostmap = {} debug1('Starting firewall with Python version %s' % platform.python_version()) if method_name == "auto": method = get_auto_method() else: method = get_method(method_name) if syslog: ssyslog.start_syslog() ssyslog.stderr_to_syslog() if not method.is_supported(): raise Fatal("The %s method is not supported on this machine. " "Check that the appropriate programs are in your " "PATH." % method_name) debug1('ready method name %s.' % method.name) stdout.write('READY %s\n' % method.name) stdout.flush() # we wait until we get some input before creating the rules. That way, # tshuttle can launch us as early as possible (and get sudo password # authentication as early in the startup process as possible). line = stdin.readline(128) if not line: return # parent died; nothing to do subnets = [] if line != 'ROUTES\n': raise Fatal('expected ROUTES but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('expected route but got %r' % line) elif line.startswith("NSLIST\n"): break try: (family, width, exclude, ip, fport, lport) = \ line.strip().split(',', 5) except BaseException: raise Fatal('expected route or NSLIST but got %r' % line) subnets.append((int(family), int(width), bool(int(exclude)), ip, int(fport), int(lport))) debug2('Got subnets: %r' % subnets) nslist = [] if line != 'NSLIST\n': raise Fatal('expected NSLIST but got %r' % line) while 1: line = stdin.readline(128) if not line: raise Fatal('expected nslist but got %r' % line) elif line.startswith("PORTS "): break try: (family, ip) = line.strip().split(',', 1) except BaseException: raise Fatal('expected nslist or PORTS but got %r' % line) nslist.append((int(family), ip)) debug2('Got partial nslist: %r' % nslist) debug2('Got nslist: %r' % nslist) if not line.startswith('PORTS '): raise Fatal('expected PORTS but got %r' % line) _, _, ports = line.partition(" ") ports = ports.split(",") if len(ports) != 4: raise Fatal('expected 4 ports but got %d' % len(ports)) port_v6 = int(ports[0]) port_v4 = int(ports[1]) dnsport_v6 = int(ports[2]) dnsport_v4 = int(ports[3]) assert (port_v6 >= 0) assert (port_v6 <= 65535) assert (port_v4 >= 0) assert (port_v4 <= 65535) assert (dnsport_v6 >= 0) assert (dnsport_v6 <= 65535) assert (dnsport_v4 >= 0) assert (dnsport_v4 <= 65535) debug2('Got ports: %d,%d,%d,%d' % (port_v6, port_v4, dnsport_v6, dnsport_v4)) line = stdin.readline(128) if not line: raise Fatal('expected GO but got %r' % line) elif not line.startswith("GO "): raise Fatal('expected GO but got %r' % line) _, _, args = line.partition(" ") udp, user = args.strip().split(" ", 1) udp = bool(int(udp)) if user == '-': user = None debug2('Got udp: %r, user: %r' % (udp, user)) subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6] nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6] subnets_v4 = [i for i in subnets if i[0] == socket.AF_INET] nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET] try: debug1('setting up.') if subnets_v6 or nslist_v6: debug2('setting up IPv6.') method.setup_firewall(port_v6, dnsport_v6, nslist_v6, socket.AF_INET6, subnets_v6, udp, user) if subnets_v4 or nslist_v4: debug2('setting up IPv4.') method.setup_firewall(port_v4, dnsport_v4, nslist_v4, socket.AF_INET, subnets_v4, udp, user) stdout.write('STARTED\n') try: stdout.flush() except IOError: # the parent process died for some reason; he's surely been loud # enough, so no reason to report another error return # Now we wait until EOF or any other kind of exception. We need # to stay running so that we don't need a *second* password # authentication at shutdown time - that cleanup is important! while 1: line = stdin.readline(128) if line.startswith('HOST '): (name, ip) = line[5:].strip().split(',', 1) hostmap[name] = ip debug2('setting up /etc/hosts.') rewrite_etc_hosts(hostmap, port_v6 or port_v4) elif line: if not method.firewall_command(line): raise Fatal('expected command, got %r' % line) else: break finally: try: debug1('undoing changes.') except BaseException: debug2('An error occurred, ignoring it.') try: if subnets_v6 or nslist_v6: debug2('undoing IPv6 changes.') method.restore_firewall(port_v6, socket.AF_INET6, udp, user) except BaseException: try: debug1("Error trying to undo IPv6 firewall.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.') try: if subnets_v4 or nslist_v4: debug2('undoing IPv4 changes.') method.restore_firewall(port_v4, socket.AF_INET, udp, user) except BaseException: try: debug1("Error trying to undo IPv4 firewall.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.') try: # debug2() message printed in restore_etc_hosts() function. restore_etc_hosts(hostmap, port_v6 or port_v4) except BaseException: try: debug1("Error trying to undo /etc/hosts changes.") debug1(traceback.format_exc()) except BaseException: debug2('An error occurred, ignoring it.')
def __del__(self): global _swcount _swcount -= 1 debug1('%r: deleting (%d remain)' % (self, _swcount)) if self.exc: debug1('%r: error was: %s' % (self, self.exc))
def _sysctl_set(name, val): argv = ['sysctl', '-w', '%s=%s' % (name, val)] debug1('>> %s' % ' '.join(argv)) return ssubprocess.call(argv, stdout=open(os.devnull, 'w'), env=get_env())
def main(listenip_v6, listenip_v4, ssh_cmd, remotename, python, latency_control, latency_buffer_size, dns, nslist, method_name, seed_hosts, auto_hosts, auto_nets, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, user, sudo_pythonpath, tmark): if not remotename: print("WARNING: You must specify -r/--remote to securely route " "traffic to a remote machine. Running without -r/--remote " "is only recommended for testing.") if daemon: try: check_daemon(pidfile) except Fatal as e: log("%s" % e) return 5 debug1('Starting tshuttle proxy (version %s).' % __version__) helpers.logprefix = 'c : ' fw = FirewallClient(method_name, sudo_pythonpath) # If --dns is used, store the IP addresses that the client # normally uses for DNS lookups in nslist. The firewall needs to # redirect packets outgoing to this server to the remote host # instead. if dns: nslist += resolvconf_nameservers(True) if to_nameserver is not None: to_nameserver = "%s@%s" % tuple(to_nameserver[1:]) else: # option doesn't make sense if we aren't proxying dns if to_nameserver and len(to_nameserver) > 0: print("WARNING: --to-ns option is ignored because --dns was not " "used.") to_nameserver = None # Get family specific subnet lists. Also, the user may not specify # any subnets if they use --auto-nets. In this case, our subnets # list will be empty and the forwarded subnets will be determined # later by the server. subnets_v4 = [i for i in subnets_include if i[0] == socket.AF_INET] subnets_v6 = [i for i in subnets_include if i[0] == socket.AF_INET6] nslist_v4 = [i for i in nslist if i[0] == socket.AF_INET] nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6] # Get available features from the firewall method avail = fw.method.get_supported_features() # A feature is "required" if the user supplies us parameters which # implies that the feature is needed. required = Features() # Select the default addresses to bind to / listen to. # Assume IPv4 is always available and should always be enabled. If # a method doesn't provide IPv4 support or if we wish to run # ipv6-only, changes to this code are required. assert avail.ipv4 required.ipv4 = True # listenip_v4 contains user specified value or it is set to "auto". if listenip_v4 == "auto": listenip_v4 = ('127.0.0.1', 0) # listenip_v6 is... # None when IPv6 is disabled. # "auto" when listen address is unspecified. # The user specified address if provided by user if listenip_v6 is None: debug1("IPv6 disabled by --disable-ipv6") if listenip_v6 == "auto": if avail.ipv6: debug1("IPv6 enabled: Using default IPv6 listen address ::1") listenip_v6 = ('::1', 0) else: debug1("IPv6 disabled since it isn't supported by method " "%s." % fw.method.name) listenip_v6 = None # Make final decision about enabling IPv6: required.ipv6 = False if listenip_v6: required.ipv6 = True # If we get here, it is possible that listenip_v6 was user # specified but not supported by the current method. if required.ipv6 and not avail.ipv6: raise Fatal("An IPv6 listen address was supplied, but IPv6 is " "disabled at your request or is unsupported by the %s " "method." % fw.method.name) if user is not None: if getpwnam is None: raise Fatal("Routing by user not available on this system.") try: user = getpwnam(user).pw_uid except KeyError: raise Fatal("User %s does not exist." % user) required.user = False if user is None else True if not required.ipv6 and len(subnets_v6) > 0: print("WARNING: IPv6 subnets were ignored because IPv6 is disabled " "in tshuttle.") subnets_v6 = [] subnets_include = subnets_v4 required.udp = avail.udp # automatically enable UDP if it is available required.dns = len(nslist) > 0 # Remove DNS servers using IPv6. if required.dns: if not required.ipv6 and len(nslist_v6) > 0: print("WARNING: Your system is configured to use an IPv6 DNS " "server but tshuttle is not using IPv6. Therefore DNS " "traffic your system sends to the IPv6 DNS server won't " "be redirected via tshuttle to the remote machine.") nslist_v6 = [] nslist = nslist_v4 if len(nslist) == 0: raise Fatal("Can't redirect DNS traffic since IPv6 is not " "enabled in tshuttle and all of the system DNS " "servers are IPv6.") # If we aren't using IPv6, we can safely ignore excluded IPv6 subnets. if not required.ipv6: orig_len = len(subnets_exclude) subnets_exclude = [ i for i in subnets_exclude if i[0] == socket.AF_INET ] if len(subnets_exclude) < orig_len: print("WARNING: Ignoring one or more excluded IPv6 subnets " "because IPv6 is not enabled.") # This will print error messages if we required a feature that # isn't available by the current method. fw.method.assert_features(required) # display features enabled def feature_status(label, enabled, available): msg = label + ": " if enabled: msg += "on" else: msg += "off " if available: msg += "(available)" else: msg += "(not available with %s method)" % fw.method.name debug1(msg) debug1("Method: %s" % fw.method.name) feature_status("IPv4", required.ipv4, avail.ipv4) feature_status("IPv6", required.ipv6, avail.ipv6) feature_status("UDP ", required.udp, avail.udp) feature_status("DNS ", required.dns, avail.dns) feature_status("User", required.user, avail.user) # Exclude traffic destined to our listen addresses. if required.ipv4 and \ not any(listenip_v4[0] == sex[1] for sex in subnets_v4): subnets_exclude.append((socket.AF_INET, listenip_v4[0], 32, 0, 0)) if required.ipv6 and \ not any(listenip_v6[0] == sex[1] for sex in subnets_v6): subnets_exclude.append((socket.AF_INET6, listenip_v6[0], 128, 0, 0)) # We don't print the IP+port of where we are listening here # because we do that below when we have identified the ports to # listen on. debug1("Subnets to forward through remote host (type, IP, cidr mask " "width, startPort, endPort):") for i in subnets_include: debug1(" " + str(i)) if auto_nets: debug1("NOTE: Additional subnets to forward may be added below by " "--auto-nets.") debug1("Subnets to exclude from forwarding:") for i in subnets_exclude: debug1(" " + str(i)) if required.dns: debug1("DNS requests normally directed at these servers will be " "redirected to remote:") for i in nslist: debug1(" " + str(i)) if listenip_v6 and listenip_v6[1] and listenip_v4 and listenip_v4[1]: # if both ports given, no need to search for a spare port ports = [ 0, ] else: # if at least one port missing, we have to search ports = range(12300, 9000, -1) # keep track of failed bindings and used ports to avoid trying to # bind to the same socket address twice in different listeners used_ports = [] # search for free ports and try to bind last_e = None redirectport_v6 = 0 redirectport_v4 = 0 bound = False for port in ports: debug2('Trying to bind redirector on port %d' % port) tcp_listener = MultiListener() if required.udp: udp_listener = MultiListener(socket.SOCK_DGRAM) else: udp_listener = None if listenip_v6 and listenip_v6[1]: lv6 = listenip_v6 redirectport_v6 = lv6[1] elif listenip_v6: lv6 = (listenip_v6[0], port) redirectport_v6 = port else: lv6 = None redirectport_v6 = 0 if listenip_v4 and listenip_v4[1]: lv4 = listenip_v4 redirectport_v4 = lv4[1] elif listenip_v4: lv4 = (listenip_v4[0], port) redirectport_v4 = port else: lv4 = None redirectport_v4 = 0 try: tcp_listener.bind(lv6, lv4) if udp_listener: udp_listener.bind(lv6, lv4) bound = True used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) else: raise e if not bound: assert (last_e) raise last_e tcp_listener.listen(10) tcp_listener.print_listening("TCP redirector") if udp_listener: udp_listener.print_listening("UDP redirector") bound = False if required.dns: # search for spare port for DNS ports = range(12300, 9000, -1) for port in ports: debug2('Trying to bind DNS redirector on port %d' % port) if port in used_ports: continue dns_listener = MultiListener(socket.SOCK_DGRAM) if listenip_v6: lv6 = (listenip_v6[0], port) dnsport_v6 = port else: lv6 = None dnsport_v6 = 0 if listenip_v4: lv4 = (listenip_v4[0], port) dnsport_v4 = port else: lv4 = None dnsport_v4 = 0 try: dns_listener.bind(lv6, lv4) bound = True used_ports.append(port) break except socket.error as e: if e.errno == errno.EADDRINUSE: last_e = e used_ports.append(port) else: raise e dns_listener.print_listening("DNS") if not bound: assert (last_e) raise last_e else: dnsport_v6 = 0 dnsport_v4 = 0 dns_listener = None # Last minute sanity checks. # These should never fail. # If these do fail, something is broken above. if subnets_v6: assert required.ipv6 if redirectport_v6 == 0: raise Fatal("IPv6 subnets defined but not listening") if nslist_v6: assert required.dns assert required.ipv6 if dnsport_v6 == 0: raise Fatal("IPv6 ns servers defined but not listening") if subnets_v4: if redirectport_v4 == 0: raise Fatal("IPv4 subnets defined but not listening") if nslist_v4: if dnsport_v4 == 0: raise Fatal("IPv4 ns servers defined but not listening") # setup method specific stuff on listeners fw.method.setup_tcp_listener(tcp_listener) if udp_listener: fw.method.setup_udp_listener(udp_listener) if dns_listener: fw.method.setup_udp_listener(dns_listener) # start the firewall fw.setup(subnets_include, subnets_exclude, nslist, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, required.udp, user, tmark) # Add routes to avoid Cico VPN issue for subnet_info in subnets_include: cidr = subnet_info[1] + "/" + str(subnet_info[2]) try: ssubprocess.run('sudo route -n add -net ' + cidr + ' -interface en0', check=True, shell=True) except ssubprocess.CalledProcessError: print('Failed to add ' + cidr) # start the client process try: return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename, python, latency_control, latency_buffer_size, dns_listener, seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver) finally: try: if daemon: # it's not our child anymore; can't waitpid fw.p.returncode = 0 fw.done() sdnotify.send(sdnotify.stop()) finally: if daemon: daemon_cleanup()