Repository: sshuttle/sshuttle (Python)

Issue:
Fails to connect with missing user
Connecting to a remove host without specifying a username fails under some circumstances.

I'm not sure WHEN this changed (recently did system upgrade and got new python version), I'm however able to reproduce this on both Python 3.7.8 and 3.8.3.

Steps to reproduce:
`sshuttle --remote 127.0.0.1:22 0.0.0.0`

Expected behavior:
A connection to localhost is established

Actual behavior:
A traceback is shown

Debug output:
```
Traceback (most recent call last):
  File "... .venv/bin/sshuttle", line 8, in <module>
    sys.exit(main())
  File "... .venv/lib64/python3.7/site-packages/sshuttle/cmdline.py", line 102, in main
    opt.sudo_pythonpath)
  File "... .venv/lib64/python3.7/site-packages/sshuttle/client.py", line 795, in main
    seed_hosts, auto_hosts, auto_nets, daemon, to_nameserver)
  File "... .venv/lib64/python3.7/site-packages/sshuttle/client.py", line 453, in _main
    auto_nets=auto_nets))
  File "... .venv/lib64/python3.7/site-packages/sshuttle/ssh.py", line 152, in connect
    close_fds=True, stderr=stderr)
  File "/usr/lib64/python3.7/subprocess.py", line 800, in __init__
    restore_signals, start_new_session)
  File "/usr/lib64/python3.7/subprocess.py", line 1482, in _execute_child
    restore_signals, start_new_session, preexec_fn)
TypeError: expected str, bytes or os.PathLike object, not IPv4Address
```


Discussion:

Patch:
diff --git a/sshuttle/ssh.py b/sshuttle/ssh.py
index c93aa8cb2..9313215fb 100644
--- a/sshuttle/ssh.py
+++ b/sshuttle/ssh.py
@@ -65,12 +65,12 @@ def parse_hostport(rhostport):
         try:
             # try to parse host as an IP adress,
             # if that works it is an IPv6 address
-            host = ipaddress.ip_address(host)
+            host = str(ipaddress.ip_address(host))
         except ValueError:
             # if that fails parse as URL to get the port
             parsed = urlparse('//{}'.format(host))
             try:
-                host = ipaddress.ip_address(parsed.hostname)
+                host = str(ipaddress.ip_address(parsed.hostname))
             except ValueError:
                 # else if both fails, we have a hostname with port
                 host = parsed.hostname


Test patch:
diff --git a/tests/ssh/test_parse_hostport.py b/tests/ssh/test_parse_hostport.py
new file mode 100644
index 000000000..759bb0788
--- /dev/null
+++ b/tests/ssh/test_parse_hostport.py
@@ -0,0 +1,20 @@
+from sshuttle.ssh import parse_hostport
+
+
+def test_host_only():
+    assert parse_hostport("host") == (None, None, None, "host")
+    assert parse_hostport("1.2.3.4") == (None, None, None, "1.2.3.4")
+    assert parse_hostport("2001::1") == (None, None, None, "2001::1")
+    assert parse_hostport("[2001::1]") == (None, None, None, "2001::1")
+
+
+def test_host_and_port():
+    assert parse_hostport("host:22") == (None, None, 22, "host")
+    assert parse_hostport("1.2.3.4:22") == (None, None, 22, "1.2.3.4")
+    assert parse_hostport("[2001::1]:22") == (None, None, 22, "2001::1")
+
+
+def test_username_and_host():
+    assert parse_hostport("user@host") == ("user", None, None, "host")
+    assert parse_hostport("user:@host") == ("user", None, None, "host")
+    assert parse_hostport("user:pass@host") == ("user", "pass", None, "host")