Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
sshuttle
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
pub
sshuttle
Commits
20254bab
Commit
20254bab
authored
Jul 11, 2011
by
Brian May
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TProxy IPv6 support.
parent
f41c6b62
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
126 additions
and
28 deletions
+126
-28
client.py
client.py
+42
-9
firewall.py
firewall.py
+23
-9
helpers.py
helpers.py
+5
-2
main.py
main.py
+52
-7
server.py
server.py
+4
-1
No files found.
client.py
View file @
20254bab
...
...
@@ -99,41 +99,55 @@ def original_dst(sock):
class
MultiListener
:
def
__init__
(
self
,
type
=
socket
.
SOCK_STREAM
,
proto
=
0
):
self
.
v6
=
socket
.
socket
(
socket
.
AF_INET6
,
type
,
proto
)
self
.
v4
=
socket
.
socket
(
socket
.
AF_INET
,
type
,
proto
)
def
setsockopt
(
self
,
level
,
optname
,
value
):
if
self
.
v6
:
self
.
v6
.
setsockopt
(
level
,
optname
,
value
)
if
self
.
v4
:
self
.
v4
.
setsockopt
(
level
,
optname
,
value
)
def
add_handler
(
self
,
handlers
,
callback
,
method
,
mux
):
if
self
.
v6
:
handlers
.
append
(
Handler
([
self
.
v6
],
lambda
:
callback
(
self
.
v6
,
method
,
mux
,
handlers
)))
if
self
.
v4
:
handlers
.
append
(
Handler
([
self
.
v4
],
lambda
:
callback
(
self
.
v4
,
method
,
mux
,
handlers
)))
def
listen
(
self
,
backlog
):
if
self
.
v6
:
self
.
v6
.
listen
(
backlog
)
if
self
.
v4
:
self
.
v4
.
listen
(
backlog
)
def
bind
(
self
,
address_v4
):
def
bind
(
self
,
address_v6
,
address_v4
):
if
address_v6
and
self
.
v6
:
self
.
v6
.
bind
(
address_v6
)
else
:
self
.
v6
=
None
if
address_v4
and
self
.
v4
:
self
.
v4
.
bind
(
address_v4
)
else
:
self
.
v4
=
None
def
print_listening
(
self
,
what
):
if
self
.
v6
:
listenip
=
self
.
v6
.
getsockname
()
debug1
(
'
%
s listening on
%
r.
\n
'
%
(
what
,
listenip
))
if
self
.
v4
:
listenip
=
self
.
v4
.
getsockname
()
debug1
(
'
%
s listening on
%
r.
\n
'
%
(
what
,
listenip
))
class
FirewallClient
:
def
__init__
(
self
,
port_v
4
,
subnets_include
,
subnets_exclude
,
dnsport_v4
,
method
):
def
__init__
(
self
,
port_v
6
,
port_v4
,
subnets_include
,
subnets_exclude
,
dnsport_v6
,
dnsport_v4
,
method
):
self
.
auto_nets
=
[]
self
.
subnets_include
=
subnets_include
self
.
subnets_exclude
=
subnets_exclude
argvbase
=
([
sys
.
argv
[
1
],
sys
.
argv
[
0
],
sys
.
argv
[
1
]]
+
[
'-v'
]
*
(
helpers
.
verbose
or
0
)
+
[
'--firewall'
,
str
(
port_v4
),
str
(
dnsport_v4
),
[
'--firewall'
,
str
(
port_v
6
),
str
(
port_v
4
),
str
(
dnsport_v
6
),
str
(
dnsport_v
4
),
method
])
if
ssyslog
.
_p
:
argvbase
+=
[
'--syslog'
]
...
...
@@ -376,7 +390,7 @@ def _main(tcp_listener, fw, ssh_cmd, remotename, python, latency_control,
mux
.
callback
()
def
main
(
listenip_v4
,
def
main
(
listenip_v
6
,
listenip_v
4
,
ssh_cmd
,
remotename
,
python
,
latency_control
,
dns
,
method
,
seed_hosts
,
auto_nets
,
subnets_include
,
subnets_exclude
,
syslog
,
daemon
,
pidfile
):
...
...
@@ -390,7 +404,7 @@ def main(listenip_v4,
return
5
debug1
(
'Starting sshuttle proxy.
\n
'
)
if
listenip_v4
and
listenip_v4
[
1
]:
if
listenip_v
6
and
listenip_v6
[
1
]
and
listenip_v
4
and
listenip_v4
[
1
]:
# if both ports given, no need to search for a spare port
ports
=
[
0
,
]
else
:
...
...
@@ -399,6 +413,7 @@ def main(listenip_v4,
# search for free ports and try to bind
last_e
=
None
redirectport_v6
=
0
redirectport_v4
=
0
bound
=
False
debug2
(
'Binding redirector:'
)
...
...
@@ -407,6 +422,16 @@ def main(listenip_v4,
tcp_listener
=
MultiListener
()
tcp_listener
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
1
)
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
]
...
...
@@ -418,7 +443,7 @@ def main(listenip_v4,
redirectport_v4
=
0
try
:
tcp_listener
.
bind
(
lv4
)
tcp_listener
.
bind
(
lv
6
,
lv
4
)
bound
=
True
break
except
socket
.
error
,
e
:
...
...
@@ -443,6 +468,13 @@ def main(listenip_v4,
dns_listener
=
MultiListener
(
socket
.
SOCK_DGRAM
)
dns_listener
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
1
)
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
...
...
@@ -451,7 +483,7 @@ def main(listenip_v4,
dnsport_v4
=
0
try
:
dns_listener
.
bind
(
lv4
)
dns_listener
.
bind
(
lv
6
,
lv
4
)
bound
=
True
break
except
socket
.
error
,
e
:
...
...
@@ -465,10 +497,11 @@ def main(listenip_v4,
assert
(
last_e
)
raise
last_e
else
:
dnsport_v6
=
0
dnsport_v4
=
0
dns_listener
=
None
fw
=
FirewallClient
(
redirectport_v
4
,
subnets_include
,
subnets_exclude
,
dnsport_v4
,
method
)
fw
=
FirewallClient
(
redirectport_v
6
,
redirectport_v4
,
subnets_include
,
subnets_exclude
,
dnsport_v6
,
dnsport_v4
,
method
)
if
fw
.
method
==
"tproxy"
:
tcp_listener
.
setsockopt
(
socket
.
SOL_IP
,
IP_TRANSPARENT
,
1
)
...
...
firewall.py
View file @
20254bab
...
...
@@ -15,7 +15,9 @@ def nonfatal(func, *args):
def
ipt_chain_exists
(
family
,
table
,
name
):
if
family
==
socket
.
AF_INET
:
if
family
==
socket
.
AF_INET6
:
cmd
=
'ip6tables'
elif
family
==
socket
.
AF_INET
:
cmd
=
'iptables'
else
:
raise
Exception
(
'Unsupported family "
%
s"'
%
family_to_string
(
family
))
...
...
@@ -30,7 +32,9 @@ def ipt_chain_exists(family, table, name):
def
_ipt
(
family
,
table
,
*
args
):
if
family
==
socket
.
AF_INET
:
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
))
...
...
@@ -110,7 +114,7 @@ def do_iptables_nat(port, dnsport, family, subnets):
if
dnsport
:
nslist
=
resolvconf_nameservers
()
for
ip
in
nslist
:
for
f
,
ip
in
filter
(
lambda
i
:
i
[
0
]
==
family
,
nslist
)
:
ipt_ttl
(
'-A'
,
chain
,
'-j'
,
'REDIRECT'
,
'--dest'
,
'
%
s/32'
%
ip
,
'-p'
,
'udp'
,
...
...
@@ -119,7 +123,7 @@ def do_iptables_nat(port, dnsport, family, subnets):
def
do_iptables_tproxy
(
port
,
dnsport
,
family
,
subnets
):
if
family
not
in
[
socket
.
AF_INET
]:
if
family
not
in
[
socket
.
AF_INET
,
socket
.
AF_INET6
]:
raise
Exception
(
'Address family "
%
s" unsupported by tproxy method'
%
family_to_string
(
family
))
table
=
"mangle"
...
...
@@ -367,7 +371,7 @@ def do_ipfw(port, dnsport, family, subnets):
divertsock
.
bind
((
'0.0.0.0'
,
port
))
# IP field is ignored
nslist
=
resolvconf_nameservers
()
for
ip
in
nslist
:
for
f
,
ip
in
filter
(
lambda
i
:
i
[
0
]
==
family
,
nslist
)
:
# relabel and then catch outgoing DNS requests
ipfw
(
'add'
,
sport
,
'divert'
,
sport
,
'log'
,
'udp'
,
...
...
@@ -450,9 +454,13 @@ def restore_etc_hosts(port):
# exit. In case that fails, it's not the end of the world; future runs will
# supercede it in the transproxy list, at least, so the leftover rules
# are hopefully harmless.
def
main
(
port_v4
,
dnsport_v4
,
method
,
syslog
):
assert
(
port_v4
>
0
)
def
main
(
port_v6
,
port_v4
,
dnsport_v6
,
dnsport_v4
,
method
,
syslog
):
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
)
...
...
@@ -519,6 +527,12 @@ def main(port_v4, dnsport_v4, method, syslog):
if
line
:
debug1
(
'firewall manager: starting transproxy.
\n
'
)
subnets_v6
=
filter
(
lambda
i
:
i
[
0
]
==
socket
.
AF_INET6
,
subnets
)
if
port_v6
:
do_wait
=
do_it
(
port_v6
,
dnsport_v6
,
socket
.
AF_INET6
,
subnets_v6
)
elif
len
(
subnets_v6
)
>
0
:
debug1
(
"IPv6 subnets defined but IPv6 disabled
\n
"
)
subnets_v4
=
filter
(
lambda
i
:
i
[
0
]
==
socket
.
AF_INET
,
subnets
)
if
port_v4
:
do_wait
=
do_it
(
port_v4
,
dnsport_v4
,
socket
.
AF_INET
,
subnets_v4
)
...
...
@@ -543,7 +557,7 @@ def main(port_v4, dnsport_v4, method, syslog):
if
line
.
startswith
(
'HOST '
):
(
name
,
ip
)
=
line
[
5
:]
.
strip
()
.
split
(
','
,
1
)
hostmap
[
name
]
=
ip
rewrite_etc_hosts
(
port_v4
)
rewrite_etc_hosts
(
port_v
6
or
port_v
4
)
elif
line
:
raise
Fatal
(
'expected EOF, got
%
r'
%
line
)
else
:
...
...
@@ -555,4 +569,4 @@ def main(port_v4, dnsport_v4, method, syslog):
pass
if
port_v4
:
do_it
(
port_v4
,
0
,
socket
.
AF_INET
,
[])
restore_etc_hosts
(
port_v4
)
restore_etc_hosts
(
port_v
6
or
port_v
4
)
helpers.py
View file @
20254bab
...
...
@@ -42,7 +42,10 @@ def resolvconf_nameservers():
for
line
in
open
(
'/etc/resolv.conf'
):
words
=
line
.
lower
()
.
split
()
if
len
(
words
)
>=
2
and
words
[
0
]
==
'nameserver'
:
l
.
append
(
words
[
1
])
if
':'
in
words
[
1
]:
l
.
append
((
socket
.
AF_INET6
,
words
[
1
]))
else
:
l
.
append
((
socket
.
AF_INET
,
words
[
1
]))
return
l
...
...
@@ -55,7 +58,7 @@ def resolvconf_random_nameserver():
random
.
shuffle
(
l
)
return
l
[
0
]
else
:
return
'127.0.0.1'
return
(
socket
.
AF_INET
,
'127.0.0.1'
)
def
islocal
(
ip
,
family
):
...
...
main.py
View file @
20254bab
...
...
@@ -22,12 +22,31 @@ def parse_subnet4(s):
return
(
socket
.
AF_INET
,
'
%
d.
%
d.
%
d.
%
d'
%
(
a
,
b
,
c
,
d
),
width
)
# 1:2::3/64 or just 1:2::3
def
parse_subnet6
(
s
):
m
=
re
.
match
(
r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$'
,
s
)
if
not
m
:
raise
Fatal
(
'
%
r is not a valid IP subnet format'
%
s
)
(
net
,
width
)
=
m
.
groups
()
if
width
==
None
:
width
=
128
else
:
width
=
int
(
width
)
if
width
>
128
:
raise
Fatal
(
'*/
%
d is greater than the maximum of 128'
%
width
)
return
(
socket
.
AF_INET6
,
net
,
width
)
# list of:
# 1.2.3.4/5 or just 1.2.3.4
# 1:2::3/64 or just 1:2::3
def
parse_subnets
(
subnets_str
):
subnets
=
[]
for
s
in
subnets_str
:
subnet
=
parse_subnet4
(
s
)
if
':'
in
s
:
subnet
=
parse_subnet6
(
s
)
else
:
subnet
=
parse_subnet4
(
s
)
subnets
.
append
(
subnet
)
return
subnets
...
...
@@ -50,13 +69,24 @@ def parse_ipport4(s):
return
(
'
%
d.
%
d.
%
d.
%
d'
%
(
a
,
b
,
c
,
d
),
port
)
# [1:2::3]:456 or [1:2::3] or 456
def
parse_ipport6
(
s
):
s
=
str
(
s
)
m
=
re
.
match
(
r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$'
,
s
)
if
not
m
:
raise
Fatal
(
'
%
s is not a valid IP:port format'
%
s
)
(
ip
,
port
)
=
m
.
groups
()
(
ip
,
port
)
=
(
ip
or
'::'
,
int
(
port
or
0
))
return
(
ip
,
port
)
optspec
=
"""
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
sshuttle --server
sshuttle --firewall <port> <subnets...>
sshuttle --hostwatch
--
l,listen= transproxy to this ip address and port number
[127.0.0.1:0]
l,listen= transproxy to this ip address and port number
H,auto-hosts scan for remote hostnames and update local /etc/hosts
N,auto-nets automatically determine subnets to route
dns capture local DNS requests and forward to the remote DNS server
...
...
@@ -93,10 +123,11 @@ try:
server
.
latency_control
=
opt
.
latency_control
sys
.
exit
(
server
.
main
())
elif
opt
.
firewall
:
if
len
(
extra
)
!=
3
:
o
.
fatal
(
'exactly
thre
e arguments expected'
)
if
len
(
extra
)
!=
5
:
o
.
fatal
(
'exactly
fiv
e arguments expected'
)
sys
.
exit
(
firewall
.
main
(
int
(
extra
[
0
]),
int
(
extra
[
1
]),
extra
[
2
],
opt
.
syslog
))
int
(
extra
[
2
]),
int
(
extra
[
3
]),
extra
[
4
],
opt
.
syslog
))
elif
opt
.
hostwatch
:
sys
.
exit
(
hostwatch
.
hw_main
(
extra
))
else
:
...
...
@@ -124,8 +155,22 @@ try:
method
=
opt
.
method
else
:
o
.
fatal
(
"method
%
s not supported"
%
opt
.
method
)
ipport_v4
=
parse_ipport4
(
opt
.
listen
or
'0.0.0.0:0'
)
sys
.
exit
(
client
.
main
(
ipport_v4
,
if
not
opt
.
listen
:
if
opt
.
method
==
"tproxy"
:
ipport_v6
=
parse_ipport6
(
'[::1]:0'
)
else
:
ipport_v6
=
None
ipport_v4
=
parse_ipport4
(
'127.0.0.1:0'
)
else
:
ipport_v6
=
None
ipport_v4
=
None
list
=
opt
.
listen
.
split
(
","
)
for
ip
in
list
:
if
'['
in
ip
and
']'
in
ip
and
opt
.
method
==
"tproxy"
:
ipport_v6
=
parse_ipport6
(
ip
)
else
:
ipport_v4
=
parse_ipport4
(
ip
)
sys
.
exit
(
client
.
main
(
ipport_v6
,
ipport_v4
,
opt
.
ssh_cmd
,
remotename
,
opt
.
python
,
...
...
server.py
View file @
20254bab
...
...
@@ -108,6 +108,7 @@ class Hostwatch:
class
DnsProxy
(
Handler
):
def
__init__
(
self
,
mux
,
chan
,
request
):
# FIXME! IPv4 specific
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_DGRAM
)
Handler
.
__init__
(
self
,
[
sock
])
self
.
timeout
=
time
.
time
()
+
30
...
...
@@ -117,6 +118,7 @@ class DnsProxy(Handler):
self
.
peer
=
None
self
.
request
=
request
self
.
sock
=
sock
# FIXME! IPv4 specific
self
.
sock
.
setsockopt
(
socket
.
SOL_IP
,
socket
.
IP_TTL
,
42
)
self
.
try_send
()
...
...
@@ -124,7 +126,8 @@ class DnsProxy(Handler):
if
self
.
tries
>=
3
:
return
self
.
tries
+=
1
self
.
peer
=
resolvconf_random_nameserver
()
# FIXME! Support IPv6 nameservers
self
.
peer
=
resolvconf_random_nameserver
()[
1
]
self
.
sock
.
connect
((
self
.
peer
,
53
))
debug2
(
'DNS: sending to
%
r
\n
'
%
self
.
peer
)
try
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment