0x00 目录

[TOC]

0x01 前言

如图1所示,OpenFlow 1.0中定义了Set动作,用于修改数据包的首部字段,这包括源宿MAC地址、源宿IP地址、源宿端口号等字段。通过将指定IP地址段与Set动作绑定作为流表项,即可在OpenFlow交换机上实现NAT的功能。

图1 OpenFlow1.0中Action定义

假设现在需要通过修改IP协议号,来实现一个数据包的伪装。例如:原本是ICMP的包,通过修改IP协议号(1->6)后,对外表现为TCP包。

此时OpenFlow 1.0显然不能满足需求,原因就是OpenFlow 1.0只能提供有限的数据包首部字段修改。

注:OpenFlow 1.3以后的版本,增加SET_FIELD动作,支持按照OXM TLV格式来修改任意数据包首部字段

为了在OpenFlow 1.0中实现修改IP协议号,可以利用Packet-In的消息处理机制。

0x02 基于Packet-In消息处理机制修改IP协议号

基于Packet-In消息处理机制实现IP协议号修改的基本思路:

  1. 显示下发流表,对指定要修改IP协议号的数据包通过Packet-In消息上传到控制器
  2. 修改控制器上的Packet-In消息处理函数,解析数据包首部,修改IP首部中IP协议号字段

例子:如图2所示,假设有主机H1向H2发送ICMP包,现在希望将这个ICMP包的IP协议号字段进行修改,使其对外表现为TCP包,按照上面提到的思路即可实现。

图2 基于Packet-In消息处理机制修改IP协议号

注:上图中H1通过ping发送给H2的ICMP包经过控制器修改IP协议号后变为TCP包,H2在收到这个包之后,实际上不会进行ICMP响应,因此最终H1上会显示ping失败

0x03 POX + Mininet实验验证

按照0x02中的例子,使用POX和Mininet进行验证。

实验环境

图3 实验拓扑

实验步骤

  1. H1向H2发送ICMP包(h1 ping h2 -c 1)
  2. SW1将ICMP包Packet-In上传到控制器
  3. 控制器修改IP协议号(ICMP -> TCP),变换后的TCP包(原ICMP包)Packet-Out至SW2并指示SW2转发给H2
  4. 在SW2与H1的链路、SW2与H2的链路上抓包,预期实验结果:前者抓到ICMP包,后者抓到TCP包,并且H1上显示ping失败

编写POX应用

为了实现IP协议号的修改,需要在POX上编写一个应用transfer_ipproto(存放在POX源码的ext文件夹下),从而实现下面两个功能:

  1. 下发流表给SW1,指定ICMP包通过Packet-In上传到控制器处理
  2. 添加Packet-In消息处理函数,解析数据包首部,修改IP协议号后通过Packet-Out消息转发至SW2,并指导SW2将修改IP协议号后的数据包转发至H2

transfer_ipproto源码如下所示:

   # -*- coding: utf-8 -*-
# 功能:
# 通过Packet-In机制修改IP协议号及TCP选项
# 模块Packet-In处理函数中下发默认流表,构建路径
#
# 网络拓扑:
# h1 - sw1 - sw2 - h2
#
# @Update History:
# @Author  Date            Content
# @CTZ     2020-03-24      添加必要的注释
# @CTZ     2020-03-31      完成代码编写及调试

from pox.core import core
import pox.openflow.libopenflow_01 as of
from pox.lib.packet.ethernet import ethernet
from pox.lib.packet.icmp import icmp
from pox.lib.packet.ipv4 import ipv4
from pox.lib.addresses import IPAddr
from pox.lib.util import dpid_to_str, str_to_dpid
log = core.getLogger()

class transfer_ipproto(object):
    def __init__(self):
        log.info("初始化模块")
        # h1信息
        self.h1_info = {}
        self.h1_info['ip'] = "10.0.0.1"
        self.h1_info['mac'] = "00:00:00:00:00:01"

        # h2信息
        self.h2_info = {}
        self.h2_info['ip'] = "10.0.0.2"
        self.h2_info['mac'] = "00:00:00:00:00:02"

        # sw1信息
        self.sw1_info = {}
        self.sw1_info['dpid'] = str_to_dpid("0x0000000000000001")
        self.sw1_info['h1'] = {}
        self.sw1_info['h1']['port'] = 1
        self.sw1_info['sw2'] = {}
        self.sw1_info['sw2']['port'] = 2

        # sw2信息
        self.sw2_info = {}
        self.sw2_info['dpid'] = str_to_dpid("0x0000000000000002")
        self.sw2_info['sw1'] = {}
        self.sw2_info['sw1']['port'] = 1
        self.sw2_info['h2'] = {}
        self.sw2_info['h2']['port'] = 2

        # 虚拟IP协议号
        #self.ipproto = ipv4.ICMP_PROTOCOL
        self.ipproto = ipv4.TCP_PROTOCOL

        # 统计packetin处理次数
        self.packetin_count = 0
        core.openflow.addListeners(self)

    def _handle_PacketIn (self, event):
        self.packetin_count = self.packetin_count+1
        log.info(str(self.packetin_count) + " : PacketIn处理开始")

        #下发初始流表
        if self.packetin_count == 1:
            #sw1 -> sw2
            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h1_info["ip"])
            msg.match.nw_dst = IPAddr(self.h2_info["ip"])
            msg.actions.append(of.ofp_action_output(port=self.sw1_info['sw2']['port']))
            core.openflow.sendToDPID(self.sw1_info['dpid'], msg)

            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h2_info["ip"])
            msg.match.nw_dst = IPAddr(self.h1_info["ip"])
            msg.actions.append(of.ofp_action_output(port=self.sw1_info['h1']['port']))
            core.openflow.sendToDPID(self.sw1_info['dpid'], msg)

            # sw1 -> controller
            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h1_info["ip"])
            msg.match.nw_dst = IPAddr(self.h2_info["ip"])
            msg.match.nw_proto = ipv4.TCP_PROTOCOL
            msg.priority = 0x8001  # 大于默认优先级0x8000
            msg.actions.append(of.ofp_action_output(port=of.ofp_port_rev_map['OFPP_CONTROLLER']))
            core.openflow.sendToDPID(self.sw1_info['dpid'], msg)

            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h1_info["ip"])
            msg.match.nw_dst = IPAddr(self.h2_info["ip"])
            msg.match.nw_proto = ipv4.ICMP_PROTOCOL
            msg.priority = 0x8001  # 大于默认优先级0x8000
            msg.actions.append(of.ofp_action_output(port=of.ofp_port_rev_map['OFPP_CONTROLLER']))
            core.openflow.sendToDPID(self.sw1_info['dpid'], msg)

            # sw2 -> sw1
            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h2_info["ip"])
            msg.match.nw_dst = IPAddr(self.h1_info["ip"])
            msg.actions.append(of.ofp_action_output(port=self.sw2_info['sw1']['port']))
            core.openflow.sendToDPID(self.sw2_info['dpid'], msg)

            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h1_info["ip"])
            msg.match.nw_dst = IPAddr(self.h2_info["ip"])
            msg.actions.append(of.ofp_action_output(port=self.sw2_info['h2']['port']))
            core.openflow.sendToDPID(self.sw2_info['dpid'], msg)

            # sw2 -> controller
            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h2_info["ip"])
            msg.match.nw_dst = IPAddr(self.h1_info["ip"])
            msg.match.nw_proto = ipv4.TCP_PROTOCOL
            msg.priority = 0x8001  # 大于默认优先级0x8000
            msg.actions.append(of.ofp_action_output(port=of.ofp_port_rev_map['OFPP_CONTROLLER']))
            core.openflow.sendToDPID(self.sw2_info['dpid'], msg)

            msg = of.ofp_flow_mod()
            msg.match.dl_type = 0x800
            msg.match.nw_src = IPAddr(self.h2_info["ip"])
            msg.match.nw_dst = IPAddr(self.h1_info["ip"])
            msg.match.nw_proto = ipv4.ICMP_PROTOCOL
            msg.priority = 0x8001  # 大于默认优先级0x8000
            msg.actions.append(of.ofp_action_output(port=of.ofp_port_rev_map['OFPP_CONTROLLER']))
            core.openflow.sendToDPID(self.sw2_info['dpid'], msg)

            log.info("已经下发初始流表")

        dpid = event.connection.dpid
        in_port = event.port
        packet = event.parsed
        if not packet.parsed:
            log.warning("%i %i ignoring unparsed packet", dpid, in_port)
            return

        if isinstance(packet, ethernet):
            log.info("解析包:eth")
            eth_packet = packet
            #if eth_packet.type == packet.IP_TYPE:
            if isinstance(eth_packet.payload, ipv4):
                log.info("解析包:ipv4")
                ip_packet = eth_packet.payload

                if isinstance(ip_packet.payload, icmp):
                    log.info("解析包:icmp")
                    icmp_packet = ip_packet.payload

                    i = ipv4(v=ip_packet.v,
                             hl=ip_packet.hl,
                             tos=ip_packet.tos,
                             iplen=ip_packet.iplen,
                             id=ip_packet.id,
                             flags=ip_packet.flags,
                             frag=ip_packet.frag,
                             ttl=ip_packet.ttl,
                             srcip=ip_packet.srcip,
                             dstip=ip_packet.dstip,
                             protocol=self.ipproto)  # 修改IP协议号
                             #csum
                    i.set_payload(icmp_packet)

                    e = ethernet(type=ethernet.IP_TYPE,
                                 src=eth_packet.src,
                                 dst=eth_packet.dst
                                 )
                    e.set_payload(i)

                    # h1 -> h2的ICMP包执行协议号变换
                    if ip_packet.srcip == IPAddr(self.h1_info["ip"]) and ip_packet.dstip == IPAddr(self.h2_info["ip"]):
                        msg = of.ofp_packet_out()
                        msg.data = e.pack()
                        msg.actions.append(of.ofp_action_output(port=self.sw2_info['h2']['port']))
                        core.openflow.sendToDPID(self.sw2_info['dpid'], msg)
                        log.info("h1 -> h2的ICMP包处理结束:IP协议号变换")
                    # h2 -> h1的ICMP包执行协议号变换
                    elif ip_packet.srcip == self.h2_info["ip"] and ip_packet.dstip == self.h1_info["ip"]:
                        msg = of.ofp_packet_out()
                        msg.data = e.pack()
                        msg.actions.append(of.ofp_action_output(port=self.sw1_info['h1']['port']))
                        core.openflow.sendToDPID(self.sw1_info['dpid'], msg)
                        log.info("h2 -> h1的ICMP包处理结束:IP协议号变换")
        log.info("PacketIn处理结束")
        return

def launch():
    core.registerNew(transfer_ipproto_tcpoptions)

配置POX启动参数

图4 POX启动参数

注:需要用到POX的arp_responder应用实现ARP的功能

Mininet验证

1.启动POX、Mininet并打开H1和H2的终端

图5 启动Mininet并打开H1和H2的终端

2.在H1和H2上启动wirehsark,分别配置显示过滤器为icmp和tcp

启动wireshark命令:sudo wireshark

图6 启动wireshark并配置显示过滤器

3.在Mininet终端上发起Ping,H1向H2发送一个ICMP包

图7 H1向H2发送一个ICMP包

4.查看wireshark抓包结果

如图8和图9所示,H1上抓到ICMP包

图8 H1上抓到的ICMP包IP首部

图9 H1上抓到的ICMP包IP Payload

如图10和图11所示,H2上抓到TCP包

图10 H2上抓到的TCP包IP首部

图11 H2上抓到的TCP包IP Payload

根据图9和图11中的IP Payload(数据包原始字节流)相同可知,H2上抓到的TCP包就是修改了IP协议号的ICMP包。

至此,该实验验证了可以通过Packet-In消息处理机制来实现IP协议号的修改。该方法除了能够修改IP协议号外,也能修改数据包中首部的其他字段。

0x04 总结

修改数据包首部字段的方法:

  1. 流表Set动作

    注:OpenFlow1.3以前版本仅支持修改部分首部字段,OpenFlow1.3以后版本支持修改任意首部字段

  2. 基于Packet-In消息处理机制修改

0x05 参考



SDN     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!