一、Scapy是啥?
- Scapy是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包等等。它可以很容易地处理一些典型操作,比如端口扫描,tracerouting,探测,单元测试,攻击或网络发现(可替代hping,NMAP,arpspoof,ARP-SK,arping,tcpdump,tethereal,POF等)。最重要的他还有很多更优秀的特性——-发送无效数据帧、注入修改的802.11数据帧、在WEP上解码加密通道(VOIP)、ARP缓存攻击(VLAN)等,这也是其他工具无法处理完成的。
二、安装使用?
- 安装不说了,自己查查,使用的话,只举个例子,这篇文章主要是讲怎么在ryu上通过scapy组包,不能扯太远。
- 具体使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
root@centec239-OptiPlex-380:/home/centec-239/ryu-4.1/ryu/app/ryu_apps/feature# scapy INFO: Can't import python gnuplot wrapper . Won't be able to plot. INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). WARNING: No route found for IPv6 destination :: (no default route?) Welcome to Scapy (2.3.2) ###2.1 组一个目的ip为"1.2.3.4"的报文 >>> pkt = Ether()/IP(dst="1.2.3.4") >>> pkt > ###2.2 将这个报文从"eth2"口发出去 >>> sendp(pkt,iface="eth2") . Sent 1 packets. ###2.3 通过抓包确认报文发出去了 root@centec239-OptiPlex-380:/home/centec-239# tcpdump -i eth2 -exxxxx 04:43:44.079992 14:fe:b5:ef:e2:f0 (oui Unknown) > 00:1e:08:09:67:6d (oui Unknown), ethertype IPv4 (0x0800), length 34: 10.10.33.239 > 1.2.3.4: ip 0 0x0000: 001e 0809 676d 14fe b5ef e2f0 0800 4500 0x0010: 0014 0001 0000 4000 4aeb 0a0a 21ef 0102 0x0020: 0304 |
三、为什么要把scapy组包方式引入ryu?
- 原因是用ryu套件如果去发ip数据报文进行测试,只能通过payload的方式去写具体数据报文内容,checksum还要自己算,而且看起来一点也不直观,如果引入scapy组包方式,那就简单了,想组啥报文就组啥报文。
3.1 怎么将scapy引入ryu?
(1)将scapy相关import进来,就存在ryu/app目录下好了,名字这里叫scapy_1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# Distributed under the OpenFlow Software License (see LICENSE) # Copyright (c) 2010 The Board of Trustees of The Leland Stanford Junior University # Copyright (c) 2012, 2013 Big Switch Networks, Inc. """ Wrap scapy to satisfy pylint """ import sys try: import scapy.config import scapy.route import scapy.layers.l2 import scapy.layers.inet import scapy.layers.sctp except ImportError: sys.exit("Need to install scapy for packet parsing") Ether = scapy.layers.l2.Ether LLC = scapy.layers.l2.LLC SNAP = scapy.layers.l2.SNAP Dot1Q = scapy.layers.l2.Dot1Q IP = scapy.layers.inet.IP IPOption = scapy.layers.inet.IPOption ARP = scapy.layers.inet.ARP TCP = scapy.layers.inet.TCP UDP = scapy.layers.inet.UDP ICMP = scapy.layers.inet.ICMP SCTP = scapy.layers.sctp.SCTP (2)定义个tcp组包方法调用的其实就是scapy组包方式,很简单,这步主要就是生成一个tcp报文了,保存成ip_pkts.py,也放在ryu/app目录下 ``` python import sys import copy import logging import types import time import re import scapy_1 as scapy global skipped_test_count skipped_test_count = 0 _import_blacklist = set(locals().keys()) # Some useful defines IP_ETHERTYPE = 0x800 TCP_PROTOCOL = 0x6 UDP_PROTOCOL = 0x11 MINSIZE = 0 def simple_tcp_packet(pktlen=100, eth_dst='00:01:02:03:04:05', eth_src='00:06:07:08:09:0a', dl_vlan_enable=False, vlan_vid=0, vlan_pcp=0, dl_vlan_cfi=0, ip_src='192.168.0.1', ip_dst='192.168.0.2', ip_tos=0, ip_ttl=64, tcp_sport=1234, tcp_dport=80, tcp_flags="S", ip_ihl=None, ip_options=False ): """ Return a simple dataplane TCP packet Supports a few parameters: @param len Length of packet in bytes w/o CRC @param eth_dst Destinatino MAC @param eth_src Source MAC @param dl_vlan_enable True if the packet is with vlan, False otherwise @param vlan_vid VLAN ID @param vlan_pcp VLAN priority @param ip_src IP source @param ip_dst IP destination @param ip_tos IP ToS @param ip_ttl IP TTL @param tcp_dport TCP destination port @param tcp_sport TCP source port @param tcp_flags TCP Control flags Generates a simple TCP request. Users shouldn't assume anything about this packet other than that it is a valid ethernet/IP/TCP frame. """ if MINSIZE > pktlen: pktlen = MINSIZE # Note Dot1Q.id is really CFI if (dl_vlan_enable): pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \ scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags) else: if not ip_options: pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags) else: pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \ scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \ scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags) pkt = pkt/("D" * (pktlen - len(pkt))) return pkt |
(3)编写app,验证一下组包效果,下一条匹配目的ip送控制器的flow,引用我们上面学会的组包方式发包验证,最终控制器收到这个报文,并且解析一下,保存成ryu_send_pkts.py,也放在ryu/app目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
from ryu.base import app_manager from ryu.controller.handler import CONFIG_DISPATCHER from ryu.controller.handler import MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.controller import ofp_event import logging from ryu.ofproto import ofproto_v1_3 from ip_pkts import simple_tcp_packet import socket import struct from ryu.lib.packet import packet from ryu.lib.packet.ethernet import ethernet from ryu.lib.packet.ipv4 import ipv4 from ryu.lib.packet.tcp import tcp LOG = logging.getLogger("ryu_send_pkts") class ryu_send_pkts(app_manager.RyuApp): def __init__(self, *args, **kwargs): self.__name__ = "flow_loading_perofrmance" super(ryu_send_pkts, self).__init__(*args, **kwargs) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def send_port_stats_request(self, ev): print "========================Add Flow========================" print "Add Flow match ip_dst=192.168.0.2,actions to controller" print "========================================================" datapath = ev.msg.datapath parser = datapath.ofproto_parser priority = 65535 match = parser.OFPMatch() match.set_dl_type(int(int(0x800))) match.set_in_port(5) oas = [] value = "192.168.0.2/32" ip = value.split('/') nw_dst = struct.unpack('!I', socket.inet_aton(ip[0]))[0] mask = 32 if len(ip) == 2: mask = int(ip[1]) # sanity check. if mask <= 0 or mask > 32: mask = 32 v = 0xffffffff >> (32 - mask) v = v << (32 - mask) match.set_ipv4_dst_masked(nw_dst, v) oa = parser.OFPActionOutput(ofproto_v1_3.OFPP_CONTROLLER, ofproto_v1_3.OFPCML_MAX) oas.append(oa) inst = parser.OFPInstructionActions(ofproto_v1_3.OFPIT_APPLY_ACTIONS, oas) insts = [] insts.append(inst) request = parser.OFPFlowMod(datapath, 0, 0, 0, # table 0 ofproto_v1_3.OFPFC_ADD, 0, 0, priority, # must have higher priority. ofproto_v1_3.OFP_NO_BUFFER, ofproto_v1_3.OFPP_ANY, 0xffffffff, 0, match, insts) datapath.send_msg(request) scapy_pkt = simple_tcp_packet(eth_dst='00:01:02:03:04:05',eth_src='00:06:07:08:09:0a',ip_src='192.168.0.1',ip_dst='192.168.0.2',tcp_sport=1234,tcp_dport=80) print "\n\n\n\n\n====================Send pkts use scapy================" print "Send a pkt use scapy" print "========================================================" scapy_pkt.show() self.send_pktss(scapy_pkt) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): msg = ev.msg pkt = packet.Packet(msg.data) eth = pkt.get_protocol(ethernet) print "\n\n\n\n\n================Check the received pkt=================" print "=======L2-header=======" print "ether_dst is %r" % eth.dst print "ether_src is %r" % eth.src print "ether_type is %r" % eth.ethertype print "=======================" packetip = pkt.get_protocol(ipv4) print "=======L3-header=======" print "ip_src is %r" % packetip.src print "ip_dst is %r" % packetip.dst print "ip_protocol is %r" % packetip.proto print "=======================" packettcp = pkt.get_protocol(tcp) print "=======L4-header=======" print "tcp_src is %r" % packettcp.src_port print "tcp_dst is %r" % packettcp.dst_port print "=======================" def send_pktss(self,scapy_pkt): rawSocket = socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(0x0800)) rawSocket.bind(("eth2",socket.htons(0x0800))) packet = str(scapy_pkt) rawSocket.send(packet+"Hello there") |
(4)最终运行效果如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
========================Add Flow======================== Add Flow match ip_dst=192.168.0.2,actions to controller ======================================================== (5)下发一条匹配ip_dst是192.168.0.2送controller的flow ###利用scapy组个tcp报文 ====================Send pkts use scapy================ Send a pkt use scapy ======================================================== ###[ Ethernet ]### dst = 00:01:02:03:04:05 src = 00:06:07:08:09:0a type = 0x800 ###[ IP ]### version = 4 ihl = None tos = 0x0 len = None id = 1 flags = frag = 0 ttl = 64 proto = tcp chksum = None src = 192.168.0.1 dst = 192.168.0.2 \options \ ###[ TCP ]### sport = 1234 dport = http seq = 0 ack = 0 dataofs = None reserved = 0 flags = S window = 8192 chksum = None urgptr = 0 options = {} ###[ Raw ]### load = 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD' ###最终匹配flow并且送控制器之后,控制器解析出来报文内容 ================Check the received pkt================= =======L2-header======= ether_dst is '00:01:02:03:04:05' ether_src is '00:06:07:08:09:0a' ether_type is 2048 ======================= =======L3-header======= ip_src is '192.168.0.1' ip_dst is '192.168.0.2' ip_protocol is 6 ======================= =======L4-header======= tcp_src is 1234 tcp_dst is 80 ======================= |
四、总结思考
- 这里我只是举个组tcp的例子,有兴趣的同学可以把其他报文也通过这种方式组起来,方便以后调用。
关于作者
- 邮件(jinl@centecnetworks.com)
- 作者(金利#盛科网络SDN组测试工程师)