scapy-整体设计和数据发送流程

晨曦之光 发布于 2012/04/10 14:58
阅读 535
收藏 0

【领华为电脑包】容器化时代到来!跳转机分配问题终于“有救”了!>>>

scapy涉及了pf_packet套结字编程,路由以及面向对象设计等诸技术,它本身使用python编写,熟悉python的家伙这下有福了,可以目睹这个优秀的软件而无需下载编译源码之类的了。
scapy中到处都是class,不过要说起来,使用class表示协议栈是再方便不过的了,协议栈本身实现的每一个层就是一个class,而每一个经过该层的数据包就是该class的一个对象,一个class拥有很多属性,比如针对ip层来讲,它有源地址,目的地址,ttl等属性,同时它还有send,receive等方法,而每一个经过的数据包只是将这一切具体化了而已,比如一个ip层的数据包可以是这样子的:[src=1.2.3.4,dst=4.3.2.1,ttl=100,...],然后它可以使用send方法被发送给下层,或者说发送给对方的对等层,从这个意义上讲,我们完全可以简单的使用oo的技术写出一个用户态的协议栈,数据包的来源来自pcap抓取的包,而我们也可以自己使用上述的oo技术构造一个数据包,逐层封装(一直到链路层)后使用packet技术发出去,如果实在不想使用packet技术,也可以搞一个很简单的硬件,比如串口,转接于双绞线(此含义即一条线的一端为rj45口,插入以太网卡,另一端为串口,插入机器串口),这样的话,从串口读出的就是裸数据了,然后进入我们的用户态使用oo实现的协议栈,解析之后传给我们自己的应用或者再次通过此技术forward出去,注意,我们虽然无法将解析后的数据传给别的应用,因为别的应用使用内核的协议栈,其socket是工作于内核的,然而我们可以代理别的应用,此技术就是本地代理。
     简单并且直接的说,scapy就是一个简单的用户态协议栈实现,虽然它还要使用内核的路由功能。我本身非常欣赏scapy这个东西的整体设计,而它的使用也是非常简单,只需先在Debian上apt-get install之,然后直接在命令行输入scapy即可,会出现下列提示符:
Welcome to Scapy (0.9.17.1beta)
>>>
此时我们输入一行数据回车之:
>>> a=IP(src="192.168.40.246",dst="193.168.40.34")/TCP(sport=1111,dport=2222)
这样我们就等于构造好了一个ip数据包了,然后我们再输入下列行回车之:
>>> sr(a)
数据包a即被发送出去了,这一切的操作就是这么简单。上述的IP(...)和TCP(...)中的IP和TCP实际上就是类,圆括号内的数据即可以构造出两个对象,一个IP的,另一个TCP的,两个对象堆列在一起即可形成一个ip数据包,其上是tcp数据,然后当我们调用sr的时候,数据被发送,sr实际上是一个全局的函数,不属于任何类的,熟悉python的一定明白这一点。
def sr(x,filter=None, iface=None, *args,**kargs):
    if not kargs.has_key("timeout"):
        kargs["timeout"] = -1
    s = conf.L3socket(filter=filter, iface=iface)
    a,b,c=sndrcv(s,x,*args,**kargs)
    s.close()
    return a,b
核心之处在于构造s和发送s,s由L3socket构造,而其也是一个类对象,它的实现为:
class L3PacketSocket(SuperSocket):
    def __init__(self, type = ETH_P_ALL, filter=None, promisc=None, iface=None): #构造方法
        self.type = type
    #下面将构造出一个packet协议族的socket,并且还是raw的,用于接收数据,使用packet实为自己解析之方便
        self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
        self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
        ...#下面将构造出一个packet协议族的socket,并且还是raw的,用于发送自己构造的数据
        self.outs = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
        ...
        if iface is None: #得到所有机器上的网卡
            self.iff = get_if_list()
        ...
        ...#设置网卡为promisc模式用于抓取任意数据包
    def close(self):
        ...
    def recv(self, x): #接收方法
        pkt, sa_ll = self.ins.recvfrom(x) #首先通过操作系统的packet套结字的接收方法接收数据
        ...
        pkt = cls(pkt)
    ...
        return pkt
   
    def send(self, x):
        if hasattr(x,"dst"):
        #根据构造ip包时的dst属性值来决定往哪个网卡口发送这个自己构造好的数据包,这是通过路由得到的,在scapy初始化的时候,路由即已经被读入了内存中,然后我们这里只是根据dst来查找该dst所对应的路由,最终将之解析(python的struct unpack操作)成iff-网卡,gw-网关等数据。
            iff,a,gw = conf.route.route(x.dst)
        ...
        sdto = (iff, self.type) #得到类似c语言的sockaddr_ll结构体
        self.outs.bind(sdto) #将packet套结字bind到该网卡上(c语言中的sockaddr_ll结构体则是bind的参数类型)
        sn = self.outs.getsockname()
        ...
        self.outs.sendto(str(x), sdto) #最终通过操作系统的packet套结字发送出去
上面就是scapy中的核心套结字class,它封装了套结字的几乎所有行为,那么可想而知套结字操作的数据则被封装成了另一个核心的class,这就是Packet,然而该Packet作为抽象父类出现,任何特定协议的数据包都是一个它的子类,对于最简单的以太帧来讲,它是:
class Ether(Packet):
    name = "Ethernet"
    fields_desc = [ DestMACField("dst"),
                    SourceMACField("src"),
                    XShortField("type", 0x0000) ]
    def hashret(self):
        return struct.pack("H",self.type)+self.payload.hashret()
    def answers(self, other):
        if isinstance(other,Ether):
            if self.type == other.type:
                return self.payload.answers(other.payload)
        return 0
    def mysummary(self):
        return "%s > %s (%04x)" % (self.src, self.dst, self.type)
sr函数的作用就是用一个SOCKET将一个PACKET发送出去,之所以使用大写就是因为这都是scapy中的类对象,而不是内建的数据类型或者系统的数据类型,既然SOCKET和PACKET都是类,那么也就是因为它们是类,所以很容易通过继承扩展出很多不同种类的SOCKET和PACKET,比如链路层socket,网络层socket这些不同层次的SOCKET(它们的send/recv方法实现当然不同),以及以太帧,ip数据报,tcp数据段这些不同层次的PACKET,总之,SOCKET定义了数据的发送接收流程和发送接收方式,而PACKET定义了数据包的格式,可以想象,将SOCKET和PACKET都定义成顶层的抽象超类,然后在不同的协议层定义不同的SOCKET和PACKET子类即可,注意每一个协议层的SOCKET子类和PACKET子类并不是一一对应的,对于每个层次来水SOCKET子类应该很少,而PACKET却很多,简单的说,该层支持那些具体协议,就有几个PACKET子类。事实上,scapy就是这么设计的。
     上述的L3PacketSocket表示一个网络层的socket,它的定义如下,以SuperSocket作为父类:
class L3PacketSocket(SuperSocket)
而上述的Ether表示一个链路层的packet,它的定义如下,以Packet作为父类:
class Ether(Packet)
有了上述的Socket和Packet,整个发送接收流程就可以运作了,接下来最后需要几个全局的方法用于发送不同层次的数据包,这就是sr,srp等方法了,在scapy的终端,敲入下列命令可以看到帮助信息:
>>> help('sr') #类似的还有srp(发送二层数据帧),sr1等等
Help on function sr in module __main__:

sr(x, filter=None, iface=None, nofilter=0, *args, **kargs)
    Send and receive packets at layer 3
    nofilter: put 1 to avoid use of bpf filters
    retry:    if positive, how many times to resend unanswered packets
              if negative, how many times to retry when no more packets are answered
    timeout:  how much time to wait after the last packet has been sent
    verbose:  set verbosity level
    multi:    whether to accept multiple answers for the same stimulus
    filter:   provide a BPF filter
    iface:    listen answers only on the given interface
整个流程是:
1.scapy启动,全局读取路由信息,供以后发送三层以及三层以上数据包时确定网卡出口时使用,要知道,建立一个af_packet类型的socket需要bind一个sockaddr_ll结构体,而此结构体需要指定一个确定的网卡信息,比如eth0,eth1等:
struct sockaddr_ll sl;
int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
sl.sll_family = PF_PACKET;
struct ifreq req;
strcpy(req.ifr_name, "eth0");
ioctl(fd, SIOCGIFINDEX, &req);
sl.sll_ifindex = req.ifr_ifindex;
sl.sll_protocol = htons(ETH_P_ALL);
bind(fd, (struct sockaddr *)&sl, sizeof(sl));
bind网卡的时候需要的eth?信息就是通过scapy初始化时得到的全局路由得到的,到时候,会根据IP(...,dst="xxx",...)中的dst信息来在全局路由中得到ethN信息,从而可以将socket进行bind;
2.用户输入一个诸如a=IP(src="192.168.40.246",dst="192.168.40.34")/TCP(sport=1111,dport=2222)之类的创建一个对象,该对象拥有dst属性:192.168.40.34;
3.用户调用sr(a)发送数据包,此时初始化一个L3PacketSocket对象,并且调用L3PacketSocket对象的send函数,后者将原生socket bind到一个ethN上(由路由确定),然后调用原生socket将数据发出;
4.读取返回。
5.以上的1-4仅仅是三层数据的发送过程,sr函数只能发送ip层以及ip携带的数据


原文链接:http://blog.csdn.net/dog250/article/details/6153626
加载中
返回顶部
顶部