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:
Hong Hui Xiao 2017-01-22 09:36:42 +08:00
parent cf32307257
commit 6d1a244652
3 changed files with 152 additions and 16 deletions

View File

@ -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)

View File

@ -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).

View File

@ -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
)
)