From 1885974f52bdef2cc1c1b6e5c1ea36af16343b86 Mon Sep 17 00:00:00 2001 From: nom3ad <19239479+nom3ad@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:13:06 +0530 Subject: [PATCH] refactor for future ipv6 support --- hack/compose.yml | 14 ++++++++-- hack/exec-sshuttle | 24 ++++++++++------- hack/exec-tool | 50 ++++++++++++++++++++++++++++------- hack/setup.service | 2 +- sshuttle/methods/windivert.py | 24 ++++++++++------- 5 files changed, 82 insertions(+), 32 deletions(-) diff --git a/hack/compose.yml b/hack/compose.yml index cd086e8..5bdb4e5 100644 --- a/hack/compose.yml +++ b/hack/compose.yml @@ -8,7 +8,10 @@ services: cap_add: - "NET_ADMIN" environment: - - IP_ADDRESSES=10.55.1.77/24 + - ADD_IP_ADDRESSES=10.55.1.77/24 + networks: + default: + ipv6_address: 2001:0DB8::551 node-2: image: ghcr.io/sshuttle/sshuttle-testbed container_name: sshuttle-testbed-node-2 @@ -16,9 +19,16 @@ services: cap_add: - "NET_ADMIN" environment: - - IP_ADDRESSES=10.55.2.77/32 + - ADD_IP_ADDRESSES=10.55.2.77/32 + networks: + default: + ipv6_address: 2001:0DB8::552 networks: default: driver: bridge + enable_ipv6: true + ipam: + config: + - subnet: 2001:0DB8::/112 # internal: true \ No newline at end of file diff --git a/hack/exec-sshuttle b/hack/exec-sshuttle index 0a82cfb..3412342 100755 --- a/hack/exec-sshuttle +++ b/hack/exec-sshuttle @@ -11,7 +11,6 @@ function with_set_x() { } 2>/dev/null } - ssh_cmd='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' args=() while [[ $# -gt 0 ]]; do @@ -24,13 +23,16 @@ while [[ $# -gt 0 ]]; do --copy-id) ssh_copy_id=true continue - ;; + ;; + -6) + ipv6_only=true + continue + ;; --sshuttle-bin=*) sshuttle_bin="${arg#*=}" continue - ;; - -*) ;; + -*) ;; *) if [[ -z "$node" ]]; then node=$arg @@ -41,19 +43,22 @@ while [[ $# -gt 0 ]]; do args+=("$arg") done - port="2222" user="test:test" if [[ $node == node-* ]]; then host=$("$(dirname "$0")/test-bed" get-ip "$node") index=${node#node-} - args+=("10.55.$index.0/24") + if [[ $ipv6_only == true ]]; then + args+=("2001:0DB8::/112") + else + args+=("10.55.$index.0/24") + fi else host=$node fi -if [[ "${args[$(( ${#args[@]} - 1 ))]}" != *.* && "${args[$(( ${#args[@]} - 1 ))]}" != *:* ]]; then +if [[ "${args[$((${#args[@]} - 1))]}" != *.* && "${args[$((${#args[@]} - 1))]}" != *:* ]]; then echo "No subnet specified. Using -N" >&2 args+=('-N') fi @@ -68,7 +73,7 @@ if [[ $ssh_copy_id == true ]]; then with_set_x ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p "$port" "$user@$host" fi -if [[ -z $sshuttle_bin || "$sshuttle_bin" == dev ]]; then +if [[ -z $sshuttle_bin || "$sshuttle_bin" == dev ]]; then cd "$(dirname "$0")/.." export PYTHONPATH="." sshuttle_bin="./run" @@ -76,5 +81,4 @@ fi set -x -exec "${sshuttle_bin}" -r "$user@$host:$port" --ssh-cmd "$ssh_cmd" "${args[@]}" - +exec "${sshuttle_bin}" -r "$user@$host:$port" --ssh-cmd "$ssh_cmd" "${args[@]}" diff --git a/hack/exec-tool b/hack/exec-tool index 22f67bf..fc4eedc 100755 --- a/hack/exec-tool +++ b/hack/exec-tool @@ -1,13 +1,39 @@ #!/usr/bin/env bash set -e -tool=${1?:"tool argument missing. should be one of iperf3,ping,curl,ab"} -node=${2?:"node argument missing. should be 'node-1' , 'node-2' etc"} -shift 2 +args=() +while [[ $# -gt 0 ]]; do + arg=$1 + shift + case "$arg" in + -6) + ipv6_only=true + continue + ;; + -*) ;; + *) + if [[ -z $tool ]]; then + tool=$arg + continue + elif [[ -z $node ]]; then + node=$arg + continue + fi + ;; + esac + args+=("$arg") +done + +tool=${tool?:"tool argument missing. should be one of iperf3,ping,curl,ab"} +node=${node?:"node argument missing. should be 'node-1' , 'node-2' etc"} if [[ $node == node-* ]]; then index=${node#node-} - host="10.55.$index.77" + if [[ $ipv6_only == true ]]; then + host="2001:0DB8::55$index" + else + host="10.55.$index.77" + fi else host=$node fi @@ -26,22 +52,26 @@ function with_set_x() { case "$tool" in ping) - with_set_x exec ping -W $connect_timeout_sec "$@" "$host" + with_set_x exec ping -W $connect_timeout_sec "${args[@]}" "$host" ;; iperf3) port=5001 - with_set_x exec iperf3 --client "$host" --port=$port --connect-timeout=$((connect_timeout_sec * 1000)) "$@" + with_set_x exec iperf3 --client "$host" --port=$port --connect-timeout=$((connect_timeout_sec * 1000)) "${args[@]}" ;; curl) port=8080 - with_set_x exec curl "http://$host:$port/" -v --connect-timeout $connect_timeout_sec "$@" + if [[ $host = *:* ]]; then + host="[$host]" + args+=(--ipv6) + fi + with_set_x exec curl "http://$host:$port/" -v --connect-timeout $connect_timeout_sec "${args[@]}" ;; ab) port=8080 - if [[ " $*" != *" -n "* && " $*" != *" -c "* ]]; then - set -- -n 500 -c 50 "$@" + if [[ " ${args[*]}" != *" -n "* && " ${args[*]}" != *" -c "* ]]; then + args+=(-n 500 -c 50 "${args[@]}") fi - with_set_x exec ab -s $connect_timeout_sec "$@" "http://$host:$port/" + with_set_x exec ab -s $connect_timeout_sec "${args[@]}" "http://$host:$port/" ;; *) echo "Unknown tool: $tool" >&2 diff --git a/hack/setup.service b/hack/setup.service index 4e36124..fb49353 100755 --- a/hack/setup.service +++ b/hack/setup.service @@ -17,7 +17,7 @@ function with_set_x() { iface="$(ip route | awk '/default/ { print $5 }')" default_gw="$(ip route | awk '/default/ { print $3 }')" -for addr in ${IP_ADDRESSES//,/ }; do +for addr in ${ADD_IP_ADDRESSES//,/ }; do echo ">>> Adding $addr to interface $iface" net_addr=$(ipcalc -n "$addr" | awk -F= '{print $2}') with_set_x ip addr add "$addr" dev "$iface" diff --git a/sshuttle/methods/windivert.py b/sshuttle/methods/windivert.py index b6f6a95..389c3b7 100644 --- a/sshuttle/methods/windivert.py +++ b/sshuttle/methods/windivert.py @@ -255,8 +255,8 @@ class ConnTrack: state_epoch, state, ) = self.struct_full_tuple.unpack(packed) - dst_addr = str(ip_address(dst_addr_packed if ip_version == 6 else dst_addr_packed[:4])) - src_addr = str(ip_address(src_addr_packed if ip_version == 6 else src_addr_packed[:4])) + dst_addr = ip_address(dst_addr_packed if ip_version == 6 else dst_addr_packed[:4]).exploded + src_addr = ip_address(src_addr_packed if ip_version == 6 else src_addr_packed[:4]).exploded return ConnectionTuple( IPProtocol(proto), ip_version, src_addr, src_port, dst_addr, dst_port, state_epoch, ConnState(state) ) @@ -306,14 +306,15 @@ class Method(BaseMethod): # As a workaround, finding another interface ip instead. (client should not bind proxy to loopback address) local_addr = self._get_bind_addresses_for_port(proxy_port, family) for addr in (ip_address(info[4][0]) for info in socket.getaddrinfo(socket.gethostname(), None)): - if addr.is_loopback or addr.version != family.version: + if addr.version != family.version or addr.is_loopback or addr.is_link_local: continue if local_addr.is_unspecified or local_addr == addr: - debug2("Found non loopback address to connect to proxy: " + str(addr)) - proxy_ip = str(addr) + proxy_ip = addr.exploded + debug2("Found non loopback address to connect to proxy: " + proxy_ip) break else: - raise Fatal("Windivert method requires proxy to listen on a non loopback address") + raise Fatal("Windivert method requires proxy to be reachable by a non loopback address." + f"No addersss found for {family.name}") subnet_addresses = [] for (_, mask, exclude, network_addr, fport, lport) in subnets: @@ -388,15 +389,18 @@ class Method(BaseMethod): subnet_filters = [] for cidr in c["subnets"]: ip_net = ip_network(cidr) - first_ip = ip_net.network_address - last_ip = ip_net.broadcast_address + first_ip = ip_net.network_address.exploded + last_ip = ip_net.broadcast_address.exploded subnet_filters.append(f"({af.filter}.DstAddr>={first_ip} and {af.filter}.DstAddr<={last_ip})") + if not subnet_filters: + continue proxy_ip, proxy_port = c["proxy_addr"] proxy_guard_filter = f'({af.filter}.DstAddr!={proxy_ip} or tcp.DstPort!={proxy_port})' family_filters.append(f"{af.filter} and ({' or '.join(subnet_filters)}) and {proxy_guard_filter}") + if not family_filters: + raise Fatal("At least one ipv4 or ipv6 subnet is expected") filter = f"{filter} and ({' or '.join(family_filters)})" - debug1(f"[EGRESS] {filter=}") with pydivert.WinDivert(filter, layer=pydivert.Layer.NETWORK, flags=pydivert.Flag.DEFAULT) as w: ready_cb() @@ -444,6 +448,8 @@ class Method(BaseMethod): direction = "outbound" proxy_addr_filters = [] for af, c in self.network_config.items(): + if not c["subnets"]: + continue proxy_ip, proxy_port = c["proxy_addr"] # "ip.SrcAddr=={hex(int(proxy_ip))}" # only Windivert >=2 supports this proxy_addr_filters.append(f"{af.filter}.SrcAddr=={proxy_ip} and tcp.SrcPort=={proxy_port}")