Skip to content

Commit 852042b

Browse files
committed
Add duplicate option for punt-policies, and introduce manipulator actions
Combination of two things: 1. `duplicate` option when setting the manipulator and punt-policies 2. manipulator actions - drop, inject raw or handle encapsultion Also added tests for manipulator actions, and clearify set_manipulation's cb argument
1 parent 3c2bdfe commit 852042b

4 files changed

Lines changed: 144 additions & 23 deletions

File tree

src/connections.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import concurrent.futures
88

99
from src.util import PortType, add_vlan_tag, fix_checksums, apply_bpf_filter
10-
from src.manipulation import ManipulateArgs
10+
from src.manipulation import ManipulateArgs, ManipulateActions
1111

1212

1313
DeviceEntry = namedtuple('DeviceEntry', [
@@ -35,6 +35,7 @@ def __init__(self):
3535

3636
self._manipulate_cb = None
3737
self._manipulate_filter = NO_FILTER
38+
self._manipulate_dup = False
3839

3940
async def _read_message_queue(self):
4041
while True:
@@ -46,29 +47,48 @@ async def _process_packet(self, packet, src_device_entry):
4647
# to tag the packet.
4748
should_inject_raw = False
4849

50+
packets = [packet]
51+
4952
if self._manipulate_cb is not None:
5053
# We'll manipulate if there's no filter or if there's
5154
# filter and the packet matches the filter
5255
if self._manipulate_filter == NO_FILTER or \
5356
apply_bpf_filter(packet, self._manipulate_filter):
54-
packet, should_inject_raw = await self._event_loop.run_in_executor(
57+
58+
# In case the packet matches the filter, and `duplicate` is not
59+
# set, we remove the packet from the list, since the manipulator
60+
# might drop the packet.
61+
if not self._manipulate_dup:
62+
packets.remove(packet)
63+
64+
packet, action = await self._event_loop.run_in_executor(
5565
self._executers,
5666
self._manipulate_cb,
5767
ManipulateArgs(packet, src_device_entry.port_type, src_device_entry.vlan))
5868

59-
src_vlan = self._get_vlan_by_mac(Ether(packet).src)
60-
if src_vlan is None:
61-
return None
69+
if action == ManipulateActions.INJECT_RAW:
70+
should_inject_raw = True
71+
packets.append(packet)
72+
elif action == ManipulateActions.HANDLE_ENCAP:
73+
should_inject_raw = False
74+
packets.append(packet)
75+
# else - action is DROP, so we don't add the packet to the list.
76+
77+
for packet in packets:
78+
79+
src_vlan = self._get_vlan_by_mac(Ether(packet).src)
80+
if src_vlan is None:
81+
return None
6282

63-
# Looking for the destination device
64-
for dst_device in self._devices:
65-
if dst_device.dev.get_mac == Ether(packet).dst and dst_device.vlan == src_vlan:
83+
# Looking for the destination device
84+
for dst_device in self._devices:
85+
if dst_device.dev.get_mac == Ether(packet).dst and dst_device.vlan == src_vlan:
6686

67-
if dst_device.port_type == PortType.TRUNK and not should_inject_raw:
68-
packet = add_vlan_tag(packet, dst_device.vlan)
87+
if dst_device.port_type == PortType.TRUNK and not should_inject_raw:
88+
packet = add_vlan_tag(packet, dst_device.vlan)
6989

70-
packet = fix_checksums(packet)
71-
await self._event_loop.sock_sendall(dst_device.sock, packet)
90+
packet = fix_checksums(packet)
91+
await self._event_loop.sock_sendall(dst_device.sock, packet)
7292

7393
def _read_raw_packet(self, device_entry):
7494
try:
@@ -146,11 +166,12 @@ def _remove_device_entry(self, dev_to_remove):
146166

147167
self._event_loop.call_soon_threadsafe(_remove_device_entry, self, dev_to_remove)
148168

149-
def set_manipulation(self, cb, bpf_filter):
169+
def set_manipulation(self, cb, bpf_filter, duplicate):
150170
''' Assumes cb is in valid form '''
151171
def __set_manipulation(cb, bpf_filter):
152172
self._manipulate_cb = cb
153173
self._manipulate_filter = bpf_filter
174+
self._manipulate_dup = duplicate
154175

155176
# set_manipulation is called from the main thread.
156177
# Since we're affecting the event loop thread, we should call_soon_threadsafe,

src/manipulation.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
import enum
12
import inspect
23
from collections import namedtuple
34

5+
6+
class ManipulateActions(enum.Enum):
7+
DROP = 0
8+
# The manipulator will deal with tagging, if needed.
9+
INJECT_RAW = 1
10+
# The switch should deal with tagging, if needed.
11+
HANDLE_ENCAP = 2
12+
13+
414
ManipulateArgs = namedtuple('ManipulateInfo', [
515
'packet',
616
# can be used if the manipulator wants to deal with tagging.
@@ -10,9 +20,8 @@
1020

1121
ManipulateRet = namedtuple('ManipulateRet', [
1222
'packet',
13-
# In case you want to deal with tagging (add dot1q layer),
14-
# this should be set to True.
15-
'should_inject_raw',
23+
# From ManipulateActions
24+
'action',
1625
])
1726

1827

@@ -21,7 +30,7 @@ def default_manipulation_cb(manipulate_args: ManipulateArgs) -> ManipulateRet:
2130
Can be used as an example of a valid signature of manipulation callback.
2231
This default callback just returns the packet.
2332
'''
24-
return ManipulateRet(manipulate_args.packet, False)
33+
return ManipulateRet(manipulate_args.packet, ManipulateActions.HANDLE_ENCAP)
2534

2635

2736
def validate_manipulation_cb(cb):

src/switch.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def _set_trunk_connection(self, dev, vlan):
5151

5252
return True
5353

54-
def set_manipulation(self, cb, punt_policies_bpf=NO_FILTER):
54+
def set_manipulation(self, cb, punt_policies_bpf=NO_FILTER, duplicate=False):
5555
"""
5656
Sets the manipulation routine.
5757
@@ -64,13 +64,32 @@ def set_manipulation(self, cb, punt_policies_bpf=NO_FILTER):
6464
In addition, In case you want to emulate "Punt-Policies" (decide what packets
6565
should be forwarded to the manipulation callback), you can pass a bpf filter.
6666
As mentioned, only packets that are filtered by this bpf filter will be forwarded
67-
to the manipultion routine before continuing the switch's flow.
67+
to the manipultion routine before they get processed by the switch.
6868
6969
:param cb: A callback the the manipulation routine.
70+
The callback must have arguments and return annotations,
71+
since this function validates this callback using them.
72+
73+
A valid callback has to match the following form:
74+
1. Recieve one argument. Its type is ManipulateArgs.
75+
2. Return ManipulateRet.
76+
77+
An example of a valid callback (taken from manipulation.py:
78+
`default_manipulation_cb`):
79+
80+
def default_manipulation_cb(manipulate_args: ManipulateArgs) -> ManipulateRet:
81+
return ManipulateRet(manipulate_args.packet, ManipulateActions.HANDLE_ENCAP)
82+
83+
This callback for example, just returns the packet.
84+
7085
:param punt_policies_bpf: The "Punt-Policies". Default is no-filter.
86+
:param duplicate: True if the packet should be processed by both switch
87+
and manipulator, False if the packet should be processed
88+
by the manipulator only.
89+
:return: True if the callback is in a valid form, False otherwise.
7190
"""
7291
if validate_manipulation_cb(cb):
73-
self._connections.set_manipulation(cb, punt_policies_bpf)
92+
self._connections.set_manipulation(cb, punt_policies_bpf, duplicate)
7493
return True
7594

7695
return False

tests/test_manipulation.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from scapy.all import Ether, IP, ICMP, Raw
33

44
from src.device import Device
5-
from src.manipulation import ManipulateArgs, ManipulateRet
5+
from src.manipulation import ManipulateArgs, ManipulateRet, ManipulateActions
6+
from src.util import add_vlan_tag
67

78

89
D1_ADDR = '192.168.250.1'
@@ -29,7 +30,19 @@ def forward_d1_to_d3(arg: ManipulateArgs) -> ManipulateRet:
2930
packet[Ether].src = GLOBAL_D4_MAC
3031
packet[Ether].dst = GLOBAL_D3_MAC
3132

32-
return ManipulateRet(Raw(packet).load, False)
33+
return ManipulateRet(Raw(packet).load, ManipulateActions.HANDLE_ENCAP)
34+
35+
36+
def drop_packet(arg: ManipulateArgs) -> ManipulateRet:
37+
return ManipulateRet(None, ManipulateActions.DROP)
38+
39+
40+
def tag_packet_inject_raw(arg: ManipulateArgs) -> ManipulateRet:
41+
return ManipulateRet(add_vlan_tag(arg.packet, arg.src_vlan), ManipulateActions.INJECT_RAW)
42+
43+
44+
def tag_packet_handle_encap(arg: ManipulateArgs) -> ManipulateRet:
45+
return ManipulateRet(add_vlan_tag(arg.packet, arg.src_vlan), ManipulateActions.HANDLE_ENCAP)
3346

3447

3548
def test_vlan_hopping_and_punt_policies(switch):
@@ -65,7 +78,7 @@ def test_vlan_hopping_and_punt_policies(switch):
6578

6679
# Setting the manipulation routine, and punt-policies
6780
switch.set_manipulation(forward_d1_to_d3, 'src host {} && dst host {}'.format(
68-
D1_ADDR, D2_ADDR))
81+
D1_ADDR, D2_ADDR), False)
6982

7083
def _run_listening_nc(dev):
7184
out = dev.run_from_namespace('timeout 7s nc -lu 0.0.0.0 4444')
@@ -89,3 +102,62 @@ def _run_listening_nc(dev):
89102
switch.disconnect_device(d2)
90103
switch.disconnect_device(d3)
91104
switch.disconnect_device(d4)
105+
106+
107+
def test_manipulator_drop_packets(switch):
108+
d1 = Device('a', '192.168.250.1', '255.255.255.0')
109+
d2 = Device('b', '192.168.250.2', '255.255.255.0')
110+
111+
switch.connect_device_access(d1, 20)
112+
switch.connect_device_access(d2, 20)
113+
114+
switch.set_manipulation(drop_packet, duplicate=False)
115+
116+
# No duplication, and the manipulator drops everything
117+
# so we expect to recieve no response.
118+
out = d1.run_from_namespace('ping -c 1 192.168.250.2 -W 2')
119+
assert '1 packets transmitted, 0 received' in out
120+
121+
# Now we set `duplicate` to True, which means that even
122+
# if we drop packets, the original packet will still be processed
123+
# by the switch.
124+
switch.set_manipulation(drop_packet, duplicate=True)
125+
126+
# So now we expect to recieve the response
127+
out = d1.run_from_namespace('ping -c 1 192.168.250.2')
128+
assert '1 packets transmitted, 1 received' in out
129+
130+
switch.disconnect_device(d1)
131+
switch.disconnect_device(d2)
132+
133+
134+
def test_manipulator_inject_raw(switch):
135+
d1 = Device('a', '192.168.250.1', '255.255.255.0')
136+
d2 = Device('b', '192.168.250.2', '255.255.255.0')
137+
138+
switch.connect_device_trunk(d1, 20)
139+
switch.connect_device_trunk(d2, 20)
140+
141+
# The manipulator will tag all packets, and will set INJECT_RAW
142+
# as the action. The original packets won't get processed by switch,
143+
# So if we get a valid connection, it means that the manipulator
144+
# successfully tagged the packet, and the switch didn't deal with
145+
# with tagging; it just send the packet as is (raw).
146+
switch.set_manipulation(tag_packet_inject_raw, duplicate=False)
147+
148+
out = d1.run_from_namespace('ping -c 1 192.168.250.2')
149+
assert '1 packets transmitted, 1 received' in out
150+
151+
out = d2.run_from_namespace('ping -c 1 192.168.250.1')
152+
assert '1 packets transmitted, 1 received' in out
153+
154+
switch.set_manipulation(tag_packet_handle_encap, duplicate=False)
155+
156+
out = d1.run_from_namespace('ping -c 1 192.168.250.2 -W 2')
157+
assert '1 packets transmitted, 0 received' in out
158+
159+
out = d2.run_from_namespace('ping -c 1 192.168.250.1 -W 2')
160+
assert '1 packets transmitted, 0 received' in out
161+
162+
switch.disconnect_device(d1)
163+
switch.disconnect_device(d2)

0 commit comments

Comments
 (0)