refactor for future ipv6 support

This commit is contained in:
nom3ad 2024-01-02 23:13:06 +05:30 committed by Brian May
parent dadfba488b
commit 1885974f52
5 changed files with 82 additions and 32 deletions

View File

@ -8,7 +8,10 @@ services:
cap_add: cap_add:
- "NET_ADMIN" - "NET_ADMIN"
environment: 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: node-2:
image: ghcr.io/sshuttle/sshuttle-testbed image: ghcr.io/sshuttle/sshuttle-testbed
container_name: sshuttle-testbed-node-2 container_name: sshuttle-testbed-node-2
@ -16,9 +19,16 @@ services:
cap_add: cap_add:
- "NET_ADMIN" - "NET_ADMIN"
environment: environment:
- IP_ADDRESSES=10.55.2.77/32 - ADD_IP_ADDRESSES=10.55.2.77/32
networks:
default:
ipv6_address: 2001:0DB8::552
networks: networks:
default: default:
driver: bridge driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: 2001:0DB8::/112
# internal: true # internal: true

View File

@ -11,7 +11,6 @@ function with_set_x() {
} 2>/dev/null } 2>/dev/null
} }
ssh_cmd='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' ssh_cmd='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
args=() args=()
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@ -25,12 +24,15 @@ while [[ $# -gt 0 ]]; do
ssh_copy_id=true ssh_copy_id=true
continue continue
;; ;;
-6)
ipv6_only=true
continue
;;
--sshuttle-bin=*) --sshuttle-bin=*)
sshuttle_bin="${arg#*=}" sshuttle_bin="${arg#*=}"
continue continue
;; ;;
-*) -*) ;;
;;
*) *)
if [[ -z "$node" ]]; then if [[ -z "$node" ]]; then
node=$arg node=$arg
@ -41,14 +43,17 @@ while [[ $# -gt 0 ]]; do
args+=("$arg") args+=("$arg")
done done
port="2222" port="2222"
user="test:test" user="test:test"
if [[ $node == node-* ]]; then if [[ $node == node-* ]]; then
host=$("$(dirname "$0")/test-bed" get-ip "$node") host=$("$(dirname "$0")/test-bed" get-ip "$node")
index=${node#node-} index=${node#node-}
if [[ $ipv6_only == true ]]; then
args+=("2001:0DB8::/112")
else
args+=("10.55.$index.0/24") args+=("10.55.$index.0/24")
fi
else else
host=$node host=$node
fi fi
@ -77,4 +82,3 @@ fi
set -x 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[@]}"

View File

@ -1,13 +1,39 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
tool=${1?:"tool argument missing. should be one of iperf3,ping,curl,ab"} args=()
node=${2?:"node argument missing. should be 'node-1' , 'node-2' etc"} while [[ $# -gt 0 ]]; do
shift 2 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 if [[ $node == node-* ]]; then
index=${node#node-} index=${node#node-}
if [[ $ipv6_only == true ]]; then
host="2001:0DB8::55$index"
else
host="10.55.$index.77" host="10.55.$index.77"
fi
else else
host=$node host=$node
fi fi
@ -26,22 +52,26 @@ function with_set_x() {
case "$tool" in case "$tool" in
ping) ping)
with_set_x exec ping -W $connect_timeout_sec "$@" "$host" with_set_x exec ping -W $connect_timeout_sec "${args[@]}" "$host"
;; ;;
iperf3) iperf3)
port=5001 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) curl)
port=8080 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) ab)
port=8080 port=8080
if [[ " $*" != *" -n "* && " $*" != *" -c "* ]]; then if [[ " ${args[*]}" != *" -n "* && " ${args[*]}" != *" -c "* ]]; then
set -- -n 500 -c 50 "$@" args+=(-n 500 -c 50 "${args[@]}")
fi 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 echo "Unknown tool: $tool" >&2

View File

@ -17,7 +17,7 @@ function with_set_x() {
iface="$(ip route | awk '/default/ { print $5 }')" iface="$(ip route | awk '/default/ { print $5 }')"
default_gw="$(ip route | awk '/default/ { print $3 }')" 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" echo ">>> Adding $addr to interface $iface"
net_addr=$(ipcalc -n "$addr" | awk -F= '{print $2}') net_addr=$(ipcalc -n "$addr" | awk -F= '{print $2}')
with_set_x ip addr add "$addr" dev "$iface" with_set_x ip addr add "$addr" dev "$iface"

View File

@ -255,8 +255,8 @@ class ConnTrack:
state_epoch, state_epoch,
state, state,
) = self.struct_full_tuple.unpack(packed) ) = self.struct_full_tuple.unpack(packed)
dst_addr = str(ip_address(dst_addr_packed if ip_version == 6 else dst_addr_packed[:4])) dst_addr = ip_address(dst_addr_packed if ip_version == 6 else dst_addr_packed[:4]).exploded
src_addr = str(ip_address(src_addr_packed if ip_version == 6 else src_addr_packed[:4])) src_addr = ip_address(src_addr_packed if ip_version == 6 else src_addr_packed[:4]).exploded
return ConnectionTuple( return ConnectionTuple(
IPProtocol(proto), ip_version, src_addr, src_port, dst_addr, dst_port, state_epoch, ConnState(state) 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) # 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) 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)): 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 continue
if local_addr.is_unspecified or local_addr == addr: if local_addr.is_unspecified or local_addr == addr:
debug2("Found non loopback address to connect to proxy: " + str(addr)) proxy_ip = addr.exploded
proxy_ip = str(addr) debug2("Found non loopback address to connect to proxy: " + proxy_ip)
break break
else: 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 = [] subnet_addresses = []
for (_, mask, exclude, network_addr, fport, lport) in subnets: for (_, mask, exclude, network_addr, fport, lport) in subnets:
@ -388,15 +389,18 @@ class Method(BaseMethod):
subnet_filters = [] subnet_filters = []
for cidr in c["subnets"]: for cidr in c["subnets"]:
ip_net = ip_network(cidr) ip_net = ip_network(cidr)
first_ip = ip_net.network_address first_ip = ip_net.network_address.exploded
last_ip = ip_net.broadcast_address last_ip = ip_net.broadcast_address.exploded
subnet_filters.append(f"({af.filter}.DstAddr>={first_ip} and {af.filter}.DstAddr<={last_ip})") 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_ip, proxy_port = c["proxy_addr"]
proxy_guard_filter = f'({af.filter}.DstAddr!={proxy_ip} or tcp.DstPort!={proxy_port})' 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}") 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)})" filter = f"{filter} and ({' or '.join(family_filters)})"
debug1(f"[EGRESS] {filter=}") debug1(f"[EGRESS] {filter=}")
with pydivert.WinDivert(filter, layer=pydivert.Layer.NETWORK, flags=pydivert.Flag.DEFAULT) as w: with pydivert.WinDivert(filter, layer=pydivert.Layer.NETWORK, flags=pydivert.Flag.DEFAULT) as w:
ready_cb() ready_cb()
@ -444,6 +448,8 @@ class Method(BaseMethod):
direction = "outbound" direction = "outbound"
proxy_addr_filters = [] proxy_addr_filters = []
for af, c in self.network_config.items(): for af, c in self.network_config.items():
if not c["subnets"]:
continue
proxy_ip, proxy_port = c["proxy_addr"] proxy_ip, proxy_port = c["proxy_addr"]
# "ip.SrcAddr=={hex(int(proxy_ip))}" # only Windivert >=2 supports this # "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}") proxy_addr_filters.append(f"{af.filter}.SrcAddr=={proxy_ip} and tcp.SrcPort=={proxy_port}")