Skip to content

Commit 75b6865

Browse files
committed
Tests for pf method.
1 parent e3a1c56 commit 75b6865

File tree

4 files changed

+178
-13
lines changed

4 files changed

+178
-13
lines changed

sshuttle/client.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ def daemon_cleanup():
9292
else:
9393
raise
9494

95-
firewall = None
96-
9795

9896
class MultiListener:
9997

@@ -207,6 +205,7 @@ def setup():
207205
raise Fatal('%r expected READY, got %r' % (self.argv, line))
208206
method_name = line[6:-1]
209207
self.method = get_method(method_name.decode("ASCII"))
208+
self.method.set_firewall(self)
210209

211210
def setup(self, subnets_include, subnets_exclude, nslist,
212211
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp):
@@ -635,10 +634,6 @@ def main(listenip_v6, listenip_v4,
635634
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
636635
udp)
637636

638-
# kludge for PF method.
639-
global firewall
640-
firewall = fw
641-
642637
try:
643638
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
644639
python, latency_control, dns_listener,

sshuttle/methods/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ class Features(object):
2828

2929
class BaseMethod(object):
3030
def __init__(self, name):
31+
self.firewall = None
3132
self.name = name
3233

34+
def set_firewall(self, firewall):
35+
self.firewall = firewall
36+
3337
def get_supported_features(self):
3438
result = Features()
3539
result.ipv6 = False

sshuttle/methods/pf.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from fcntl import ioctl
88
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
99
sizeof, addressof, memmove
10-
from sshuttle.helpers import debug1, debug2, Fatal
10+
from sshuttle.helpers import debug1, debug2, Fatal, family_to_string
1111
from sshuttle.methods import BaseMethod
1212

1313

@@ -146,18 +146,17 @@ def pf_add_anchor_rule(type, name):
146146
class Method(BaseMethod):
147147

148148
def get_tcp_dstip(self, sock):
149-
# yuck
150-
from sshuttle.client import firewall
149+
pfile = self.firewall.pfile
151150

152151
peer = sock.getpeername()
153152
proxy = sock.getsockname()
154153

155154
argv = (sock.family, socket.IPPROTO_TCP,
156155
peer[0], peer[1], proxy[0], proxy[1])
157-
firewall.pfile.write("QUERY_PF_NAT %r,%r,%s,%r,%s,%r\n" % argv)
158-
firewall.pfile.flush()
159-
line = firewall.pfile.readline()
160-
debug2("QUERY_PF_NAT %r,%r,%s,%r,%s,%r" % argv + ' > ' + line)
156+
pfile.write("QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % argv)
157+
pfile.flush()
158+
line = pfile.readline()
159+
debug2("QUERY_PF_NAT %d,%d,%s,%d,%s,%d" % argv + ' > ' + line)
161160
if line.startswith('QUERY_PF_NAT_SUCCESS '):
162161
(ip, port) = line[21:].split(',')
163162
return (ip, int(port))
@@ -170,6 +169,13 @@ def setup_firewall(self, port, dnsport, nslist, family, subnets, udp):
170169
translating_rules = []
171170
filtering_rules = []
172171

172+
if family != socket.AF_INET:
173+
raise Exception(
174+
'Address family "%s" unsupported by pf method_name'
175+
% family_to_string(family))
176+
if udp:
177+
raise Exception("UDP not supported by pf method_name")
178+
173179
if subnets:
174180
includes = []
175181
# If a given subnet is both included and excluded, list the

sshuttle/tests/test_methods_pf.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import pytest
2+
from mock import Mock, patch, call, ANY
3+
import socket
4+
5+
from sshuttle.methods import get_method
6+
7+
8+
def test_get_supported_features():
9+
method = get_method('pf')
10+
features = method.get_supported_features()
11+
assert not features.ipv6
12+
assert not features.udp
13+
14+
15+
def test_get_tcp_dstip():
16+
sock = Mock()
17+
sock.getpeername.return_value = ("127.0.0.1", 1024)
18+
sock.getsockname.return_value = ("127.0.0.2", 1025)
19+
sock.family = socket.AF_INET
20+
21+
firewall = Mock()
22+
firewall.pfile.readline.return_value = \
23+
"QUERY_PF_NAT_SUCCESS 127.0.0.3,1026\n"
24+
25+
method = get_method('pf')
26+
method.set_firewall(firewall)
27+
assert method.get_tcp_dstip(sock) == ('127.0.0.3', 1026)
28+
29+
assert sock.mock_calls == [
30+
call.getpeername(),
31+
call.getsockname(),
32+
]
33+
assert firewall.mock_calls == [
34+
call.pfile.write('QUERY_PF_NAT 2,6,127.0.0.1,1024,127.0.0.2,1025\n'),
35+
call.pfile.flush(),
36+
call.pfile.readline()
37+
]
38+
39+
40+
def test_recv_udp():
41+
sock = Mock()
42+
sock.recvfrom.return_value = "11111", "127.0.0.1"
43+
method = get_method('pf')
44+
result = method.recv_udp(sock, 1024)
45+
assert sock.mock_calls == [call.recvfrom(1024)]
46+
assert result == ("127.0.0.1", None, "11111")
47+
48+
49+
def test_send_udp():
50+
sock = Mock()
51+
method = get_method('pf')
52+
method.send_udp(sock, None, "127.0.0.1", "22222")
53+
assert sock.mock_calls == [call.sendto("22222", "127.0.0.1")]
54+
55+
56+
def test_setup_tcp_listener():
57+
listener = Mock()
58+
method = get_method('pf')
59+
method.setup_tcp_listener(listener)
60+
assert listener.mock_calls == []
61+
62+
63+
def test_setup_udp_listener():
64+
listener = Mock()
65+
method = get_method('pf')
66+
method.setup_udp_listener(listener)
67+
assert listener.mock_calls == []
68+
69+
70+
def test_check_settings():
71+
method = get_method('pf')
72+
method.check_settings(True, True)
73+
method.check_settings(False, True)
74+
75+
76+
@patch('sshuttle.methods.pf.sys.stdout')
77+
@patch('sshuttle.methods.pf.ioctl')
78+
@patch('sshuttle.methods.pf.pf_get_dev')
79+
def test_firewall_command(mock_pf_get_dev, mock_ioctl, mock_stdout):
80+
method = get_method('pf')
81+
assert not method.firewall_command("somthing")
82+
83+
command = "QUERY_PF_NAT %d,%d,%s,%d,%s,%d\n" % (
84+
socket.AF_INET, socket.IPPROTO_TCP,
85+
"127.0.0.1", 1025, "127.0.0.2", 1024)
86+
assert method.firewall_command(command)
87+
88+
assert mock_pf_get_dev.mock_calls == [call()]
89+
assert mock_ioctl.mock_calls == [
90+
call(mock_pf_get_dev(), 3226747927, ANY),
91+
]
92+
assert mock_stdout.mock_calls == [
93+
call.write('QUERY_PF_NAT_SUCCESS 0.0.0.0,0\n'),
94+
call.flush(),
95+
]
96+
97+
98+
# FIXME - test fails with platform=='darwin' due re.search not liking Mock
99+
# objects.
100+
@patch('sshuttle.methods.pf.sys.platform', 'not_darwin')
101+
@patch('sshuttle.methods.pf.pfctl')
102+
@patch('sshuttle.methods.pf.ioctl')
103+
@patch('sshuttle.methods.pf.pf_get_dev')
104+
def test_setup_firewall(mock_pf_get_dev, mock_ioctl, mock_pfctl):
105+
method = get_method('pf')
106+
assert method.name == 'pf'
107+
108+
with pytest.raises(Exception) as excinfo:
109+
method.setup_firewall(
110+
1024, 1026,
111+
[(10, u'2404:6800:4004:80c::33')],
112+
10,
113+
[(10, 64, False, u'2404:6800:4004:80c::'),
114+
(10, 128, True, u'2404:6800:4004:80c::101f')],
115+
True)
116+
assert str(excinfo.value) \
117+
== 'Address family "AF_INET6" unsupported by pf method_name'
118+
assert mock_pf_get_dev.mock_calls == []
119+
assert mock_ioctl.mock_calls == []
120+
assert mock_pfctl.mock_calls == []
121+
122+
with pytest.raises(Exception) as excinfo:
123+
method.setup_firewall(
124+
1025, 1027,
125+
[(2, u'1.2.3.33')],
126+
2,
127+
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
128+
True)
129+
assert str(excinfo.value) == 'UDP not supported by pf method_name'
130+
assert mock_pf_get_dev.mock_calls == []
131+
assert mock_ioctl.mock_calls == []
132+
assert mock_pfctl.mock_calls == []
133+
134+
method.setup_firewall(
135+
1025, 1027,
136+
[(2, u'1.2.3.33')],
137+
2,
138+
[(2, 24, False, u'1.2.3.0'), (2, 32, True, u'1.2.3.66')],
139+
False)
140+
assert mock_ioctl.mock_calls == [
141+
call(mock_pf_get_dev(), 3295691827, ANY),
142+
call(mock_pf_get_dev(), 3424666650, ANY),
143+
call(mock_pf_get_dev(), 3424666650, ANY),
144+
call(mock_pf_get_dev(), 3295691827, ANY),
145+
call(mock_pf_get_dev(), 3424666650, ANY),
146+
call(mock_pf_get_dev(), 3424666650, ANY),
147+
]
148+
# FIXME - needs more work
149+
# print(mock_pfctl.mock_calls)
150+
# assert mock_pfctl.mock_calls == []
151+
mock_pf_get_dev.reset_mock()
152+
mock_ioctl.reset_mock()
153+
mock_pfctl.reset_mock()
154+
155+
method.setup_firewall(1025, 0, [], 2, [], False)
156+
assert mock_ioctl.mock_calls == []
157+
assert mock_pfctl.mock_calls == [call('-a sshuttle -F all')]
158+
mock_pf_get_dev.reset_mock()
159+
mock_pfctl.reset_mock()
160+
mock_ioctl.reset_mock()

0 commit comments

Comments
 (0)