Do revert NAT to ICMP embedded packet
According to RFC5508, the embedded packet of icmp message should do revert NAT. Or else, the icmp packet will become invalid. Change-Id: Iaf4f1793d35b513e9573726707042de3aad4fce3 Partially-implements: blueprint traceroute-support
This commit is contained in:
parent
cf32307257
commit
6d1a244652
@ -20,6 +20,8 @@ from neutron_lib import constants as n_const
|
||||
from oslo_log import log
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.lib.packet import icmp
|
||||
from ryu.lib.packet import in_proto
|
||||
from ryu.lib.packet import ipv4
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.ofproto import ether
|
||||
import six
|
||||
@ -29,6 +31,7 @@ from dragonflow import conf as cfg
|
||||
from dragonflow.controller.common import arp_responder
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.controller.common import icmp_error_generator
|
||||
from dragonflow.controller.common import utils
|
||||
from dragonflow.controller import df_base_app
|
||||
|
||||
|
||||
@ -36,6 +39,10 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
FIP_GW_RESOLVING_STATUS = 'resolving'
|
||||
|
||||
EGRESS = 'egress'
|
||||
|
||||
INGRESS = 'ingress'
|
||||
|
||||
|
||||
class DNATApp(df_base_app.DFlowApp):
|
||||
|
||||
@ -76,6 +83,11 @@ class DNATApp(df_base_app.DFlowApp):
|
||||
self.send_packet(in_port, icmp_ttl_pkt)
|
||||
return
|
||||
|
||||
pkt = packet.Packet(msg.data)
|
||||
reply_pkt = self._revert_nat_for_icmp_embedded_packet(pkt, INGRESS)
|
||||
out_port = msg.match.get('reg7')
|
||||
self.send_packet(out_port, reply_pkt)
|
||||
|
||||
def egress_packet_in_handler(self, event):
|
||||
msg = event.msg
|
||||
ofproto = self.ofproto
|
||||
@ -97,6 +109,31 @@ class DNATApp(df_base_app.DFlowApp):
|
||||
"can't be recognized."), e_pkt.src)
|
||||
return
|
||||
|
||||
if self.external_bridge_mac:
|
||||
reply_pkt = self._revert_nat_for_icmp_embedded_packet(pkt, EGRESS)
|
||||
self.send_packet(self.external_ofport, reply_pkt)
|
||||
|
||||
def _revert_nat_for_icmp_embedded_packet(self, pkt, direction):
|
||||
e_pkt = pkt.get_protocol(ethernet.ethernet)
|
||||
ipv4_pkt = pkt.get_protocol(ipv4.ipv4)
|
||||
icmp_pkt = pkt.get_protocol(icmp.icmp)
|
||||
|
||||
embeded_ipv4_pkt, _, payload = ipv4.ipv4.parser(icmp_pkt.data.data)
|
||||
if direction == EGRESS:
|
||||
embeded_ipv4_pkt.dst = ipv4_pkt.src
|
||||
else:
|
||||
embeded_ipv4_pkt.src = ipv4_pkt.dst
|
||||
embeded_data = embeded_ipv4_pkt.serialize(None, None) + payload
|
||||
icmp_pkt.data.data = embeded_data
|
||||
# Re-calculate when encoding
|
||||
icmp_pkt.csum = 0
|
||||
|
||||
reply_pkt = packet.Packet()
|
||||
reply_pkt.add_protocol(e_pkt)
|
||||
reply_pkt.add_protocol(ipv4_pkt)
|
||||
reply_pkt.add_protocol(icmp_pkt)
|
||||
return reply_pkt
|
||||
|
||||
def ovs_port_updated(self, ovs_port):
|
||||
if ovs_port.get_name() != self.external_network_bridge:
|
||||
return
|
||||
@ -219,6 +256,31 @@ class DNATApp(df_base_app.DFlowApp):
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
|
||||
# Add flows to packet-in icmp time exceed and icmp unreachable message
|
||||
lport = self.db_store.get_local_port(floatingip.get_lport_id())
|
||||
lport_ofport = lport.get_external_value('ofport')
|
||||
actions = [
|
||||
parser.OFPActionDecNwTtl(),
|
||||
parser.OFPActionSetField(eth_src=vm_gateway_mac),
|
||||
parser.OFPActionSetField(eth_dst=vm_mac),
|
||||
parser.OFPActionSetField(ipv4_dst=vm_ip),
|
||||
parser.OFPActionSetField(reg7=lport_ofport),
|
||||
parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
||||
ofproto.OFPCML_NO_BUFFER)
|
||||
]
|
||||
action_inst = [parser.OFPInstructionActions(
|
||||
ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
||||
for icmp_type in (icmp.ICMP_DEST_UNREACH, icmp.ICMP_TIME_EXCEEDED):
|
||||
match = parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
|
||||
ip_proto=in_proto.IPPROTO_ICMP,
|
||||
icmpv4_type=icmp_type,
|
||||
ipv4_dst=floatingip.get_ip_address())
|
||||
self.mod_flow(
|
||||
inst=action_inst,
|
||||
table_id=const.INGRESS_NAT_TABLE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _remove_dnat_ingress_rules(self, floatingip):
|
||||
parser = self.parser
|
||||
ofproto = self.ofproto
|
||||
@ -233,9 +295,10 @@ class DNATApp(df_base_app.DFlowApp):
|
||||
def _get_dnat_egress_match(self, floatingip):
|
||||
_, vm_ip, _, local_network_id = self._get_vm_port_info(floatingip)
|
||||
parser = self.parser
|
||||
match = parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
|
||||
metadata=local_network_id,
|
||||
ipv4_src=vm_ip)
|
||||
match = parser.OFPMatch()
|
||||
match.set_dl_type(ether.ETH_TYPE_IP)
|
||||
match.set_metadata(local_network_id)
|
||||
match.set_ipv4_src(utils.ipv4_text_to_int(vm_ip))
|
||||
return match
|
||||
|
||||
def _install_dnat_egress_rules(self, floatingip, network_bridge_mac):
|
||||
@ -261,6 +324,21 @@ class DNATApp(df_base_app.DFlowApp):
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
match=match)
|
||||
|
||||
# Add flows to packet-in icmp time exceed and icmp unreachable message
|
||||
actions.append(parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
||||
ofproto.OFPCML_NO_BUFFER))
|
||||
action_inst = [parser.OFPInstructionActions(
|
||||
ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
||||
for icmp_type in (icmp.ICMP_DEST_UNREACH, icmp.ICMP_TIME_EXCEEDED):
|
||||
match = self._get_dnat_egress_match(floatingip)
|
||||
match.set_ip_proto(in_proto.IPPROTO_ICMP)
|
||||
match.set_icmpv4_type(icmp_type)
|
||||
self.mod_flow(
|
||||
inst=action_inst,
|
||||
table_id=const.EGRESS_NAT_TABLE,
|
||||
priority=const.PRIORITY_HIGH,
|
||||
match=match)
|
||||
|
||||
def _remove_dnat_egress_rules(self, floatingip):
|
||||
ofproto = self.ofproto
|
||||
match = self._get_dnat_egress_match(floatingip)
|
||||
|
@ -813,6 +813,29 @@ class RyuICMPTimeExceedFilter(RyuICMPFilter):
|
||||
return True
|
||||
|
||||
|
||||
class RyuICMPUnreachFilter(RyuICMPFilter):
|
||||
"""
|
||||
A filter to detect ICMP unreachable messages.
|
||||
:param get_ip: Return an object contained the original IP header
|
||||
:type get_ip: Callable with no arguments.
|
||||
"""
|
||||
def __init__(self, get_ip):
|
||||
super(RyuICMPUnreachFilter, self).__init__()
|
||||
self.get_ip = get_ip
|
||||
|
||||
def filter_icmp(self, pkt, icmp_prot):
|
||||
if icmp_prot.type != icmp.ICMP_DEST_UNREACH:
|
||||
return False
|
||||
ip_pkt = self.get_ip()
|
||||
embedded_ip_pkt, c, p = ipv4.ipv4.parser(icmp_prot.data.data)
|
||||
if ip_pkt.src != embedded_ip_pkt.src:
|
||||
return False
|
||||
if ip_pkt.dst != embedded_ip_pkt.dst:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Action(object):
|
||||
"""Base class of actions to execute. Actions are executed on matched
|
||||
packets in policy rules (PortPolicyRule).
|
||||
|
@ -1772,14 +1772,14 @@ class TestDNATApp(test_base.DFTestBase):
|
||||
self.topology.close()
|
||||
raise
|
||||
|
||||
def _create_ttl_test_port_policies(self):
|
||||
def _create_icmp_test_port_policies(self, icmp_filter):
|
||||
ignore_action = app_testing_objects.IgnoreAction()
|
||||
raise_action = app_testing_objects.RaiseAction("Unexpected packet")
|
||||
key = (self.subnet.subnet_id, self.port.port_id)
|
||||
rules = [
|
||||
app_testing_objects.PortPolicyRule(
|
||||
# Detect ICMP time exceed, end simulation
|
||||
app_testing_objects.RyuICMPTimeExceedFilter(self._get_ip),
|
||||
# Detect ICMP, end simulation
|
||||
icmp_filter(self._get_ip),
|
||||
actions=[app_testing_objects.DisableRuleAction(),
|
||||
app_testing_objects.StopSimulationAction()]
|
||||
),
|
||||
@ -1804,7 +1804,7 @@ class TestDNATApp(test_base.DFTestBase):
|
||||
)
|
||||
return {key: policy}
|
||||
|
||||
def _create_ping_packet(self, dst_ip, ttl=255):
|
||||
def _create_packet(self, dst_ip, proto, ttl=255):
|
||||
router_interface = self.router.router_interfaces[
|
||||
self.subnet.subnet_id
|
||||
]
|
||||
@ -1820,17 +1820,23 @@ class TestDNATApp(test_base.DFTestBase):
|
||||
src=self.port.port.get_logical_port().get_ip(),
|
||||
dst=dst_ip,
|
||||
ttl=ttl,
|
||||
proto=ryu.lib.packet.ipv4.inet.IPPROTO_ICMP,
|
||||
)
|
||||
icmp = ryu.lib.packet.icmp.icmp(
|
||||
type_=ryu.lib.packet.icmp.ICMP_ECHO_REQUEST,
|
||||
data=ryu.lib.packet.icmp.echo(data=self._create_random_string())
|
||||
proto=proto,
|
||||
)
|
||||
if proto == ryu.lib.packet.ipv4.inet.IPPROTO_ICMP:
|
||||
ip_data = ryu.lib.packet.icmp.icmp(
|
||||
type_=ryu.lib.packet.icmp.ICMP_ECHO_REQUEST,
|
||||
data=ryu.lib.packet.icmp.echo(
|
||||
data=self._create_random_string())
|
||||
)
|
||||
elif proto == ryu.lib.packet.ipv4.inet.IPPROTO_UDP:
|
||||
ip_data = ryu.lib.packet.udp.udp(
|
||||
dst_port=33534,
|
||||
)
|
||||
self._ip = ip
|
||||
result = ryu.lib.packet.packet.Packet()
|
||||
result.add_protocol(ethernet)
|
||||
result.add_protocol(ip)
|
||||
result.add_protocol(icmp)
|
||||
result.add_protocol(ip_data)
|
||||
result.serialize()
|
||||
return result.data
|
||||
|
||||
@ -1839,8 +1845,10 @@ class TestDNATApp(test_base.DFTestBase):
|
||||
|
||||
def test_icmp_ttl_packet(self):
|
||||
ignore_action = app_testing_objects.IgnoreAction()
|
||||
initial_packet = self._create_ping_packet(
|
||||
self.topology.external_network.get_gw_ip(), ttl=1)
|
||||
initial_packet = self._create_packet(
|
||||
self.topology.external_network.get_gw_ip(),
|
||||
ryu.lib.packet.ipv4.inet.IPPROTO_ICMP,
|
||||
ttl=1)
|
||||
policy = self.store(
|
||||
app_testing_objects.Policy(
|
||||
initial_actions=[
|
||||
@ -1850,7 +1858,34 @@ class TestDNATApp(test_base.DFTestBase):
|
||||
str(initial_packet)
|
||||
),
|
||||
],
|
||||
port_policies=self._create_ttl_test_port_policies(),
|
||||
port_policies=self._create_icmp_test_port_policies(
|
||||
app_testing_objects.RyuICMPTimeExceedFilter),
|
||||
unknown_port_action=ignore_action
|
||||
)
|
||||
)
|
||||
policy.start(self.topology)
|
||||
policy.wait(const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
||||
if len(policy.exceptions) > 0:
|
||||
raise policy.exceptions[0]
|
||||
|
||||
def test_nat_embedded_packet(self):
|
||||
ignore_action = app_testing_objects.IgnoreAction()
|
||||
self.port.port.update({"security_groups": []})
|
||||
|
||||
initial_packet = self._create_packet(
|
||||
self.topology.external_network.get_gw_ip(),
|
||||
ryu.lib.packet.ipv4.inet.IPPROTO_UDP)
|
||||
policy = self.store(
|
||||
app_testing_objects.Policy(
|
||||
initial_actions=[
|
||||
app_testing_objects.SendAction(
|
||||
self.subnet.subnet_id,
|
||||
self.port.port_id,
|
||||
str(initial_packet)
|
||||
),
|
||||
],
|
||||
port_policies=self._create_icmp_test_port_policies(
|
||||
app_testing_objects.RyuICMPUnreachFilter),
|
||||
unknown_port_action=ignore_action
|
||||
)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user