Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(pipelined): blocking of local ipv6 addresses is supported #12339

Merged
merged 3 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
162 changes: 122 additions & 40 deletions lte/gateway/python/magma/pipelined/app/access_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
"""
import ipaddress
from collections import namedtuple
from enum import Enum

import netifaces
from magma.pipelined.app.base import ControllerType, MagmaController
from magma.pipelined.openflow import flows
from magma.pipelined.openflow.magma_match import MagmaMatch
from magma.pipelined.openflow.registers import Direction
from ryu.lib.packet import ether_types
from ryu.lib.packet.in_proto import IPPROTO_ICMP
from ryu.lib.packet.in_proto import IPPROTO_ICMP, IPPROTO_ICMPV6


class _IpVersion(Enum):
IPV4 = 1
IPV6 = 2


class AccessControlController(MagmaController):
Expand Down Expand Up @@ -161,22 +167,53 @@ def _install_local_ip_blocking_flows(self, datapath):
return
interfaces = netifaces.interfaces()
direction = self.CONFIG_INBOUND_DIRECTION
# block access to entire 127.* ip network
local_ipnet = ipaddress.ip_network('127.0.0.0/8')
self._install_ip_blocking_flow(datapath, local_ipnet, direction)

for iface in interfaces:
if_addrs = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
for addr in if_addrs:
if ipaddress.ip_address(addr['addr']) in local_ipnet:
continue
self.logger.info("Add blocking rule for: %s, iface %s", addr['addr'], iface)

ip_network = ipaddress.IPv4Network(addr['addr'])
self._install_ip_blocking_flow(datapath, ip_network, direction)
# Add flow to allow ICMP for monitoring flows.
if iface == self.config.mtr_interface:
self._install_local_icmp_flows(datapath, ip_network)

for ip_version in (_IpVersion.IPV4, _IpVersion.IPV6):
local_ipnet = AccessControlController._get_loopback_addresses(ip_version)
self._install_ip_blocking_flow(datapath, local_ipnet, direction, ip_version)

for iface in interfaces:
self._install_local_ip_blocking_flows_for_iface(datapath, direction, iface, ip_version, local_ipnet)

def _install_local_ip_blocking_flows_for_iface(self, datapath, direction, iface, ip_version, local_ipnet):
if_addrs = AccessControlController._get_interface_ip_addresses(ip_version, iface)

for addr in if_addrs:
current_addr = AccessControlController._get_address_from_wrapper(ip_version, addr)
if ipaddress.ip_address(current_addr) in local_ipnet:
continue
self.logger.info("Add blocking rule for: %s, iface %s", current_addr, iface)

ip_network = AccessControlController._get_network_from_address(ip_version, current_addr)
self._install_ip_blocking_flow(datapath, ip_network, direction, ip_version)
# Add flow to allow ICMP for monitoring flows.
if iface == self.config.mtr_interface:
self._install_local_icmp_flows(datapath, ip_network, ip_version)

@staticmethod
def _get_interface_ip_addresses(ip_version, iface):
if ip_version == _IpVersion.IPV4:
return netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
return netifaces.ifaddresses(iface).get(netifaces.AF_INET6, [])

@staticmethod
def _get_loopback_addresses(ip_version):
if ip_version == _IpVersion.IPV4:
return ipaddress.ip_network('127.0.0.0/8')
return ipaddress.ip_network('::1')

@staticmethod
def _get_network_from_address(ip_version, address):
if ip_version == _IpVersion.IPV4:
return ipaddress.IPv4Network(address)
return ipaddress.IPv6Network(address)

@staticmethod
def _get_address_from_wrapper(ip_version, addr):
if ip_version == _IpVersion.IPV4:
return addr['addr']
# remove suffix %interface, e.g. ::1%eth1, if it exists
return str(addr['addr'].split('%')[0])

def _install_ip_blocklist_flow(self, datapath):
"""
Expand All @@ -186,17 +223,11 @@ def _install_ip_blocklist_flow(self, datapath):
for entry in self.config.ip_blocklist:
ip_network = ipaddress.IPv4Network(entry['ip'])
direction = entry.get('direction', None)
self._install_ip_blocking_flow(datapath, ip_network, direction)
self._install_ip_blocking_flow(datapath, ip_network, direction, _IpVersion.IPV4)

def _install_local_icmp_flows(self, datapath, ip_network):
match = MagmaMatch(
direction=Direction.OUT,
eth_type=ether_types.ETH_TYPE_IP,
ipv4_dst=(
ip_network.network_address,
ip_network.netmask,
),
ip_proto=IPPROTO_ICMP,
def _install_local_icmp_flows(self, datapath, ip_network, ip_version):
match = AccessControlController._create_magma_match_icmp_flow(
ip_version, ip_network,
)
flows.add_resubmit_next_service_flow(
datapath, self.tbl_num,
Expand All @@ -205,7 +236,29 @@ def _install_local_icmp_flows(self, datapath, ip_network):
resubmit_table=self.next_table,
)

def _install_ip_blocking_flow(self, datapath, ip_network, direction):
@staticmethod
def _create_magma_match_icmp_flow(ip_version, ip_network):
if ip_version == _IpVersion.IPV4:
return MagmaMatch(
direction=Direction.OUT,
eth_type=ether_types.ETH_TYPE_IP,
ipv4_dst=(
ip_network.network_address,
ip_network.netmask,
),
ip_proto=IPPROTO_ICMP,
)
return MagmaMatch(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better readability you could add 'else' here.

direction=Direction.OUT,
eth_type=ether_types.ETH_TYPE_IPV6,
ipv6_dst=(
ip_network.network_address,
ip_network.netmask,
),
ip_proto=IPPROTO_ICMPV6,
)

def _install_ip_blocking_flow(self, datapath, ip_network, direction, ip_version):
"""
Install flows to drop any packets with ip address blocks matching the
blocklist.
Expand All @@ -221,29 +274,58 @@ def _install_ip_blocking_flow(self, datapath, ip_network, direction):
# If no direction is specified, both outbound and inbound traffic
# will be dropped.
if direction is None or direction == self.CONFIG_INBOUND_DIRECTION:
match = MagmaMatch(
match = AccessControlController._create_magma_match_blocking_flow_out(
ip_version, ip_network,
)
flows.add_drop_flow(
datapath, self.tbl_num, match, [],
priority=flows.DEFAULT_PRIORITY,
)
if direction is None or direction == self.CONFIG_OUTBOUND_DIRECTION:
match = AccessControlController._create_magma_match_blocking_flow_in(
ip_version, ip_network,
)
flows.add_drop_flow(
datapath, self.tbl_num, match, [],
priority=flows.DEFAULT_PRIORITY,
)

@staticmethod
def _create_magma_match_blocking_flow_out(ip_version, ip_network):
if ip_version == _IpVersion.IPV4:
return MagmaMatch(
direction=Direction.OUT,
eth_type=ether_types.ETH_TYPE_IP,
ipv4_dst=(
ip_network.network_address,
ip_network.netmask,
),
)
flows.add_drop_flow(
datapath, self.tbl_num, match, [],
priority=flows.DEFAULT_PRIORITY,
)
if direction is None or \
direction == self.CONFIG_OUTBOUND_DIRECTION:
match = MagmaMatch(
return MagmaMatch(
direction=Direction.OUT,
eth_type=ether_types.ETH_TYPE_IPV6,
ipv6_dst=(
ip_network.network_address,
ip_network.netmask,
),
)

@staticmethod
def _create_magma_match_blocking_flow_in(ip_version, ip_network):
if ip_version == _IpVersion.IPV4:
return MagmaMatch(
direction=Direction.IN,
eth_type=ether_types.ETH_TYPE_IP,
ipv4_src=(
ip_network.network_address,
ip_network.netmask,
),
)
flows.add_drop_flow(
datapath, self.tbl_num, match, [],
priority=flows.DEFAULT_PRIORITY,
)
return MagmaMatch(
direction=Direction.IN,
eth_type=ether_types.ETH_TYPE_IPV6,
ipv6_src=(
ip_network.network_address,
ip_network.netmask,
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -433,19 +433,22 @@ def create_service_manager(
return service_manager


def _parse_flow(flow):
def _parse_flow(flow, ipv6_prefix_only=False):
fields_to_remove = [
r'duration=[\d\w\.]*, ',
r'idle_age=[\d]*, ',
]
for field in fields_to_remove:
flow = re.sub(field, '', flow)
if ipv6_prefix_only:
flow = re.sub(r'ipv6_dst=fe80::[0-9,a-f,:]+ ', 'ipv6_dst=fe80::linkLocalSuffix ', flow)
return flow


def _get_current_bridge_snapshot(
bridge_name, service_manager,
include_stats=True,
ipv6_prefix_only=False,
) -> List[str]:
table_assignments = service_manager.get_all_table_assignments()
# Currently, the unit test setup library does not set up the ryu api app.
Expand All @@ -457,7 +460,7 @@ def _get_current_bridge_snapshot(
table_assignments,
include_stats=include_stats,
)
return [_parse_flow(flow) for flow in flows]
return [_parse_flow(flow, ipv6_prefix_only) for flow in flows]


def fail(
Expand Down Expand Up @@ -518,6 +521,7 @@ def assert_bridge_snapshot_match(
service_manager: ServiceManager,
snapshot_name: Optional[str] = None,
include_stats: bool = True,
ipv6_prefix_only: bool = False,
):
"""
Verifies the current bridge snapshot matches the snapshot saved in file for
Expand All @@ -536,6 +540,7 @@ def assert_bridge_snapshot_match(
bridge_name,
service_manager,
include_stats,
ipv6_prefix_only,
)

snapshot_file, expected = expected_snapshot(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=100,icmp,reg1=0x1,nw_dst=10.1.0.1 actions=resubmit(,middle(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=100,icmp6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=resubmit(,middle(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=access_control(main_table), n_packets=2, n_bytes=68, priority=10,ip,reg1=0x1,nw_dst=127.0.0.0/8 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=10.0.2.15 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=192.168.60.142 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=192.168.129.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=172.17.0.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=192.168.128.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=10.1.0.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=172.17.0.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ip,reg1=0x1,nw_dst=192.168.1.1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=::1 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=2020::10 actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=10,ipv6,reg1=0x1,ipv6_dst=fe80::linkLocalSuffix actions=drop
cookie=0x0, table=access_control(main_table), n_packets=0, n_bytes=0, priority=0,reg1=0x10 actions=resubmit(,middle(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=access_control(main_table), n_packets=1, n_bytes=34, priority=0,reg1=0x1 actions=resubmit(,access_control(scratch_table_0)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=access_control(scratch_table_0), n_packets=1, n_bytes=34, priority=0 actions=resubmit(,middle(main_table)),set_field:0->reg0,set_field:0->reg3