分享
 
 
 

Linux网络接口源码导读

王朝system·作者佚名  2006-01-14
窄屏简体版  字體: |||超大  

BSD是UNIX系统中通用的网络接口,它不仅支持各种不同的网络类型,而且也是一种内部进程之间的通信机制。两个通信进程都用一个套接口来描述通信链路的两端。套接口可以认为是一种特殊的管道,但和管道不同的是,套接口对于可以容纳的数据的大小没有限制。

Linux支持多种类型的套接口,也叫做套接口寻址族,这是因为每种类型的套接口都有自己的寻址方法。Linux支持以下的套接口类型:

UNIX UNIX域套接口

INET Internet地址族TCP/IP协议支持通信。

AX25 Amateur radio X25

IPX Novell IPX

APPLETALK Appletalk DDP

X25 X25

这些类型的套接口代表各种不同的连接服务。 Linux的BSD 套接口支持下面的几种套接口类型:

1. 流式(stream)

提供了可靠的双向顺序数据流连接。可以保证数据传输中的完整性、正确性和单一性。INET寻址族中TCP协议支持这种类型。

2. 数据报(Datagram)

这种类型的套接口也可以像流式套接口一样提供双向的数据传输,但它们不能保证传输的数据一定能够到达目的节点。即使数据能够到达,也无法保证数据以正确的顺序到达以及数据的单一性、正确性。UDP协议支持这种类型的套接口。

3. 原始(Raw)

这种类型的套接口允许进程直接存取下层的协议。

4. 可靠递送消息(Reliable Delivered Messages)

这种套接口和数据报套接口一样,只能保证数据的到达。

5. 顺序数据包(Sequenced Packets)

这种套接口和流式套接口相同,除了数据包的大小是固定的。

6. 数据包(Packet)

这不是标准的BSD 套接口类型,而是Linux 中的一种扩展。它允许进程直接存取设备层的数据包。

INET套接口层包括支持TCP/IP协议的Internet地址族。正如上面提到的,这些协议是分层的,每一个协议都使用另一个协议的服务。Linux系统中的TCP/IP代码和数据结构也反映了这种分层的思想。它和BSD 套接口层的接口是通过一系列与Internet地址族有关的套接口操作来实现的,而这些套接口操作是在网络初始化的过程中由INET 套接口层在BSD 套接口层中注册的。这些操作和其他地址族的操作一样保存在pops向量中。

BSD 套接口层通过INET的proto_ops数据结构来调用与INET 层有关的套接口子程序来实现有关INET层的服务。例如,当BSD 套接口创建一个发送给INET地址族的请求时将会使用INET的套接口创建功能。BSD 套接口层将会把套接口数据结构传递给每一个操作中的INET层。INET 套接口层在它自己的数据结构sock中而不是在BSD 套接口的数据结构中插入有关TCP/IP的信息,但sock数据结构是和B S D套接口的数据结构有关的。它使用BSD 套接口中的数据指针来连接sock数据结构和BSD 套接口数据结构,这意味着以后的INET 套接口调用可以十分方便地得到sock数据结构。数据结构sock中的协议操作指针也会在创建时设置好,并且此指针是和所需要的协议有关的。如果需要的是TCP协议,那么数据结构sock中的协议操

下面是Linux系统的TCP包,从netif_rx开始 [net/core/dev.c]

中断管理管理: "netif_rx"

|netif_rx

|__skb_queue_tail

|qlen++

|* simple pointer insertion *

|cpu_raise_softirq

|softirq_active(cpu) |= (1 << NET_RX_SOFTIRQ) // set bit NET_RX_SOFTIRQ in the BH vector

·__skb_queue_tail [include/linux/skbuff.h]

·cpu_raise_softirq [kernel/softirq.c]

中断的后半部分: "net_rx_action"

IRQ的基本处理以后,还需要另外的“底半”处理,(参考软中断)这里的是NET_RX_SOFTIRQ完成的。

net_rx_action [net/core/dev.c]

net_dev_init [net/core/dev.c]

|net_rx_action

|skb = __skb_dequeue (the exact opposite of __skb_queue_tail)

|for (ptype = first_protocol; ptype < max_protocol; ptype++) // Determine

|if (skb->protocol == ptype) // what is the network protocol

|ptype->func -> ip_rcv // according to ''struct ip_packet_type [net/ipv4/ip_output.c]''

**** NOW WE KNOW THAT PACKET IS IP ****

|ip_rcv

|NF_HOOK (ip_rcv_finish)

|ip_route_input // search from routing table to determine function to call

|skb->dst->input -> ip_local_deliver // according to previous routing table check, destination is local machine

|ip_defrag // reassembles IP fragments

|NF_HOOK (ip_local_deliver_finish)

|ipprot->handler -> tcp_v4_rcv // according to ''tcp_protocol [include/net/protocol.c]''

**** NOW WE KNOW THAT PACKET IS TCP ****

|tcp_v4_rcv

|sk = __tcp_v4_lookup

|tcp_v4_do_rcv

|switch(sk->state)

*** Packet can be sent to the task which uses relative socket ***

|case TCP_ESTABLISHED:

|tcp_rcv_established

|__skb_queue_tail // enqueue packet to socket

|sk->data_ready -> sock_def_readable

|wake_up_interruptible

*** Packet has still to be handshaked by 3-way TCP handshake ***

|case TCP_LISTEN:

|tcp_v4_hnd_req

|tcp_v4_search_req

|tcp_check_req

|syn_recv_sock -> tcp_v4_syn_recv_sock

|__tcp_v4_lookup_established

|tcp_rcv_state_process

*** 3-Way TCP Handshake ***

|switch(sk->state)

|case TCP_LISTEN: // We received SYN

|conn_request -> tcp_v4_conn_request

|tcp_v4_send_synack // Send SYN + ACK

|tcp_v4_synq_add // set SYN state

|case TCP_SYN_SENT: // we received SYN + ACK

|tcp_rcv_synsent_state_process

tcp_set_state(TCP_ESTABLISHED)

|tcp_send_ack

|tcp_transmit_skb

|queue_xmit -> ip_queue_xmit

|ip_queue_xmit2

|skb->dst->output

|case TCP_SYN_RECV: // We received ACK

|if (ACK)

|tcp_set_state(TCP_ESTABLISHED)

·net_rx_action [net/core/dev.c]

·__skb_dequeue [include/linux/skbuff.h]

·ip_rcv [net/ipv4/ip_input.c]

·NF_HOOK -> nf_hook_slow [net/core/netfilter.c]

·ip_rcv_finish [net/ipv4/ip_input.c]

·ip_route_input [net/ipv4/route.c]

·ip_local_deliver [net/ipv4/ip_input.c]

·ip_defrag [net/ipv4/ip_fragment.c]

·ip_local_deliver_finish [net/ipv4/ip_input.c]

·tcp_v4_rcv [net/ipv4/tcp_ipv4.c]

·__tcp_v4_lookup

·tcp_v4_do_rcv

·tcp_rcv_established [net/ipv4/tcp_input.c]

·__skb_queue_tail [include/linux/skbuff.h]

·sock_def_readable [net/core/sock.c]

·wake_up_interruptible [include/linux/sched.h]

·tcp_v4_hnd_req [net/ipv4/tcp_ipv4.c]

·tcp_v4_search_req

·tcp_check_req

·tcp_v4_syn_recv_sock

·__tcp_v4_lookup_established

·tcp_rcv_state_process [net/ipv4/tcp_input.c]

·tcp_v4_conn_request [net/ipv4/tcp_ipv4.c]

·tcp_v4_send_synack

·tcp_v4_synq_add

·tcp_rcv_synsent_state_process [net/ipv4/tcp_input.c]

·tcp_set_state [include/net/tcp.h]

·tcp_send_ack [net/ipv4/tcp_output.c]

[目录]

网络接口源码导读

[目录]

网络接口源码的结构(一)

Linux最新稳定内核2.4.x的网络接口源码的结构(一)

李元佳 一.前言

Linux的源码里,网络接口的实现部份是非常值得一读的,通过读源码,不仅对网络协议会有更深的了解,也有助于在网络编程的时候,对应用函数有更精确的了解和把握。本文把重点放在网络接口程序的总体结构上,希望能作为读源码时一些指导性的文字。

本文以Linux2.4.16内核作为讲解的对象,内核源码可以在http://www.kernel.org上下载。我读源码时参考的是http://lxr.linux.no/这个交差参考的网站,我个人认为是一个很好的工具,如果有条件最好上这个网站。国内http://211.71.69.201/joyfire/有类似

二.网络接口程序的结构

Linux的网络接口分为四部份:网络设备接口部份,网络接口核心部份,网络协议族部份,以及网络接口socket层。

网络设备接口部份主要负责从物理介质接收和发送数据。实现的文件在linu/driver/net目录下面。

网络接口核心部份是整个网络接口的关键部位,它为网络协议提供统一的发送接口,屏蔽各种各样的物理介质,同时有负责把来自下层的包向合适的协议配送。它是网络接口的中枢部份。它的主要实现文件在linux/net/core目录下,其中linux/net/core/dev.c为主要管理文件。

网络协议族部份是各种具体协议实现的部份。Linux支持TCP/IP,IPX,X.25,AppleTalk等的协议,各种具体协议实现的源码在linux/net/目录下相应的名称。在这里主要讨论TCP/IP(IPv4)协议,实现的源码在linux/net/ipv4,其中linux/net/ipv4/af_inet.c是主要的管理文件。

网络接口Socket层为用户提供的网络服务的编程接口。主要的源码在linux/net/socket.c

三.网络设备接口部份

物理层上有许多不同类型的网络接口设备, 在文件include/linux/if_arp.h的28行里定义了ARP能处理的各种的物理设备的标志符。网络设备接口要负责具体物理介质的控制,从物理介质接收以及发送数据,并对物理介质进行诸如最大数据包之类的各种设置。这里我们以比较简单的3Com3c501 太网卡的驱动程序为例,大概讲一下这层的工作原理。源码在Linux/drivers/net/3c501.c。

我们从直觉上来考虑,一个网卡当然最主要的是完成数据的接收和发送,在这里我们来看看接收和发送的过程是怎么样的。

发送相对来说比较简单,在Linux/drivers/net/3c501.c的行475 开始的el_start_xmit()这个函数就是实际向3Com3c501以太网卡发送数据的函数,具体的发送工作不外乎是对一些寄存器的读写,源码的注释很清楚,大家可以看看。

接收的工作相对来说比较复杂。通常来说,一个新的包到了,或者一个包发送完成了,都会产生一个中断。Linux/drivers/net/3c501.c的572开始el_interrupt()的函数里面,前半部份处理的是包发送完以后的汇报,后半部份处理的是一个新的包来的,就是说接收到了新的数据。el_interrupt()函数并没有对新的包进行太多的处理,就交给了接收处理函数el_receive()。el_receive()首先检查接收的包是否正确,如果是一个“好”包就会为包分配一个缓冲结构(dev_alloc_skb()),这样驱动程序对包的接收工作就完成了,通过调用上层的函数netif_rx()(net/core/dev.c1214行) ,把包交给上层。

现在驱动程序有了发送和接收数据的功能了,驱动程序怎么样和上层建立联系呢?就是说接收到包以后怎么送给上层,以及上层怎么能调用驱动程序的发送函数呢?

由下往上的关系,是通过驱动程序调用上层的netif_rx()(net/core/dev.c 1214行)函数实现的,驱动程序通过这个函数把接到的数据交给上层,请注意所有的网卡驱动程序都需要调用这个函数的,这是网络接口核心层和网络接口设备联系的桥梁。

由上往下的关系就复杂点。网络接口核心层需要知道有多少网络设备可以用,每个设备的函数的入口地址等都要知道。网络接口核心层会大声喊,“嘿,有多少设备可以帮我发送数据包?能发送的请给我排成一队!”。这一队就由dev_base开始,指针structnet_device *dev_base (Linux/include/linux/netdevice.h 436行)就是保存了网络接口核心层所知道的所有设备。对于网络接口核心层来说,所有的设备都是一个net_device结构,它在include/linux/netdevice.h,line 233里被定义,这是从网络接口核心层的角度看到的一个抽象的设备,我们来看看网络接口核心层的角度看到的网络设备具有的功能:

struct net_device {

………

open()

stop()

hard_start_xmit()

hard_header()

rebuild_header()

set_mac_address()

do_ioctl()

set_config()

hard_header_cache()

header_cache_update()

change_mtu()

tx_timeout()

hard_header_parse()

neigh_setup()

accept_fastpath()

………

}

如果网络接口核心层需要由下层发送数据的时候,在dev_base找到设备以后,就直接调dev->hard_start_xmit()的这个函数来让下层发数据包。

驱动程序要让网络接口核心层知道自己的存在,当然要加入dev_base所指向的指针链,然后把自己的函数以及各种参数和net_device里的相应的域对应起来。加入dev_base所指向的指针链是通过函数register_netdev(&dev_3c50)(linux/drivers/net/net_init.c, line 532)

建立的。而把自己的函数以和net_device里的相应的域及各种参数关系的建立是在el1_probe1()(Linux/drivers/net/3c501.c)里进行的:

el1_probe1(){

………

dev->open = &el_open;

dev->hard_start_xmit = &el_start_xmit;

dev->tx_timeout = &el_timeout;

dev->watchdog_timeo = HZ;

dev->stop = &el1_close;

dev->get_stats = &el1_get_stats;

dev->set_multicast_list = &set_multicast_list;

………

ether_setup(dev);

………

}

进一步的对应工作在ether_setup(dev) (drivers/net/net_init.c, line 405 )里进行。我们注意到dev->hard_start_xmit =&el_start_xmit,这样发送函数的关系就建立了,上层只知道调用dev->hard_start_xmit这个来发送数据,上面的语句就把驱动程序实际的发送函数告诉了上层。

四.网络接口核心部分

刚才谈论了驱动程序怎么和网络接口核心层衔接的。网络接口核心层知道驱动程序以及驱动程序的函数的入口是通过*dev_base指向的设备链的,而下层是通过调用这一层的函数netif_rx()(net/core/dev.c 1214行) 把数据传递个这一层的。

网络接口核心层的上层是具体的网络协议,下层是驱动程序,我们以及解决了下层的关系,但和上层的关系没有解决。先来讨论一下网络接口核心层和网络协议族部份的关系,这种关系不外乎也是接收和发送的关系。

网络协议,例如IP,ARP等的协议要发送数据包的时候会把数据包传递给这层,那么这种传递是通过什么函数来发生的呢?网络接口核心层通过dev_queue_xmit()(net/core/dev.c,line975)这个函数向上层提供统一的发送接口,也就是说无论是IP,还是ARP协议,通过这个函数把要发送的数据传递给这一层,想发送数据的时候就调用这个函数就可以了。dev_queue_xmit()做的工作最后会落实到dev->hard_start_xmit(),而dev->h

ard_start_xmit()会调用实际的驱动程序来完成发送的任务。例如上面的例子中,调用dev->hard_start_xmit()实际就是调用了el_start_xmit()。

现在讨论接收的情况。网络接口核心层通过的函数netif_rx()(net/core/dev.c 1214行)接收了上层发送来的数据,这时候当然要把数据包往上层派送。所有的协议族的下层协议都需要接收数据,TCP/IP的IP协议和ARP协议,SPX/IPX的IPX协议,AppleTalk的DDP和AARP协议等都需要直接从网络接口核心层接收数据,网络接口核心层接收数据是如何把包发给这些协议的呢?这时的情形和于下层的关系很相似,网络接口核心层的下面

可能有许多的网卡的驱动程序,为了知道怎么向这些驱动程序发数据,前面以及讲过时,是通过*dev_base这个指针指向的链解决的,现在解决和上层的关系是通过static struct packet_ptype_base[16]( net/core/dev.c line 164)这个数组解决的。这个数组包含了需要接收数据包的协议,以及它们的接收函数的入口。

从上面可以看到,IP协议接收数据是通过ip_rcv()函数的,而ARP协议是通过arp_rcv()的,网络接口核心层只要通过这个数组就可以把数据交给上层函数了。

如果有协议想把自己添加到这个数组,是通过dev_add_pack()(net/core/dev.c, line233)函数,从数组删除是通过dev_remove_pack()函数的。Ip层的注册是在初始化函数进行的

void __init ip_init(void) (net/ipv4/ip_output.c, line 1003)

{

………

dev_add_pack(&ip_packet_type);

………

}

重新到回我们关于接收的讨论,网络接口核心层通过的函数netif_rx()(net/core/dev.c 1214行)接收了上层发送来的数据,看看这个函数做了些什么。

由于现在还是在中断的服务里面,所有并不能够处理太多的东西,剩下的东西就通过cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ)

交给软中断处理, 从open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL)可以知道NET_RX_SOFTIRQ软中断的处理函数是net_rx_action()(net/core/dev.c, line 1419),net_rx_action()根据数据包的协议类型在数组ptype_base[16]里找到相应的协议,并从中知道了接收的处理函数,然后把数据包交给处理函数,这样就交给了上层处理,实际调用处理函数是通过net_rx_action()里的pt_prev->func()这一句。例如如果数据

包是IP协议的话,ptype_base[ETH_P_IP]->func()(ip_rcv()),这样就把数据包交给了IP协议。

五.网络协议部分

协议层是真正实现是在这一层。在linux/include/linux/socket.h里面,Linux的BSD Socket定义了多至32支持的协议族,其中PF_INET就是我们最熟悉的TCP/IP协议族(IPv4, 以下没有特别声明都指IPv4)。以这个协议族为例,看看这层是怎么工作的。实现TCP/IP协议族的主要文件在inux/net/ipv4/目录下面,Linux/net/ipv4/af_inet.c为主要的管理文件。

在Linux2.4.16里面,实现了TCP/IP协议族里面的的IGMP,TCP,UDP,ICMP,ARP,IP。我们先讨论一下这些协议之间的关系。IP和ARP协议是需要直接和网络设备接口打交道的协议,也就是需要从网络核心模块(core) 接收数据和发送数据的。而其它协议TCP,UDP,IGMP,ICMP是需要直接利用IP协议的,需要从IP协议接收数据,以及利用IP协议发送数据,同时还要向上层Socket层提供直接的调用接口。可以看到IP层是一个核心的协议,向

下需要和下层打交道,又要向上层提供所以的传输和接收的服务。

先来看看IP协议层。网络核心模块(core) 如果接收到IP层的数据,通过ptype_base[ETH_P_IP] 数组的IP层的项指向的IP协议的ip_packet_type->ip_rcv()函数把数据包传递给IP层,也就是说IP层通过这个函数ip_rcv()(linux/net/ipv4/ip_input.c)接收数据的。ip_rcv()这个函数只对IP数据保做了一些checksum的检查工作,如果包是正确的就把包交给了下一个处理函数ip_rcv_finish()(注意调用是通过NF_HOOK这个宏实现的)。现在,ip_rcv_finish()这个函数真正要完成一些IP层的工作了。IP层要做的主要工作就是路由,要决定把数据包往那里送。路由的工作是通过函数ip_route_input()(/linux/net/ipv4/route.c,line 1622)实现的。对于进来的包可能的路由有这些:

属于本地的数据(即是需要传递给TCP,UDP,IGMP这些上层协议的) ;

需要要转发的数据包(网关或者NAT服务器之类的);

不可能路由的数据包(地址信息有误);

我们现在关心的是如果数据是本地数据的时候怎么处理。ip_route_input()调用ip_route_input_slow()(net/ipv4/route.c, line 1312),在ip_route_input_slow()里面的1559行rth->u.dst.input= ip_local_deliver,这就是判断到IP包是本地的数据包,并把本地数据包处理函数的地址返回。好了,路由工作完成了,返回到ip_rcv_finish()。ip_rcv_finish()最后调用拉skb->dst->input(skb),从上面可以看到,这其实就是调用了ip_local_deliver()函数,而ip_local_deliver(),接着就调用了ip_local_deliver_finish()。现在真正到了往上层传递数据包的时候了。

现在的情形和网络核心模块层(core) 往上层传递数据包的情形非常相似,怎么从多个协议选择合适的协议,并且往这个协议传递数据呢?网络网络核心模块层(core) 通过一个数组ptype_base[16]保存了注册了的所有可以接收数据的协议,同样网络协议层也定义了这样一个数组struct net_protocol*inet_protos[MAX_INET_PROTOS](/linux/net/ipv4/protocol.c#L102),它保存了所有需要从IP协议层接收数据的上层协议(IGMP,TCP,UDP,ICMP)的接收处理函数的地址。我们来看看TCP协议的数据结构是怎么样的:

linux/net/ipv4/protocol.c line67

static struct inet_protocol tcp_protocol = {

handler: tcp_v4_rcv,// 接收数据的函数

err_handler: tcp_v4_err,// 出错处理的函数

next: IPPROTO_PREVIOUS,

protocol: IPPROTO_TCP,

name: "TCP"

};

第一项就是我们最关心的了,IP层可以通过这个函数把数据包往TCP层传的。在linux/net/ipv4/protocol.c的上部,我们可以看到其它协议层的处理函数是igmp_rcv(),udp_rcv(), icmp_rcv()。同样在linux/net/ipv4/protocol.c,往数组inet_protos[MAX_INET_PROTOS] 里面添加协议是通过函数inet_add_protocol()实现的,删除协议是通过 inet_del_protocol()实现的。inet_protos[MAX_INET_PROTOS]初始化的过程在linux/net/ipv4/af_inet.c inet_init()初始化函数里面。

inet_init(){

……

printk(KERN_INFO "IP Protocols: ");

for (p = inet_protocol_base; p != NULL;) {

struct inet_protocol *tmp = (struct inet_protocol *) p->next;

inet_add_protocol(p);// 添加协议

printk("%s%s",p->name,tmp?", ":"n");

p = tmp;

………

}

[目录]

网络接口源码的结构(二)

Linux最新稳定内核2.4.x的网络接口源码的结构(二)

李元佳 如果你在Linux启动的时候有留意启动的信息, 或者在linux下打命令dmesg就可以看到这一段程序输出的信息:

IP Protocols: ICMP,UDP,TCP,IGMP也就是说现在数组inet_protos[]里面有了ICMP,UDP,TCP,IGMP四个协议的inet_protocol数据结构,数据结构包含了它们接收数据的处理函数。

Linux 2.4.16在linux/include/linux/socket.h里定义了32种支持的BSDsocket协议,常见的有TCP/IP,IPX/SPX,X.25等,而每种协议还提供不同的服务,例如TCP/IP协议通过TCP协议支持连接服务,而通过UDP协议支持无连接服务,面对这么多的协议,向用户提供统一的接口是必要的,这种统一是通过socket来进行的。

在BSD socket网络编程的模式下,利用一系列的统一的函数来利用通信的服务。例如一个典型的利用TCP协议通信程序是这样:

sock_descriptor = socket(AF_INET,SOCK_STREAM,0);

connect(sock_descriptor, 地址,) ;

send(sock_descriptor,”hello world”);

recv(sock_descriptor,buffer,1024,0);

第一个函数指定了协议Inet协议,即TCP/IP协议,同时是利用面向连接的服务,这样就对应到TCP协议,以后的操作就是利用socket的标准函数进行的。

从上面我们可以看到两个问题,首先socket层需要根据用户指定的协议族(上面是AF_INET) 从下面32种协议中选择一种协议来完成用户的要求,当协议族确定以后,还要把特定的服务映射到协议族下的具体协议,例如当用户指定的是面向连接的服务时,Inet协议族会映射到TCP协议。

从多个协议中选择用户指定的协议,并把具体的出理交给选中的协议,这和一起网络核心层向上和向下衔接的问题本质上是一样的,所以解决的方法也是一样的,同样还是通过数组。在Linux/net/socket.c定义了这个数组staticstruct net_proto_family*net_families[NPROTO] 。数组的元素已经确定了,net_families[2]是TCP/IP协议,net_families[3]是X.25协议,具体那一项对应什么协议,在include/linux/socket.h有定义。但是每一项的数据结构net_proto_family的ops是空的,也就是具体协议处理函数的地址是不知道的。协议的处理函数和ops建立联系是通过sock_register()(Linux/net/socket.c)这个函数建立的,例如TCP/IP协议的是这样建立关系的:

int __init inet_init(void) (net/ipv4/af_inet.c)

{

(void) sock_register(&inet_family_ops);

}

只要给出AF_INET(在宏里定义是2),就可以找到net_failies[2] 里面的处理函数了。

协议的映射完成了,现在要进行服务的映射了。上层当然不可能知道下层的什么协议能对应特定的服务,所以这种映射自然由协议族自己完成。在TCP/IP协议族里,这种映射是通过struct list_head inetsw[SOCK_MAX]( net/ipv4/af_inet.c)

这个数组进行映射的,在谈论这个数组之前我们来看另外一个数组inetsw_array[](net/ipv4/af_inet.c)

static struct inet_protosw inetsw_array[] =

{

{

type: SOCK_STREAM,

protocol: IPPROTO_TCP,

prot: &tcp_prot,

ops: &inet_stream_ops,

capability: -1,

no_check: 0,

flags: INET_PROTOSW_PERMANENT,

},

{

type: SOCK_DGRAM,

protocol: IPPROTO_UDP,

prot: &udp_prot,

ops: &inet_dgram_ops,

capability: -1,

no_check: UDP_CSUM_DEFAULT,

flags: INET_PROTOSW_PERMANENT,

},

{

type: SOCK_RAW,

protocol: IPPROTO_IP, /* wild card */

prot: &raw_prot,

ops: &inet_dgram_ops,

capability: CAP_NET_RAW,

no_check: UDP_CSUM_DEFAULT,

flags: INET_PROTOSW_REUSE,

}

};

我们看到,SOCK_STREAM映射到了TCP协议,SOCK_DGRAM映射到了UDP协议,SOCK_RAW映射到了IP协议。现在只要把inetsw_array里的三项添加到数组inetsw[SOCK_MAX]就可以了,添加是通过函数inet_register_protosw()实现的。在inet_init()(net/ipv4/af_inet.c) 里完成了这些工作。

还有一个需要映射的就是socket其它诸如accept,send(), connect(),release(),bind()等的操作函数是怎么映射的呢?我们来看一下上面的数组的TCP的项

{

type: SOCK_STREAM,

protocol: IPPROTO_TCP,

prot: &tcp_prot,

ops: &inet_stream_ops,

capability: -1,

no_check: 0,

flags: INET_PROTOSW_PERMANENT,

},

我们看到这种映射是通过ops,和prot来映射的,我们再来看看 tcp_prot这一项:

struct proto tcp_prot = {

name: "TCP",

close: tcp_close,

connect: tcp_v4_connect,

disconnect: tcp_disconnect,

accept: tcp_accept,

ioctl: tcp_ioctl,

init: tcp_v4_init_sock,

destroy: tcp_v4_destroy_sock,

shutdown: tcp_shutdown,

setsockopt: tcp_setsockopt,

getsockopt: tcp_getsockopt,

sendmsg: tcp_sendmsg,

recvmsg: tcp_recvmsg,

backlog_rcv: tcp_v4_do_rcv,

hash: tcp_v4_hash,

unhash: tcp_unhash,

get_port: tcp_v4_get_port,

};

所以的映射都已经完成了,用户调用connect()函数,其实就是调用了tcp_v4_connect()函数,按照这幅图,读起源码来就简单了很多了。

六 Socket层

上一节把socket层大多数要讨论的东西都谈论了,现在只讲讲socket 层和用户的衔接。

系统调用socket(),bind(),connect(),accept,send(),release()等是在Linux/net/socket.c里面的实现的,系统调用实现的函数是相应的函数名加上sys_的前缀。

现在看看当用户调用socket()这个函数,到底下面发生了什么。

Socket(AF_INET,SOCK_STREAM,0)调用了sys_socket(),sys_socket()接着调用socket_creat(),socket_creat()就要根据用户提供的协议族参数在net_families[]里寻找合适的协议族,如果协议族没有被安装就要请求安装该协议族的模块,然后就调用该协议族的create()函数的处理句柄。根据参数AF_INET,inet_creat()就被调用了,在inet_creat()根据服务类型在inetsw[SOCK_MAX]选择合适的协议,并把协议的操作集赋给socket就是了,根据SOCK_STREAM,TCP协议被选中,

inet_creat(){

answer=inetsw [用户要求服务服务] ;

sock->ops = answer->ops;

sk->prot = answer->prot

}

到此为止,上下都打通了,该是大家读源码的时候了。

[目录]

防火墙

[目录]

netfilter

标题: A new place to LKM:netfilter

作者: yawl <yawl@nsfocus.com>

时间: 2000-10 目录:

-.前言

二.分析

三.例子代码

四.附录:与2.2在应用方面的区别简介

五.后记

-.前言

在linux2.2内核中的防火墙ipchains已经被用户广泛认可,它提供了完整的防火墙功能(包过滤,地址伪装,透明代理),又避免了商业防火墙那高的惊人的价格。如果你用的是某款国产防火墙,那么十有八九你实际在受到ipchains(有些甚至是2.0系列中ipfwadm)的保护:-).在未来的2.4内核中,被称为netfilter(http://netfilter.kernelnotes.org/)的防火墙以更好的结构重新构造,并实现了许多新功能,如完整的动态NAT(2.2内核实际是多对一的"地址伪装"),基于MAC及用户的过滤,真正的基于状态的过滤(不再是简单的查看tcp的标志位等),包速率限制等。

在原有的网络部分的LKM中,如果对网络部分进行处理,一般是先生成struct packet_type结构,在用dev_add_pack将其插入网络层(注意此时的packet_type实际相当于一个的三层的协议,如ip_packet_type,ipx_8023_packet_type等),具体的例子可参见phrack 55期<Building into the linux network layer>和本月小四写的月刊文章<利用LLKM处理网络通信----对抗IDS、Firewall>。

而netfilter本身在IP层内提供了另外的5个插入点(其文档中称为HOOK):NF_IP_PRE_ROUTING,NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT,NF_IP_POST_ROUTING,分别对应IP层的五个不同位置,这样理论上在写lkm时便可以选择更适合的切入点,再辅以netfilter内置的新功能(如connect tracking),应该会帮助写出功能更强的lkm。

本来准备写出一个完整的例子(限制IP连接数),但计划总赶不上变化:-(,只好先贴出个简单的例子来,权且自我安慰成抛砖引玉了。

本文的参考配置是linux2.4.0-test4和iptable-1.1.1,好,开始抛砖,闪人喽!

二.分析

通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上上登记了一些处理函数进行处理(如包过滤,NAT等,甚至可以是用户自定义的功能)。

IP层的五个HOOK点的位置如下图所示(copy from <packet filter howto>) :

--->[1]--->[ROUTE]--->[3]--->[5]--->

| ^

| |

| [ROUTE]

v |

[2] [4]

| ^

| |

v |

[local process]

[1]:NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),源地址转换在此点

进行;

[2]:NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;

[3]:NF_IP_FORWARD:要转发的包通过此检测点,FORWORD包过滤在此点进行;

[4]:NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行;

[5]:NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的目的地址转换功能(包括地址伪

装)在此点进行。

在IP层代码中,有一些带有NF_HOOK宏的语句,如IP的转发函数中有:

<-ipforward.c ip_forward()->

NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,

ip_forward_finish);

其中NF_HOOK宏的定义提炼如下:

<-/include/linux/netfilter.h->

#ifdef CONFIG_NETFILTER

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

#else /* !CONFIG_NETFILTER */

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)

#endif /*CONFIG_NETFILTER*/

如果在编译内核时没有配置netfilter时,就相当于调用最后一个参数,此例中即执行ip_forward_finish函数;否则进入HOOK点,执行通过nf_register_hook()登记的功能(这句话表达的可能比较含糊,实际是进入

nf_hook_slow()函数,再由它执行登记的函数)。

NF_HOOK宏的参数分别为:

1.pf:协议族名,netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字。

2.hook:HOOK点的名字,对于IP层,就是取上面的五个值;

3.skb:不用多解释了吧;

4.indev:进来的设备,以struct net_device结构表示;

5.outdev:出去的设备,以struct net_device结构表示;

(后面可以看到,以上五个参数将传到用nf_register_hook登记的处理函数中。)

6.okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。

这些点是已经在内核中定义好的,除非你是这部分内核代码的维护者,否则无权增加或修改,而在此检测点进行的处理,则可由用户指定。像packet filter,NAT,connection track这些功能,也是以这种方式提供的。正如netfilter的当初的设计目标--提供一个完善灵活的框架,为扩展功能提供方便。

如果我们想加入自己的代码,便要用nf_register_hook函数,其函数原型为:

int nf_register_hook(struct nf_hook_ops *reg)

我们考察一下struct nf_hook_ops结构:

struct nf_hook_ops

{

struct list_head list;

/* User fills in from here down. */

nf_hookfn *hook;

int pf;

int hooknum;

/* Hooks are ordered in ascending priority. */

int priority;

};

我们的工作便是生成一个struct nf_hook_ops结构的实例,并用nf_register_hook将其HOOK上。其中list项我们总要初始化为{NULL,NULL};由于一般在IP层工作,pf总是PF_INET;hooknum就是我们选择的HOOK点;一个HOOK点可能挂多个处理函数,谁先谁后,便要看优先级,即priority的指定了。netfilter_ipv4.h中用一个枚举类型指定了内置的处理函数的优先级:

enum nf_ip_hook_priorities {

NF_IP_PRI_FIRST = INT_MIN,

NF_IP_PRI_CONNTRACK = -200,

NF_IP_PRI_MANGLE = -150,

NF_IP_PRI_NAT_DST = -100,

NF_IP_PRI_FILTER = 0,

NF_IP_PRI_NAT_SRC = 100,

NF_IP_PRI_LAST = INT_MAX,

};

hook是提供的处理函数,也就是我们的主要工作,其原型为:

unsigned int nf_hookfn(unsigned int hooknum,

struct sk_buff **skb,

const struct net_device *in,

const struct net_device *out,

int (*okfn)(struct sk_buff *));

它的五个参数将由NFHOOK宏传进去。

了解了这些,基本上便可以可以写一个lkm出来了。

三.例子代码

这段代码是一个例子,其功能实现了一个IDS,检测几个简单攻击(land,winnuke)和特殊扫描(nmap),当然,不会有人真把

它当严肃的IDS使用吧:-)。可以利用类似结构干点别的。。。

<-example.c begin->

/*

* netfilter module example: it`s a kernel IDS(be quie,donot laugh, my friend)

* yawl@nsfocus.com

* Compile:gcc -O -c -Wall sample.c ,under linux2.4 kernel,netfilter is needed.

*/

#define __KERNEL__

#define MODULE

#include <linux/module.h>

#include <linux/skbuff.h>

#include <linux/netdevice.h>

#include <linux/config.h>

#include <linux/ip.h>

#include <linux/tcp.h>

#include <linux/udp.h>

#include <linux/netfilter_ipv4.h>

#define ALERT(fmt,args...) printk("nsfocus: " fmt, ##args)

/*message will be print to screen(too many~),and logged to /var/log/message*/

static unsigned int sample(unsigned int hooknum,struct sk_buff **skb,

const struct net_device *in,

const struct net_device *out,int (*okfn)(struct sk_buff *))

{

struct iphdr *iph;

struct tcphdr *tcph;

struct udphdr *udph;

__u32 sip;

__u32 dip;

__u16 sport;

__u16 dport;

iph=(*skb)->nh.iph;

sip=iph->saddr;

dip=iph->daddr;

/*play ip packet here

(note:checksum has been checked,if connection track is enabled,defrag have been done )*/

if(iph->ihl!=5){

ALERT("IP packet with packet from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if(iph->protocol==6){

tcph=(struct tcphdr*)((__u32 *)iph+iph->ihl);

sport=tcph->source;

dport=tcph->dest;

/*play tcp packet here*/

if((tcph->syn)&&(sport==dport)&&(sip==dip)){

ALERT("maybe land attack\n");

}

if(ntohs(tcph->dest)==139&&tcph->urg){

ALERT("maybe winnuke a from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if(tcph->ece&&tcph->cwr){

ALERT("queso from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if((tcph->fin)&&(tcph->syn)&&(!tcph->rst)&&(!tcph->psh)&&(!tcph->ack)&&(!tcph->urg)){

ALERT("SF_scan from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if((!tcph->fin)&&(!tcph->syn)&&(!tcph->rst)&&(!tcph->psh)&&(!tcph->ack)&&(!tcph->urg)){

ALERT("NULL_scan from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if(tcph->fin&&tcph->syn&&tcph->rst&&tcph->psh&&tcph->ack&&tcph->urg){

ALERT("FULL_Xmas_scan from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

if((tcph->fin)&&(!tcph->syn)&&(!tcph->rst)&&(tcph->psh)&&(!tcph->ack)&&(tcph->urg)){

ALERT("XMAS_Scan(FPU)from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

}

}

else if(iph->protocol==17){

udph=(struct udphdr *)((__u32 *)iph+iph->ihl);

sport=udph->source;

dport=udph->dest;

/*play udp packet here*/

}

else if(iph->protocol==1){

/*play icmp packet here*/

}

else if(iph->protocol==2){

ALERT("igmp packet from %d.%d.%d.%d to %d.%d.%d.%d\n",NIPQUAD(sip),NIPQUAD(dip));

/*play igmp packet here*/

}

else{

ALERT("unknown protocol%d packet from %d.%d.%d.%d to %d.%d.%d.%d\n",iph->protocol,NIPQUAD(sip),NIPQUAD(dip));

}

return NF_ACCEPT;

/*for it is IDS,we just accept all packet,

if you really want to drop this skb,just return NF_DROP*/

}

static struct nf_hook_ops iplimitfilter

={ {NULL,NULL} ,sample,PF_INET,NF_IP_PRE_ROUTING,NF_IP_PRI_FILTER-1};

int init_module(void)

{

return nf_register_hook(&iplimitfilter);

}

void cleanup_module(void)

{

nf_unregister_hook(&iplimitfilter);

}

<-example.c end->

四.附录:与2.2在应用方面的区别简介

本来还想详细介绍一下iptables的用法,但如果说的太详细的话,还不如索性将HOWTO翻译一下,于是干脆了却了这个念头,只顺便简介一下与以前版本的变化(而且是我认为最重要的)。如果ipchains本来便没有在你的脑子中扎根,其实便没有必要看这部分。

netfilter,又可称为iptables.开发初期准备将packet filter和NAT的配置工具完全分开,一个称为iptables,另一个称为ipnatctl,而将整个项目成为netfilter.但后来可能是还是习惯2.2内核中用ipchians一个工具干两件事的用法,又改为全部用iptables配置了。

理论上还可以用2.2系列的ipchains和2.0系列的ipfwadm作配置工具,但只是做兼容或过渡的考虑了。通过源码看到他们也是通过现有的结构HOOK上去的(主要是net/ipv4/netfilter目录下的ip_fw_compat.c,ip_fw_compat_masq.c,

ip_fw_compat_redir.c,ipchains_core.c,ipfwadm_core.c这几个文件)。

一个重要的变化是原有的INPUT,OUTPUT(原来是小写的input,ouput)链(现在应称为表?)的位置变了,原来的input,output的位置相当于现在的NF_IP_PRE_ROUTING,NF_IP_POST_ROUTING 。原有的结构确实不太合理,转发的包要经过三条链,现在INPUT专指去往本机的,OUPUT专指从本机发出的,而FOWARD仍旧是转发的包。

举两个简单的例子:

1.作地址伪装(场景:对外通过拨号连接internet)注意原来的MASQ变成好长的MASQUERATE,而伪装相当于SNAT,因此位置是在POSTROUTING:

iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERATE

2.还有一个限制包速率的功能比较好玩,例如如下规则:

iptables -A FORWARD -p tcp --syn -m limit --limit 1/s -j ACCEPT

简单的说就是在转发时(-A FORWARD:因为是防火墙嘛),如果是tcp协议,且有syn标记(-p tcp --syn),可以限制为每秒一个(-m limit --limit 1/s ),行动项是ACCEPT。最后连起来意义就是每秒只允许转发一个tcp连接请求。

五.后记

netfilter还提供了许多新功能,如可以将包转发到应用层,由应用程序进行处理等,可目前我还没有分析多少,慢慢抽出点时间看吧。唉,尽管以前看过ipchains的代码,但netfilter实在变动太大了,一切都要从头看起:-(

最后,当然要感谢Rusty Russell,netfilter项目的负责人,不仅为我们提供了这个强大好用的工具,还写了大量非常优秀的文档。

参考文献:

[1.] Linux 2.4 Packet Filtering HOWTO

Rusty Russell, mailing list netfilter@lists.samba.org

v1.0.1 Mon May 1 18:09:31 CST 2000

[2.] Linux IPCHAINS-HOWTO

Paul Russell, ipchains@rustcorp.com

v1.0.7, Fri Mar 12 13:46:20 CST 1999

[3.] Linux 2.4 NAT HOWTO

Rusty Russell, mailing list netfilter@lists.samba.org

v1.0.1 Mon May 1 18:38:22 CST 2000

[4.] Linux netfilter Hacking HOWTO

Rusty Russell, mailing list netfilter@lists.samba.org

v1.0.1 Sat Jul 1 18:24:41 EST 2000

[5.] Writing a Module for netfilter

by Paul "Rusty" Russell

Linux Magazine June 2000 http://www.linux-mag.com/2000-06/gear_01.html

[6.] Salvatore Sanfilippo<antirez@invece.org>写的一份netfilter sample,但可惜我找不到出处了,只剩下手头一份打印稿,But anyway,thanks to Salvatore.

[目录]

ip/tables

1 /*

2 * 25-Jul-1998 Major changes to allow for ip chain table

3 *

4 * 3-Jan-2000 Named tables to allow packet selection for different uses.

5 */

6

7 /*

8 * Format of an IP firewall descriptor

9 * 注意这里的说明:IP地址和掩码是按照网络字节存储(大端存储)标志字节和端口号是按照主机字节序存储(依主机硬件结构而定)

10 * src, dst, src_mask, dst_mask are always stored in network byte order.

11 * flags are stored in host byte order (of course).

12 * Port numbers are stored in HOST byte order.

13 */

14

15 #ifndef _IPTABLES_H

16 #define _IPTABLES_H

17

18 #ifdef __KERNEL__

19 #include linux/if.h

20 #include linux/types.h

21 #include linux/in.h

22 #include linux/ip.h

23 #include linux/skbuff.h

24 #endif

25 #include linux/netfilter_ipv4.h

26

27 #define IPT_FUNCTION_MAXNAMELEN 30

28 #define IPT_TABLE_MAXNAMELEN 32

29

这个结构存储与IP头部有关的防火墙规则信息。这里的注释说“这个结构无须填充零字节”,就是说这个结构的大小正好是4的倍数。这里由于IFNAMSIZ等于16,所以整个结构大小确实是4的倍数。

30 /* Yes, Virginia, you have to zero the padding. */

31 struct ipt_ip {

32 /* Source and destination IP addr */

33 struct in_addr src, dst;

34 /* Mask for src and dest IP addr */

35 struct in_addr smsk, dmsk;

36 char iniface[IFNAMSIZ], outiface[IFNAMSIZ];

37 unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];

38

39 /* Protocol, 0 = ANY */

40 u_int16_t proto;

41

42 /* Flags word */

43 u_int8_t flags;

44 /* Inverse flags */

45 u_int8_t invflags;

46 };

47这个结构存储match的信息,这里的匹配主要是指与IP无关的防火墙规则信息。由系统缺省设置的匹配主要有三个“tcp”、“udp”,“icmp”,我在分析ip_tables.c时将详细描述。

48 struct ipt_entry_match

49 {

50 union {

51 struct {

52 u_int16_t match_size;

53

54 /* Used by userspace */

55 char name[IPT_FUNCTION_MAXNAMELEN];

56 } user;

57 struct {

58 u_int16_t match_size;

59

60 /* Used inside the kernel */

61 struct ipt_match *match;

62 } kernel;

63

64 /* Total length */

65 u_int16_t match_size;

66 } u;

67

68 unsigned char data[0];

69 };

70 target结构信息,是决定一个分组命运的信息。也可以理解为action信息,其意义是指当一个分组与rule和match信息匹配后,如何处置该分组。处置方法一般有三种:一,命令常数,比如DROP ACCEPT等等;二 系统预定义的模块处理函数,比如”SNAT DNAT"等等;第三种是用户自己写模块函数。

71 struct ipt_entry_target

72 {

73 union {

74 struct {

75 u_int16_t target_size;

76

77 /* Used by userspace */

78 char name[IPT_FUNCTION_MAXNAMELEN];

79 } user;

80 struct {

81 u_int16_t target_size;

82

83 /* Used inside the kernel */

84 struct ipt_target *target;

85 } kernel;

86

87 /* Total length */

88 u_int16_t target_size;

89 } u;

90

91 unsigned char data[0];

92 };

93这个结构已经很明显给出了target的形式:命令常数、或者模块函数。

94 struct ipt_standard_target

95 {

96 struct ipt_entry_target target;

97 int verdict;

98 };

99

计数器结构,每一个rule都有一个计数器结构用来统计匹配该条规则的分组数目和字节数目。为基于统计的安全工具提供分析基础。

100 struct ipt_counters

101 {

102 u_int64_t pcnt, bcnt; /* Packet and byte counters */

103 };

104

标志字段,各个常数后面的注释已经给出了明确的解释,这里不再赘述。

105 /* Values for "flag" field in struct ipt_ip (general ip structure). */

106 #define IPT_F_FRAG 0x01 /* Set if rule is a fragment rule */

107 #define IPT_F_MASK 0x01 /* All possible flag bits mask. */

108

109 /* Values for "inv" field in struct ipt_ip. */

110 #define IPT_INV_VIA_IN 0x01 /* Invert the sense of IN IFACE. */

111 #define IPT_INV_VIA_OUT 0x02 /* Invert the sense of OUT IFACE */

112 #define IPT_INV_TOS 0x04 /* Invert the sense of TOS. */

113 #define IPT_INV_SRCIP 0x08 /* Invert the sense of SRC IP. */

114 #define IPT_INV_DSTIP 0x10 /* Invert the sense of DST OP. */

115 #define IPT_INV_FRAG 0x20 /* Invert the sense of FRAG. */

116 #define IPT_INV_PROTO 0x40 /* Invert the sense of PROTO. */

掩码标志。用法是当出现超出掩码范围的标志时,确认是非法标志。

117 #define IPT_INV_MASK 0x7F /* All possible flag bits mask. */

118

其实这个结构的构成这里的注释已经说的很清楚,但是从论坛上有人问"关于netfilter的问题“时,可以看出很多人还是不理解。与前面ipchains版本防火墙不同的是iptables的防火墙规则构成发生了变化。ipchains的构成是rule+target,而iptables的构成是ip匹配信息+match+target。同时iptables构成的每一个部分都是可变大小的,由于经常出现”char XXX[0]“就可以看出。但是我个人认为规则的组织有点不好理解,它经常是先分配一段空间,然后将规则一条一条放入。如同文件系统存放变长记录的文件时,总要在记录中放入记录长度,以便以后取出记录,这里iptables正是使用这种方法,在每个规则中都放入长度字段,这样方便提取各个组成部分和计算下一条规则的位置。

119 /* This structure defines each of the firewall rules. Consists of 3

120 parts which are 1) general IP header stuff 2) match specific

121 stuff 3) the target to perform if the rule matches */

122 struct ipt_entry

123 {

124 struct ipt_ip ip;

125

126 /* Mark with fields that we care about. */

127 unsigned int nfcache;

128下面两个字段用来计算target的位置和下一条规则的位置。

129 /* Size of ipt_entry + matches */

130 u_int16_t target_offset;

131 /* Size of ipt_entry + matches + target */

132 u_int16_t next_offset;

133

这个字段的存在,为发现规则中存在”环路“提供手段。

134 /* Back pointer */

135 unsigned int comefrom;

136

137 /* Packet and byte counters. */

138 struct ipt_counters counters;

139

140 /* The matches (if any), then the target. */

141 unsigned char elems[0];

142 };

143

144 /*

145 * New IP firewall options for [gs]etsockopt at the RAW IP level.

146 * Unlike BSD Linux inherits IP options so you don't have to use a raw

147 * socket for this. Instead we check rights in the calls. */

定义提供给set/getsockopt系统调用的命令常数的基常数。

148 #define IPT_BASE_CTL 64 /* base for firewall socket options */

149

150 #define IPT_SO_SET_REPLACE (IPT_BASE_CTL)

151 #define IPT_SO_SET_ADD_COUNTERS (IPT_BASE_CTL + 1)

152 #define IPT_SO_SET_MAX IPT_SO_SET_ADD_COUNTERS

153

154 #define IPT_SO_GET_INFO (IPT_BASE_CTL)

155 #define IPT_SO_GET_ENTRIES (IPT_BASE_CTL + 1)

156 #define IPT_SO_GET_MAX IPT_SO_GET_ENTRIES

157

158 /* CONTINUE verdict for targets */

159 #define IPT_CONTINUE 0xFFFFFFFF

160

161 /* For standard target */

162 #define IPT_RETURN (-NF_MAX_VERDICT - 1)

163

Tcp匹配规则信息。

164 /* TCP matching stuff */

165 struct ipt_tcp

166 {

167 u_int16_t spts[2]; /* Source port range. */

168 u_int16_t dpts[2]; /* Destination port range. */

169 u_int8_t option; /* TCP Option iff non-zero*/

170 u_int8_t flg_mask; /* TCP flags mask byte */

171 u_int8_t flg_cmp; /* TCP flags compare byte */

172 u_int8_t invflags; /* Inverse flags */

173 };

174

tcp的取反标志值。

175 /* Values for "inv" field in struct ipt_tcp. */

176 #define IPT_TCP_INV_SRCPT 0x01 /* Invert the sense of source ports. */

177 #define IPT_TCP_INV_DSTPT 0x02 /* Invert the sense of dest ports. */

178 #define IPT_TCP_INV_FLAGS 0x04 /* Invert the sense of TCP flags. */

179 #define IPT_TCP_INV_OPTION 0x08 /* Invert the sense of option test. */

180 #define IPT_TCP_INV_MASK 0x0F /* All possible flags. */

181

udp匹配规则信息

182 /* UDP matching stuff */

183 struct ipt_udp

184 {

185 u_int16_t spts[2]; /* Source port range. */

186 u_int16_t dpts[2]; /* Destination port range. */

187 u_int8_t invflags; /* Inverse flags */

188 };

189

190 /* Values for "invflags" field in struct ipt_udp. */

191 #define IPT_UDP_INV_SRCPT 0x01 /* Invert the sense of source ports. */

192 #define IPT_UDP_INV_DSTPT 0x02 /* Invert the sense of dest ports. */

193 #define IPT_UDP_INV_MASK 0x03 /* All possible flags. */

194

195 /* ICMP matching stuff */

ICMP匹配规则信息

196 struct ipt_icmp

197 {

198 u_int8_t type; /* type to match */

199 u_int8_t code[2]; /* range of code */

200 u_int8_t invflags; /* Inverse flags */

201 };

202

203 /* Values for "inv" field for struct ipt_icmp. */

204 #define IPT_ICMP_INV 0x01 /* Invert the sense of type/code test */

205

这个结构实质上用户通过getsockopt系统调用获取table信息时所传递参数的类型。

206 /* The argument to IPT_SO_GET_INFO */

207 struct ipt_getinfo

208 {

209 /* Which table: caller fills this in. */

210 char name[IPT_TABLE_MAXNAMELEN];

211

212 /* Kernel fills these in. */

213 /* Which hook entry points are valid: bitmask */

214 unsigned int valid_hooks;

215

216 /* Hook entry points: one per netfilter hook. */

217 unsigned int hook_entry[NF_IP_NUMHOOKS];

218

219 /* Underflow points. */

220 unsigned int underflow[NF_IP_NUMHOOKS];

221

222 /* Number of entries */

223 unsigned int num_entries;

224

225 /* Size of entries. */

226 unsigned int size;

227 };

228

这个结构是用户通过系统调用更换table是所传递的参数类型。

229 /* The argument to IPT_SO_SET_REPLACE. */

230 struct ipt_replace

231 {

232 /* Which table. */

233 char name[IPT_TABLE_MAXNAMELEN];

234

235 /* Which hook entry points are valid: bitmask. You can't

236 change this. */

237 unsigned int valid_hooks;

238

239 /* Number of entries */

240 unsigned int num_entries;

241

242 /* Total size of new entries */

243 unsigned int size;

244

245 /* Hook entry points. */

246 unsigned int hook_entry[NF_IP_NUMHOOKS];

247

248 /* Underflow points. */

249 unsigned int underflow[NF_IP_NUMHOOKS];

250

251 /* Information about old entries: */

252 /* Number of counters (must be equal to current number of entries). */

253 unsigned int num_counters;

254 /* The old entries' counters. */

255 struct ipt_counters *counters;

256

257 /* The entries (hang off end: not really an array). */

258 struct ipt_entry entries[0];

259 };

260

这个更改计数器时传递的参数类型。

261 /* The argument to IPT_SO_ADD_COUNTERS. */

262 struct ipt_counters_info

263 {

264 /* Which table. */

265 char name[IPT_TABLE_MAXNAMELEN];

266

267 unsigned int num_counters;

268

269 /* The counters (actually `number' of these). */

270 struct ipt_counters counters[0];

271 };

272

这个是想获取防火墙规则时,传递给系统调用的参数类型。

273 /* The argument to IPT_SO_GET_ENTRIES. */

274 struct ipt_get_entries

275 {

276 /* Which table: user fills this in. */

277 char name[IPT_TABLE_MAXNAMELEN];

278

279 /* User fills this in: total entry size. */

280 unsigned int size;

281

282 /* The entries. */

283 struct ipt_entry entrytable[0];

284 };

285

286 /* Standard return verdict, or do jump. */

287 #define IPT_STANDARD_TARGET ""

288 /* Error verdict. */

289 #define IPT_ERROR_TARGET "ERROR"

290

现面定义了一些使用例程

291 /* Helper functions */

获取一条防火墙规则的target位置

292 extern __inline__ struct ipt_entry_target *

293 ipt_get_target(struct ipt_entry *e)

294 {

295 return (void *)e + e->target_offset;

296 }

297

下面的宏遍历处理一条防火墙规则的所有匹配。我已经说过每一条防火墙规则在iptables中分为三部分,而且每一部分的大小都是可变的。比如match部分,它本身可以有多个match项。

298 /* fn returns 0 to continue iteration */

299 #define IPT_MATCH_ITERATE(e, fn, args...) 300 ({ 301 unsigned int __i; 302 int __ret = 0; 303 struct ipt_entry_match *__m; \

这个for语句我来解释一下:首先__i取值为ipt_entry结构的大小,实质上就是match匹配的开始处的偏移地址,将其与e相加就得到了match匹配的地址,然后调用fn处理这个匹配。如果函数返回值为零,当前匹配的偏移地址加上当前匹配的大小,如果不超过target的偏移地址,则继续处理下一条匹配。

304 305 for (__i = sizeof(struct ipt_entry); 306 __i target_offset; 307 __i += __m->u.match_size) { 308 __m = (void *)(e) + __i; 309 310 __ret = fn(__m , ## args); 311 if (__ret != 0) 312 break; 313 } 314 __ret; 315 })

316

这个宏处理一个table中的所有防火墙规则。对比对上一个宏的理解,这里我就不解释了。

317 /* fn returns 0 to continue iteration */

318 #define IPT_ENTRY_ITERATE(entries, size, fn, args...) 319 ({ 320 unsigned int __i; 321 int __ret = 0; 322 struct ipt_entry *__e; 323 324 for (__i = 0; __i next_offset) { 325 __e = (void *)(entries) + __i; 326 327 __ret = fn(__e , ## args); 328 if (__ret != 0) 329 break; 330 } 331 __ret; 332 })

333

334 /*

335 * Main firewall chains definitions and global var's definitions.

336 */

337 #ifdef __KERNEL__

338

339 #include ·

340 extern void ipt_init(void) __init;

341

所有的匹配处理都注册到一个match处理链表中,链表结点的类型就是这里的结构类型。当处理匹配时都是调用这里注册的处理函数。每个结点实质上由三个函数构成,一个匹配处理函数,一个合法性检查函数,一个析构函数。最后一个是反身指针,指针的作用如注释所示。

342 struct ipt_match

343 {

344 struct list_head list;

345

346 const char name[IPT_FUNCTION_MAXNAMELEN];

347

348 /* Return true or false: return FALSE and set *hotdrop = 1 to

349 force immediate packet drop. */

350 int (*match)(const struct sk_buff *skb,

351 const struct net_device *in,

352 const struct net_device *out,

353 const void *matchinfo,

354 int offset,

355 const void *hdr,

356 u_int16_t datalen,

357 int *hotdrop);

358

359 /* Called when user tries to insert an entry of this type. */

360 /* Should return true or false. */

361 int (*checkentry)(const char *tablename,

362 const struct ipt_ip *ip,

363 void *matchinfo,

364 unsigned int matchinfosize,

365 unsigned int hook_mask);

366

367 /* Called when entry of this type deleted. */

368 void (*destroy)(void *matchinfo, unsigned int matchinfosize);

369

370 /* Set this to THIS_MODULE if you are a module, otherwise NULL */

371 struct module *me;

372 };

373

和match一样,所有的target都注册到这个结构类型的全局链表中,每个target的处理函数都是这里注册的函数。和上面的解释一样,这里也主要包含三个函数指针。

374 /* Registration hooks for targets. */

375 struct ipt_target

376 {

377 struct list_head list;

378

379 const char name[IPT_FUNCTION_MAXNAMELEN];

380

381 /* Returns verdict. */

382 unsigned int (*target)(struct sk_buff **pskb,

383 unsigned int hooknum,

384 const struct net_device *in,

385 const struct net_device *out,

386 const void *targinfo,

387 void *userdata);

388

389 /* Called when user tries to insert an entry of this type:

390 hook_mask is a bitmask of hooks from which it can be

391 called. */

392 /* Should return true or false. */

393 int (*checkentry)(const char *tablename,

394 const struct ipt_entry *e,

395 void *targinfo,

396 unsigned int targinfosize,

397 unsigned int hook_mask);

398

399 /* Called when entry of this type deleted. */

400 void (*destroy)(void *targinfo, unsigned int targinfosize);

401

402 /* Set this to THIS_MODULE if you are a module, otherwise NULL */

403 struct module *me;

404 };

405

注册函数

406 extern int ipt_register_target(struct ipt_target *target);

407 extern void ipt_unregister_target(struct ipt_target *target);

408

409 extern int ipt_register_match(struct ipt_match *match);

410 extern void ipt_unregister_match(struct ipt_match *match);

411

table结构

412 /* Furniture shopping... */

413 struct ipt_table

414 {

415 struct list_head list;

416

417 /* A unique name... */

418 char name[IPT_TABLE_MAXNAMELEN];

419

420 /* Seed table: copied in register_table */

421 struct ipt_replace *table;

422

423 /* What hooks you will enter on */

424 unsigned int valid_hooks;

425

426 /* Lock for the curtain */

427 rwlock_t lock;

428

429 /* Man behind the curtain... */

430 struct ipt_table_info *private;

431 };

432

小结iptables的防火墙组织结构:

现在我们可以给出iptables防火墙规则的组织结构了。第一级是table级,每一个防火墙可以有多个table;第二级是hook级,每一个table都有一个hook集合,每个hook都有一个防火墙规则链;第三级基本规则级,基本规则级的规则包括三部分,IP规则信息、匹配规则信息和target。而这三个组成部分的每一个都可以包括同类型的多个部分规则。

433 extern int ipt_register_table(struct ipt_table *table);

434 extern void ipt_unregister_table(struct ipt_table *table);

435 extern unsigned int ipt_do_table(struct sk_buff **pskb,

436 unsigned int hook,

437 const struct net_device *in,

438 const struct net_device *out,

439 struct ipt_table *table,

440 void *userdata);

441

有关table结构对齐的宏。

442 #define IPT_ALIGN(s) (((s) + (__alignof__(struct ipt_entry)-1)) ~(__alignof__(struct ipt_entry)-1))

443 #endif /*__KERNEL__*/

444 #endif /* _IPTABLES_H */

445

西安交通大学 王灏

[目录]

防火墙技术分析讲义

防火墙技术分析讲义

yawl@docshow.net 一 基本概念

1.1 防火墙分类:

包过滤

代理(应用层网关)

1.2 代理:

两个连接(browser与proxy之间,proxy与web server之间)。

工作在应用层。

直接发往服务器的包:

GET / HTTP/1.1

Accept: */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)

Host: www.lisoleg.net

Connection: Keep-Alive

往代理发出的包:

GET http://www.lisoleg.net/ HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/msword, */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

If-Modified-Since: Thu, 14 Dec 2000 07:24:52 GMT

If-None-Match: "8026-185-3a3875c4"

User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)

Host: www.lisoleg.net

Proxy-Connection: Keep-Alive

增强:

cache

1.3 包过滤

单IP包检测

缺陷:无状态

1.4 增强1-状态检测(Stateful Inspection),又称动态包过滤(dynamic packet filtering)

1.4.1 规则表和动态状态表

1.4.2 ftp的例子:

A 4847->B 21 PORT 192,168,7,60,18,241

B 21->A 4847 PORT command successful.

B 20->A 4849 syn

> A classic example is transferring files using FTP. The firewall remembers the details of the

> incoming request to get a file from an FTP server. The firewall then tracks the back-channel

> request (the FTP Port command) by the server for transferring information back to the client.

> As long as the information agrees (same IP addresses, no changes in port numbers, and no

> non-FTP requests), the firewall allows the traffic. After the transfer is complete, the

> firewall closes the ports involved.

1.4.3 两种实现方法:

1.4.3.1 checkpoint FW1,netfilter

1.4.3.2 动态添加规则(ipchains patch)

> I believe it does exactly what I want: Installing a temporary

> "backward"-rule to let packets in as a response to an

> outgoing request.

1.5 增强2-地址转换:

1.5.1 静态NAT

1.5.2 动态NAT

1.5.3 地址伪装

1.6 增强3-VPN:

位置的优越性

二 Linux下防火墙的实现之一(2.2内核):

2.1 截获位置:

网络层

----------------------------------------------------------------

| ACCEPT/ lo interface |

v REDIRECT _______ |

--> C --> S --> ______ --> D --> ~~~~~~~~ -->|forward|----> _______ -->

h a |input | e {Routing } |Chain | |output |ACCEPT

e n |Chain | m {Decision} |_______| --->|Chain |

c i |______| a ~~~~~~~~ | | ->|_______|

k t | s | | | | |

s y | q | v | | |

u | v e v DENY/ | | v

m | DENY/ r Local Process REJECT | | DENY/

| v REJECT a | | | REJECT

| DENY d --------------------- |

v e -----------------------------

DENY

2.2 提炼出的代码:

输入检测:

/*

* Main IP Receive routine.

*/

int ip_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)

{

#ifdef CONFIG_FIREWALL

int fwres;

u16 rport;

#endif /* CONFIG_FIREWALL */

......

#ifdef CONFIG_FIREWALL

/*

* See if the firewall wants to dispose of the packet.

*

* We can't do ICMP reply or local delivery before routing,

* so we delay those decisions until after route. --RR

*/

fwres = call_in_firewall(PF_INET, dev, iph, &rport, &skb);

if (fwres < FW_ACCEPT && fwres != FW_REJECT)

goto drop;

iph = skb->nh.iph;

#endif /* CONFIG_FIREWALL */

......

#ifdef CONFIG_FIREWALL

if (fwres == FW_REJECT) {

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);

goto drop;

}

#endif /* CONFIG_FIREWALL */

return skb->dst->input(skb); //根据路由查找的结果决定是转发(ip_forward)还是发往上层(ip_local_deliver)

drop:

kfree_skb(skb); //如果规则匹配的结果是FW_REJECT,FW_BLOCK,丢弃此包

return(0);

}

转发检测:

int ip_forward(struct sk_buff *skb)

{

...

#ifdef CONFIG_FIREWALL

fw_res=call_fw_firewall(PF_INET, dev2, iph, NULL, &skb);

switch (fw_res) {

case FW_ACCEPT:

case FW_MASQUERADE:

break;

case FW_REJECT:

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);

/* fall thru */

default:

kfree_skb(skb);

return -1;

}

#endif

...

}

输出检测:(不同的上层协议走不同的流程,因此检测点较多)

UDP/RAW/ICMP报文:ip_build_xmit

TCP报文:ip_queue_xmit

转发的包:ip_forward

其他:ip_build_and_send_pkt

实际的匹配:

/*

* Returns one of the generic firewall policies, like FW_ACCEPT.

*

* The testing is either false for normal firewall mode or true for

* user checking mode (counters are not updated, TOS & mark not done).

*/

static int

ip_fw_check(struct iphdr *ip, //IP头位置

const char *rif, //出口网卡的名字

__u16 *redirport, //端口转发时用到

struct ip_chain *chain, //规则链的名字

struct sk_buff *skb, //要检测的数据包

unsigned int slot,

int testing) //见函数本身的注释

调用举例:

call_in_firewall实际调用ipfw_input_check,而ipfw_input_check中有:

int ipfw_input_check(struct firewall_ops *this, int pf, struct device *dev,

void *phdr, void *arg, struct sk_buff **pskb)

{

return ip_fw_check(phdr, dev->name,

arg, IP_FW_INPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);

}

实际流程:

ip_fw_check

{

从传入的skb参数中提取源地址src,目的地址dst,源端口src_port,目的端口dst_port,

TCP发起连接标志tcpsyn,分片包位移offset,IP包TOS消息oldtos;

......

f = chain->chain; //取出规则链的的一条规则,规则链由chain参数传入

count = 0;

do {

for (; f; f = f->next) { //遍历规则链中的规则,直到匹配(ip_rule_match返回1)

count++;

if (ip_rule_match(f,rif,ip,

tcpsyn,src_port,dst_port,offset)) {

if (!testing

&& !ip_fw_domatch(f, ip, rif, chain->label,//作些标记,一般返回1

skb, slot,

src_port, dst_port,

count, tcpsyn)) {

ret = FW_BLOCK;

goto out;

}

break;

}

}

if(f) { //找到匹配规则

......

}else { //这次遍历根本没找到

是从别的地方跳转过来的,则转回去,然后继续遍历;

否则应用这条链的缺省规则;

}

} while (ret == FW_SKIP+2);

out:

......

return ret;

}

碎片:

根据第一个片的消息进行过滤,其他分片则允许通过。如果规则是丢弃的话,虽然后面的分片都可到达主机,

但由于第一片被滤掉了,无法重组成功,因此从效果上也相当于整个IP包被丢弃。

存在的漏洞等.

2.3 规则:

from 192.168.7.0/24 to 192.168.6.32/32 tcp 80 BLOCK

规则的数据结构表示:

规则链

struct ip_chain

{

ip_chainlabel label; /* Defines the label for each block */

struct ip_chain *next; /* Pointer to next block */

struct ip_fwkernel *chain; /* Pointer to first rule in block */

__u32 refcount; /* Number of refernces to block */

int policy; /* Default rule for chain. Only *

* used in built in chains */

struct ip_reent reent[0]; /* Actually several of these */

};

规则

struct ip_fwkernel

{

struct ip_fw ipfw;

struct ip_fwkernel *next; /* where to go next if current

* rule doesn't match */

struct ip_chain *branch; /* which branch to jump to if

* current rule matches */

int simplebranch; /* Use this if branch == NULL */

struct ip_counters counters[0]; /* Actually several of these */

};

待匹配的数据包消息

struct ip_fw

{

struct in_addr fw_src, fw_dst; /* Source and destination IP addr */

struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */

__u32 fw_mark; /* ID to stamp on packet */

__u16 fw_proto; /* Protocol, 0 = ANY */

__u16 fw_flg; /* Flags word */

__u16 fw_invflg; /* Inverse flags */

__u16 fw_spts[2]; /* Source port range. */

__u16 fw_dpts[2]; /* Destination port range. */

__u16 fw_redirpt; /* Port to redirect to. */

__u16 fw_outputsize; /* Max amount to output to

NETLINK */

char fw_vianame[IFNAMSIZ]; /* name of interface "via" */

__u8 fw_tosand, fw_tosxor; /* Revised packet priority */

};

2.4 地址转换

ip_fw_demasquerade

ip_fw_masquerade

三 Linux下防火墙的实现之二(2.4内核):

3.1

A Packet Traversing the Netfilter System:

--->PRE------>[ROUTE]--->FWD---------->POST------>

Conntrack | Filter ^ NAT (Src)

Mangle | | Conntrack

NAT (Dst) | [ROUTE]

(QDisc) v |

IN Filter OUT Conntrack

| Conntrack ^ Mangle

| | NAT (Dst)

v | Filter

3.2 例子

## Insert connection-tracking modules (not needed if built into kernel).

# insmod ip_conntrack

# insmod ip_conntrack_ftp

## Create chain which blocks new connections, except if coming from inside.

# iptables -N block

# iptables -A block -m state --state ESTABLISHED,RELATED -j ACCEPT

# iptables -A block -m state --state NEW -i ! ppp0 -j ACCEPT

# iptables -A block -j DROP

## Jump to that chain from INPUT and FORWARD chains.

# iptables -A INPUT -j block

# iptables -A FORWARD -j block

3.3 规则的描述

一条规则分为三部分:

struct ipt_entry //主要用来匹配IP头

struct ip_match //额外的匹配(tcp头,mac地址等)

struct ip_target //除缺省的动作外(如ACCEPT,DROP),可以增加新的(如REJECT)。

3.4 代码提炼

ip_input.c:

/*

* Main IP Receive routine.

*/

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)

{

...

return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,

ip_rcv_finish);

...

}

netfilter.h:

#ifdef CONFIG_NETFILTER

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

#else /* !CONFIG_NETFILTER */

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)

#endif /*CONFIG_NETFILTER*/

大的框架:"HOOK表":

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]; //netfilter.c

通过nf_register_hook和nf_unregister_hook完成添加删除工作,nf_iterate负责执行hook上的函数。

增加用户自定义的HOOK,参见【8】,【10】:

重要流程(建议结合netfilter hacking howto 4.1.3来看):

/* Returns one of the generic firewall policies, like NF_ACCEPT. */

unsigned int

ipt_do_table(struct sk_buff **pskb,

unsigned int hook,

const struct net_device *in,

const struct net_device *out,

struct ipt_table *table,

void *userdata)

{

struct ipt_entry *e;

struct ipt_entry_target *t;

unsigned int verdict = NF_DROP;

table_base = (void *)table->private->entries

+ TABLE_OFFSET(table->private,

cpu_number_map(smp_processor_id()));

e = get_entry(table_base, table->private->hook_entry[hook]);

...

ip_packet_match(ip, indev, outdev, &e->ip, offset);

...

IPT_MATCH_ITERATE(e, do_match, *pskb, in, out, offset, protohdr, datalen, &hotdrop)

...

t = ipt_get_target(e);

...

verdict = t->u.kernel.target->target(pskb, hook, in, out, t->data, userdata);//非标准的target走这一步

...

return verdict;

}

要加强对这段话的理解(netfilter hacking howto 4.1节) :

>iptables does not register with any netfilter hooks: it relies on

>other modules to do that and feed it the packets as appropriate; a

>module must register the netfilter hooks and ip_tables separately, and

>provide the mechanism to call ip_tables when the hook is reached.

四 Linux下防火墙的实现之三(checkpoint FW1)

让我们看看checkpoint的在linux上的防火墙是如何实现的,最终我们会发现,竟然和lkm使用的手段差不多:)

fw1通过dev_add_pack的办法加载输入过滤函数,但是在net_bh()中,传往网络层的skbuff是克隆的,即

skb2=skb_clone(skb, GFP_ATOMIC);

if(skb2)

pt_prev->func(skb2, skb->dev, pt_prev);

而fw1是怎么解决这个问题的呢?见下面的代码:

输入一:

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 4

; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?

; Attributes: bp-based frame

public fwinstallin

fwinstallin proc near ; CODE XREF: fwinstall+E9p

; fwinstall+149p

var_18 = byte ptr -18h

arg_0 = dword ptr 8

push ebp

mov ebp, esp

sub esp, 10h

push esi

push ebx

mov esi, ds:dev_base

cmp [ebp+arg_0], 0

jz short loc_0_802CBD0

add esp, 0FFFFFFF4h

push offset fw_ip_packet_type

call dev_add_pack

mov ebx, fw_ip_packet_type+10h ;如果考虑字节对齐问题的话fw_ip_packet_type+10h这时应该是ip_packet_type

mov dword ptr ds:fw_type_list, ebx

jmp short loc_0_802CB9C

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 4

loc_0_802CB90: ; CODE XREF: fwinstallin+41j

add esp, 0FFFFFFF4h

push ebx

call dev_remove_pack ;fw1把ip_packet_type歇载掉了,然后自己在自己的处理函数(fw_filterin)中调ip_recv

mov ebx, [ebx+10h]

loc_0_802CB9C: ; CODE XREF: fwinstallin+2Dj

add esp, 10h

test ebx, ebx

jnz short loc_0_802CB90

test esi, esi

jz short loc_0_802CC14

loc_0_802CBA7: ; CODE XREF: fwinstallin+68j

test byte ptr fwdebug, 81h

jz short loc_0_802CBC3

add esp, 0FFFFFFF8h

mov eax, [esi]

push eax

push offset aFwinstallinS ; "fwinstallin: %s\n"

call fwkdebug_printf

add esp, 10h

loc_0_802CBC3: ; CODE XREF: fwinstallin+4Ej

mov esi, [esi+28h]

test esi, esi

jnz short loc_0_802CBA7

jmp short loc_0_802CC14

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 8

loc_0_802CBD0: ; CODE XREF: fwinstallin+12j

cmp dword ptr ds:fw_type_list, 0

jz short loc_0_802CC14

add esp, 0FFFFFFF4h

push offset fw_ip_packet_type

call dev_remove_pack

add esp, 10h

cmp dword ptr ds:fw_type_list, 0

jz short loc_0_802CC14

loc_0_802CBF2: ; CODE XREF: fwinstallin+B2j

add esp, 0FFFFFFF4h

mov eax, dword ptr ds:fw_type_list

push eax

call dev_add_pack

mov eax, dword ptr ds:fw_type_list

add esp, 10h

mov eax, [eax+10h]

mov dword ptr ds:fw_type_list, eax

test eax, eax

jnz short loc_0_802CBF2

loc_0_802CC14: ; CODE XREF: fwinstallin+45j

; fwinstallin+6Aj ...

lea esp, [ebp+var_18]

xor eax, eax

pop ebx

pop esi

mov esp, ebp

pop ebp

retn

fwinstallin endp

输入二:

public fw_ip_packet_type

fw_ip_packet_type dd 8, 0, offset fw_filterin, 2 dup(0) ; DATA XREF: fwinstallin+17o

输出的挂载和lkm的手法一样,更改dev->hard_start_xmit。dev结构在2.2版本的发展过程中变了一次,

为了兼容fw1对这点也做了处理。

输出一:

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 4

; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?

; Attributes: bp-based frame

public fwinstallout

fwinstallout proc near ; CODE XREF: fwinstall+FBp

; fwinstall+153p

var_18 = byte ptr -18h

arg_0 = dword ptr 8

push ebp

mov ebp, esp

sub esp, 0Ch

push edi

push esi

push ebx

mov edi, [ebp+arg_0]

xor esi, esi

mov ebx, ds:dev_base

jmp short loc_0_802D0A8

loc_0_802D096: ; CODE XREF: fwinstallout+50j

add esp, 0FFFFFFFCh

push edi

push esi

push ebx

call installout_on_device

add esp, 10h

mov ebx, [ebx+28h]

inc esi

loc_0_802D0A8: ; CODE XREF: fwinstallout+14j

test ebx, ebx

jz short loc_0_802D0F8

test byte ptr fwdebug, 81h

jz short loc_0_802D0CD

xor eax, eax

mov ax, [ebx+50h]

push eax

mov eax, [ebx]

push eax

push esi

push offset aFwinstalloutIn ; "fwinstallout: interface %d: name=%s, fl"...

call fwkdebug_printf

add esp, 10h

loc_0_802D0CD: ; CODE XREF: fwinstallout+33j

cmp esi, 3Fh

jle short loc_0_802D096

add esp, 0FFFFFFF8h

push 40h

push offset aFw1CanOnlyHand ; "FW-1: Can only handle %d interfaces\n"

call fwkdebug_printf

add esp, 10h

test edi, edi

jz short loc_0_802D0F8

add esp, 0FFFFFFF4h

push offset aFw1NotAllInter ; "FW-1: Not all interfaces installed\n"

call fwkdebug_printf

add esp, 10h

loc_0_802D0F8: ; CODE XREF: fwinstallout+2Aj

; fwinstallout+66j

mov fw_nif, esi

test byte ptr fwdebug, 81h

jz short loc_0_802D124

add esp, 0FFFFFFFCh

mov eax, offset aUn ; "un"

test edi, edi

jz short loc_0_802D118

mov eax, offset unk_0_80687E4

loc_0_802D118: ; CODE XREF: fwinstallout+91j

push eax

push esi

push offset aFw1DInterfaces ; "FW-1: %d interfaces %sinstalled\n"

call fwkdebug_printf

loc_0_802D124: ; CODE XREF: fwinstallout+85j

lea esp, [ebp+var_18]

xor eax, eax

pop ebx

pop esi

pop edi

mov esp, ebp

pop ebp

retn

fwinstallout endp

输出二:

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 10h

; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?

; Attributes: bp-based frame

public installout_on_device

installout_on_device proc near ; CODE XREF: fwinstallout+1Cp

var_18 = byte ptr -18h

var_4 = dword ptr -4

arg_0 = dword ptr 8

arg_4 = dword ptr 0Ch

arg_8 = dword ptr 10h

push ebp

mov ebp, esp

sub esp, 0Ch

push edi

push esi

push ebx

mov edi, [ebp+arg_0]

mov esi, [ebp+arg_4]

mov ebx, [ebp+arg_8]

add esp, 0FFFFFFF4h

push edi

call xmit_func_addr

mov [ebp+var_4], eax

add esp, 10h

test ebx, ebx

jz short loc_0_802CFD4

mov ebx, esi

shl ebx, 4

cmp (oftab+4)[ebx], 0

jz short loc_0_802CF90

add esp, 0FFFFFFF4h

push offset aFw1OutputFilte ; "FW-1: Output filter already installed\n"

call fwkdebug_printf

mov eax, 6Ah

jmp loc_0_802D074

输出三:

; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

align 8

; 圹圹圹圹圹圹圹?S U B R O U T I N E 圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹圹?

; Attributes: bp-based frame

public xmit_func_addr

xmit_func_addr proc near ; CODE XREF: installout_on_device+16p

arg_0 = dword ptr 8

push ebp

mov ebp, esp

mov edx, [ebp+arg_0]

lea eax, [edx+0ACh]

cmp kver, 0Dh

jle short loc_0_802CB5B

lea eax, [edx+0B0h]

loc_0_802CB5B: ; CODE XREF: xmit_func_addr+13j

mov esp, ebp

pop ebp

retn

xmit_func_addr endp

FW1与linux的一些比较,可以参看参考文献【11】

五 参考文献

【1】了解Check Point FW-1状态表

http://magazine.nsfocus.com/detail.asp?id=538

【2】A Stateful Inspection of FireWall-1

http://www.dataprotect.com/bh2000/

【3】Linux IPCHAINS-HOWTO

http://www.linuxdoc.org

【4】防火墙新生代:Stateful-inspection

http://www.liuxuan.com/safe/anquan/html/firewall/04.htm

【5】netfilter站点上的文档

http://netfilter.kernelnotes.org

【6】Application Gateways and Stateful Inspection:A Brief Note Comparing and Contrasting

http://www.avolio.com/apgw+spf.html

【7】Internet Firewalls:Frequently Asked Questions

http://www.interhack.net/pubs/fwfaq

【8】Writing a Module for netfilter

http://www.linux-mag.com/2000-06/gear_01.html

【9】ipchains的源代码分析

http://www.lisoleg.net/lisoleg/network/ipchains.zip

【10】内核防火墙netfilter入门

http://magazine.nsfocus.com/detail.asp?id=637

【11】Check Point Firewall-1 on Linux, Part Two

http://www.securityfocus.com/frames/?focus=linux&content=/focus/linux/articles/checkpoint2.html

[目录]

TCP/IP协议栈阅读笔记

下面是我看RH6.2(Kernel 2-2-14)的TCP/IP代码的笔记 [目录]

启动以后

先从init/main.c的start_kernel函数说起。

在这个函数里面调用kernel_thread启动了init进程,这个进程对应的函数是同一个文件里面的init函数,在init函数里面调用了一个

叫do_basic_setup的在同一个文件里面的函数,这个函数调用了net/socket.c里面的sock_init函数,这个函数就是TCP/IP协议栈,也包括ipx等的入口。 首先sock_init函数里面有很多ifdef这样的东东,我觉得对于一个普通的主机来说,这些都不会配置的,它们包括:

SLAB_SKB,CONFIG_WAN_ROUTER,CONFIG_FIREWALL,CONFIG_RTNETLINK,CONFIG_NETLINK_DEV

去掉了这些编译选项以后就剩下这样的代码:

for (i = 0; i < NPROTO; i++)

net_families[i] = NULL;

sk_init();

proto_init();

其中net_families在include/linux/net.h里面定义,是这样的:

struct net_proto_family

{

int family;

int (*create)(struct socket *sock, int protocol);

/* These are counters for the number of different methods of

each we support */

short authentication;

short encryption;

short encrypt_net;

};

其中有用的只有前两项,那个create的callback函数是每个协议,例如AF_INET等初始化上层协议如TCP/ICMP协议需要的,以后还会遇到的,这里先放着把

sk_init函数在net/core/sock.c里面,没什么说的..

struct sock *sk_alloc(int family, int priority, int zero_it)

{

struct sock *sk = kmem_cache_alloc(sk_cachep, priority);

if(sk) {

if (zero_it)

memset(sk, 0, sizeof(struct sock));

sk->family = family;

}

return sk;

}

proto_init函数在同一个文件里面:

void __init proto_init(void)

{

extern struct net_proto protocols[];

struct net_proto *pro;

pro = protocols;

while (pro->name != NULL)

{

(*pro->init_func)(pro);

pro++;

}

}

struct net_proto在include/linux/net.h里面是这样的:

struct net_proto

{

const char *name; /* Protocol name */

void (*init_func)(struct net_proto *); /* Bootstrap */

};

这个protocols的数组是在net/protocols.c里面定义的,包含了一堆的协议初始化结构体,其中我只注意两个:AF_INET和AF_PACKET

它们的初始化函数分别是inet_proto_init和packet_proto_init

[目录]

协议初始化-1

下面来看看IPv4协议和PACKET协议的初始化过程。 首先看PACKET协议,首先我们假定PACKET协议是编译在核心里面的,而不是一个MODULE,这样得到packet_proto_init函数在net/packet/af_packet.c里面是这样的:

void __init packet_proto_init(struct net_proto *pro)

{

sock_register(&packet_family_ops);

register_netdevice_notifier(&packet_netdev_notifier);

}

其中sock_register函数在net/socket.c里面,就是简单的设置前面说的net_families数组中间对应的值:

int sock_register(struct net_proto_family *ops)

{

if (ops->family >= NPROTO) {

printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n",

ops->family, NPROTO);

return -ENOBUFS;

}

net_families[ops->family]=ops;

return 0;

}

这里要说明的是packet_netdev_notifier是一个struct notifier_block类型,这个struct是在include/linux/notifier.h里面的:

struct notifier_block

{

int (*notifier_call)(struct notifier_block *self, unsigned long, void *);

struct notifier_block *next;

int priority;

};

而register_netdevice_notifier函数在net/core/dev.c里面,是这样的:

int register_netdevice_notifier(struct notifier_block *nb)

{

return notifier_chain_register(&netdev_chain, nb);

}

而notifier_chain_register函数在include/linux/notifier.h里面,是这样的:

extern __inline__ int notifier_chain_register(

struct notifier_block **list, struct notifier_block *n)

{

while(*list)

{

if(n->priority > (*list)->priority)

break;

list= &((*list)->next);

}

n->next = *list;

*list=n;

return 0;

}

显然就是根据每个block的优先级把这个block排列在一个block的链表里面,在notifier_chain_register函数里面我们可以发现这个链表是netdev_chain。实际上这个链表的作用就是在每个interface打开,关闭状态改变或者外界调用相应的ioctl的时候通知这个链表上面的所有相关的设备,而每一个协议都调用register_netdevice_notifier注册了一个netdev_notifier的结构体,这样就可以在interface改变的时候得到通知了(通过调用每个notifier_call函数)。

下面来看inet_proto_init函数,这个函数在net/ipv4/af_inet.c中间,里面也有很多ifdef的编译选项,假定下面几个是没有定义的:CONFIG_NET_IPIP,CONFIG_NET_IPGRE,CONFIG_IP_FIREWALL,CONFIG_IP_MASQUERADE,CONFIG_IP_MROUTE

假定下面几个是定义了的:CONFIG_INET_RARP,CONFIG_PROC_FS

下面是整理后的代码:

(void) sock_register(&inet_family_ops);

for(p = inet_protocol_base; p != NULL;) {

struct inet_protocol *tmp=(struct inet_protocol *)p->next;

inet_add_protocol(p);

printk("%s%s",p->name,tmp?", ":"\n");

p = tmp;

}

arp_init();

ip_init();

tcp_v4_init(&inet_family_ops);

tcp_init();

icmp_init(&inet_family_ops);

rarp_ioctl_hook = rarp_ioctl;

proc_net_register(&proc_net_rarp);

proc_net_register(&proc_net_raw);

proc_net_register(&proc_net_snmp);

proc_net_register(&proc_net_netstat);

proc_net_register(&proc_net_sockstat);

proc_net_register(&proc_net_tcp);

proc_net_register(&proc_net_udp);

[目录]

协议初始化-2

其中的sock_register函数的作用已经在前面说了,现在来看看struct inet_protocol和inet_add_protocol函数。前面的结构体

是在include/net/protocol.h里面: struct inet_protocol

{

int (*handler)(struct sk_buff *skb, unsigned short len);

void (*err_handler)(struct sk_buff *skb, unsigned char *dp, int len);

struct inet_protocol *next;

unsigned char protocol;

unsigned char copy:1;

void *data;

const char *name;

};

第一个函数是用来接收数据的callback函数,第二个是错误处理函数,其它的copy是用来协议共享的,这个以后再说,data当然就是这个结构体的私有数据了。

inet_add_protocol函数是在net/ipv4/protocol.c里面的:

void inet_add_protocol(struct inet_protocol *prot)

{

unsigned char hash;

struct inet_protocol *p2;

hash = prot->protocol & (MAX_INET_PROTOS - 1);

prot ->next = inet_protos[hash];

inet_protos[hash] = prot;

prot->copy = 0;

p2 = (struct inet_protocol *) prot->next;

while(p2 != NULL)

{

if (p2->protocol == prot->protocol)

{

prot->copy = 1;

break;

}

p2 = (struct inet_protocol *) p2->next;

}

}

显然这个函数就是建立一个hash表,然后每个hash表项都是一个链表头,然后通过这个hash表加链表的方式访问每个协议结构体。在这里你也见到了copy成员的用法了把。

arp_init函数是在net/ipv4/arp.c里面的(假定没有定义CONFIG_SYSCTL):

neigh_table_init(&arp_tbl);

dev_add_pack(&arp_packet_type);

proc_net_register(&proc_net_arp);

不知道是不是有人眼睛一亮啊,呵呵,看到了dev_add_pack函数。

还是一步步来把。

neigh_table_init函数在net/core/neighbour.c中间:

void neigh_table_init(struct neigh_table *tbl)

{

unsigned long now = jiffies;

tbl->parms.reachable_time = neigh_rand_reach_time(

tbl->parms.base_reachable_time);

init_timer(&tbl->gc_timer);

tbl->gc_timer.data = (unsigned long)tbl;

tbl->gc_timer.function = neigh_periodic_timer;

tbl->gc_timer.expires = now + tbl->gc_interval +

tbl->parms.reachable_time;

add_timer(&tbl->gc_timer);

init_timer(&tbl->proxy_timer);

tbl->proxy_timer.data = (unsigned long)tbl;

tbl->proxy_timer.function = neigh_proxy_process;

skb_queue_head_init(&tbl->proxy_queue);

tbl->last_flush = now;

tbl->last_rand = now + tbl->parms.reachable_time*20;

tbl->next = neigh_tables;

neigh_tables = tbl;

}

jiffies是当前系统的时间,在i386系统上面好象一个jiffies代表50ms,显然这个函数就是生成两个timer将一个放在系统的timerlist里面。那个gc_timer的意思是garbage collect timer,因为每过一段时间arp的cache就应该更新,所以要有一个expires时间,这段时间过了以后就要更新arp地址了,那个proxy_timer还没有看是什么,不过我假定我的机器不使用proxy也不做成proxy,所以proxy相关的都没有管:P

那个timer的function显然是时钟到期的回调函数,data是这个回调函数要使用的私有数据了。

下面是dev_add_pack函数,它在net/core/dev.c里面:

void dev_add_pack(struct packet_type *pt)

{

int hash;

#ifdef CONFIG_NET_FASTROUTE

/* Hack to detect packet socket */

if (pt->data) {

netdev_fastroute_obstacles++;

dev_clear_fastroute(pt->dev);

}

#endif

if(pt->type==htons(ETH_P_ALL))

{

netdev_nit++;

pt->next=ptype_all;

ptype_all=pt;

}

else

{

hash=ntohs(pt->type)&15;

pt->next = ptype_base[hash];

ptype_base[hash] = pt;

}

}

显然系统保留了两个表,一个是ptype_all,用来接收所有类型的包的链表,一个是一个hash数组+链表的结构,用来接收特定类型的包。

struct packet_type的定义在include/linux/netdevice.h里面,我保留原来的注释,这样就不用我多说了:)

{

unsigned short type;

/* This is really htons(ether_type). */

struct device *dev;

/* NULL is wildcarded here */

int (*func) (struct sk_buff *,

struct device *, struct packet_type *);

void *data;

/* Private to the packet type */

struct packet_type *next;

};

其中的func当然是回调函数了,举个例子来说,arp_packet_type是这样的:

static struct packet_type arp_packet_type =

{

__constant_htons(ETH_P_ARP),

NULL, /* All devices */

arp_rcv,

NULL,

NULL

};

arp_init函数还有最后一个proc_net_register函数,这个函数在include/linux/proc_fs.h里面:

static inline int proc_net_register(struct proc_dir_entry * x)

{

return proc_register(proc_net, x);

}

而proc_register在fs/proc/root.c里面,主要作用是在proc_net对应的目录下面生成每个协议的子目录,例如TCP等在/proc目录下面生成相应的目录,用户可以通过访问/proc/net目录下面的相应目录得到每个协议的统计参数。

[目录]

协议初始化-3

下面是ip_init函数,它在net/ipv4/ip_output.c里面:(下面假定定义了CONFIG_PROC_FS,CONFIG_IP_MULTICAST和CONFIG_NET_CLS_ROUTE) __initfunc(void ip_init(void))

{

dev_add_pack(&ip_packet_type);

ip_rt_init();

proc_net_register(&proc_net_igmp);

}

前面的dev_add_pack是说过的,这里就不再说了,而且proc_net_register也是前面提过的,这里都不说了,先来看看ip_rt_init函数把,它在net/ipv4/route.c里面,函数是这样的:

__initfunc(void ip_rt_init(void))

{

struct proc_dir_entry *ent;

devinet_init();

ip_fib_init();

rt_periodic_timer.function = rt_check_expire;

/* All the timers, started at system startup tend

to synchronize. Perturb it a bit.

*/

rt_periodic_timer.expires = jiffies + net_random()%

ip_rt_gc_interval + ip_rt_gc_interval;

add_timer(&rt_periodic_timer);

proc_net_register(&(struct proc_dir_entry) {

PROC_NET_RTCACHE, 8, "rt_cache",

S_IFREG | S_IRUGO, 1, 0, 0,

0, &proc_net_inode_operations,

rt_cache_get_info

});

ent = create_proc_entry("net/rt_acct", 0, 0);

ent->read_proc = ip_rt_acct_read;

}

这个函数总的看来就是注册几个notifier(后面还要看的)和初始化路由表的timer,最后就在/proc目录下面创建一个目录项。其中proc_net_register函数就不说了,而create_proc_entry函数就是在/proc/net目录下面创建一个rt_acct,就是路由参数统计(account)目录,读函数就是ip_rt_acct_read,这个函数就是从全局变量ip_rt_acct中间拷贝数据到用户缓冲中而已。

devinet_init函数是net/ipv4/devinet.c里面的函数,整理后如下:

register_gifconf(PF_INET, inet_gifconf);

register_netdevice_notifier(&ip_netdev_notifier);

register_netdevice_notifier函数在说PACKET协议的时候提过,这里不说了,register_gifconf函数是用来注册对应SIOCGIFCONF这个系统调用的协议无关的一个回调函数,这个函数对于PF_INET来说就是inet_gifconf函数。

其中inet_gifconf函数是net/ipv4/devinet.c里面的,我大概的看了一点,主要好象是在所有的interface里面做一个循环,得到相应的name和address然后返回的。不过不是非常确定。大家参谋呀:)

而register_gifconf函数本身是在net/core/dev.c里面的,如下:

static gifconf_func_t * gifconf_list [NPROTO];

int register_gifconf(unsigned int family, gifconf_func_t * gifconf)

{

if (family>=NPROTO)

return -EINVAL;

gifconf_list[family] = gifconf;

return 0;

}

这个函数的意义一目了然,就不说了。

gifconf_list里的函数会在dev_ifconf函数中间被调用,而dev_ifconf函数被dev_ioctl函数调用,dev_ioctl函数负责所有的针对interface的I/O控制。所以我们调用的interface的ioctl函数有一部分就会分到每个协议的gifconf函数里面来,我猜gifconf大概是generous interface configure的意思。就是通用接口配置的意思。

下面再看ip_fib_init函数,它在net/ipv4/fib_frontend.c中间,如下:

(假定没有define CONFIG_IP_MULTIPLE_TABLES,这个参数好象是要创建两个路由表,一个是local的,一个叫main)

__initfunc(void ip_fib_init(void))

{

proc_net_register(&(struct proc_dir_entry) {

PROC_NET_ROUTE, 5, "route",

S_IFREG | S_IRUGO, 1, 0, 0,

0, &proc_net_inode_operations,

fib_get_procinfo

});

fib_rules_init();

register_netdevice_notifier(&fib_netdev_notifier);

register_inetaddr_notifier(&fib_inetaddr_notifier);

}

其中proc_net_register和register_netdevice_notifier函数上面已经提过了,register_inetaddr_notifier函数的作用和register_netdevice_notifier差不多,这个函数也是调用的notifier_chain_register函数注册一个回调函数,这个回调函数在interface加上和删除的时候被调用,fib_rules_init函数其实也差不多,这个函数在net/ipv4/fib_rules.c里面,它其实就是调用一个

register_netdevice_notifier函数注册fib_rules_notifier回调结构体。

fib代表IPv4 Forwarding Information Base,就是IPv4转发信息的意思

[目录]

协议初始化-4

下面是分析tcp_v4_init的时候了,这个函数在net/ipv4/tcp_ipv4.c里面: __initfunc(void tcp_v4_init(struct net_proto_family *ops))

{

int err;

tcp_inode.i_mode = S_IFSOCK;

tcp_inode.i_sock = 1;

tcp_inode.i_uid = 0;

tcp_inode.i_gid = 0;

tcp_socket->inode = &tcp_inode;

tcp_socket->state = SS_UNCONNECTED;

tcp_socket->type=SOCK_RAW;

if ((err=ops->create(tcp_socket, IPPROTO_TCP))<0)

panic("Failed to create the TCP control socket.\n");

tcp_socket->sk->allocation=GFP_ATOMIC;

tcp_socket->sk->num = 256;

tcp_socket->sk->ip_ttl = MAXTTL;

}

tcp_inode当然就是一个inode节点了,而tcp_socket等于tcp_inode.u.socket_i,通过一个指针他们指向同一个内存.

tcp_socket是用来通信使用的,可以叫TCP的control socket或者是communicationsocket,当TCP通信没有相应的socket的时候这个socket就充当了socket的角色.比如在一个关闭端口上收到SYN时发送RST,或者是在三次握手的时候发送SYN(还没有accept产生新的socket)

值得注意的是ops->create函数的调用,我们前面见过对于AF_INET来说这个回调函数是net/ipv4/af_inet.c的inet_create函数,这个函数是用来创建一个socket的时候用的,由于函数比较长,这里先略过分析,这第一次的分析只是一个大致流程的熟悉而已.

由于有socket创建和通信,所以这段代码是协议相关的,所以把这段代码从原来的tcp.c里面提取了出来

下面是tcp_init函数,它在net/ipv4/tcp.c里面,大体上来说就是创建了几个hash表和bucket.这段代码创建了下面几个全局对象:

tcp_openreq_cachep

tcp_bucket_cachep

tcp_timewait_cachep

tcp_ehash

tcp_bhash

其中ehash代表established hash, bhash代表bind hash,它们当然分别是所有的满足TCP_ESTABLISHED <= sk->state < TCP_CLOSE状态的SOCK.但是我不清楚bucket在这里是什么意思.anyone knows?那几个cachep的作用也不是很清楚.由于整个函数主要是内存分配和错误处理,这里不贴了.

再下来就是icmp_init函数了,在net/ipv4/icmp.c里面,事实上,如果把tcp_v4_init里面的IPPROTO_TCP替换成IPPROTO_ICMP,基本都是一样的.

剩下的proc_net_register函数前面已经讲过了,这里就不说了.

到这里为止,Linux下面IP栈的开始的工作我们基本应该有了个了解,其中有几个关键的函数:

dev_add_pack:

注册一个链路层以上的处理函数,一般是用来使用新的网络层协议的,不过如果注册时重复也是可以的,这时候系统会设置一个copy位.如果是ETH_P_ALL则会接收所有的数据包.加入的元素保存在ptype_all链表和ptype_base hash链表中间.

inet_add_protocol:

注册一个建立在IP层以上的协议,例如TCP和UDP等

proc_net_register(还有类似的proc_register):

在/proc/net目录下面创建一个子目录项来使管理者能通过文件系统得到统计信息

现在迷惑的地方还有很多,一个是结构体sk_buff的每个成员的意义,一个是结构体sock的意义,不过这两个问题应该在以后看多了就知道了.下面我就打算一个个分析每个协议的处理了,包括状态转化/数据发送/接收.

[目录]

bottom up

let's start from bottom up :)有的时候用英语说话比汉语要简洁和有意思一点:) 一个lance得到数据以后总会这样处理:

skb = dev_alloc_skb (....);

skb->protocol = eth_type_trans(skb, dev);

....

netif_rx (skb);

eth_type_trans函数在net/ethernet/eth.c里面,作用当然很简单了,大家可以自己看;).

而netif_rx函数是在net/core/dev.c里面的,假定没有定义CONFIG_CPU_IS_SLOW(我觉得自己的CPU不慢:))和CONFIG_NET_HW_FLOWCONTROL(很少有人会意识到很多网卡有流量控制把,不过没有交换设备的支持,想凭这个东西达到Qos也没什么

用)以后的代码是这样的:

void netif_rx(struct sk_buff *skb)

{

skb->stamp = xtime;

if (backlog.qlen <= netdev_max_backlog) {

if (backlog.qlen) {

if (netdev_dropping == 0) {

skb_queue_tail(&backlog,skb);

mark_bh(NET_BH);

return;

}

atomic_inc(&netdev_rx_dropped);

kfree_skb(skb);

return;

}

netdev_dropping = 0;

skb_queue_tail(&backlog,skb);

mark_bh(NET_BH);

return;

}

netdev_dropping = 1;

atomic_inc(&netdev_rx_dropped);

kfree_skb(skb);

}

xtime是当前的时间,一个struct timeval,利用gettimeofday函数得到的就是这个东西的内容.backlog是一个sk_buff的双向链表, netdev_dropping初始化为0,如果没有定义CONFIG_NET_HW_FLOWCONTROL,这个变量一直都将是0.skb_queue_tail就是把一个sk_buff加入到backlog双向队列中去.然后mark_bh是设置了一个全局变量相对位移NET_BH处的bit就返回了.这个bit的设置将使得内核下次schedule的时候从TIMER_BH向下处理时检查到NET_BH处发现有设置就会调用对应NET_BH优先级的函数net_bh来处理,这个回调函数是在net_dev_init函数里面调用init_bh设置的,呵呵,兄弟们,如果感兴趣可以自己再init_bh看看设置一个自己的处理backlog的函数啊.

Linux在这里采取了一个古怪的策略进行控制权的转移和处理机优先级的处理.另一个函数net_bh来处理从backlog中间得到包,它是这样的(假定没定义CONFIG_BRIDGE这个选项):

void net_bh(void)

{

struct packet_type *ptype;

struct packet_type *pt_prev;

unsigned short type;

unsigned long start_time = jiffies;

NET_PROFILE_ENTER(net_bh);

if (qdisc_head.forw != &qdisc_head)

qdisc_run_queues();

while (!skb_queue_empty(&backlog))

{

struct sk_buff * skb;

if (jiffies - start_time > 1)

goto net_bh_break;

skb = skb_dequeue(&backlog);

#ifdef CONFIG_NET_FASTROUTE

if (skb->pkt_type == PACKET_FASTROUTE) {

dev_queue_xmit(skb);

continue;

}

#endif

/* XXX until we figure out every place to modify.. */

skb->h.raw = skb->nh.raw = skb->data;

if(skb->mac.raw < skb->head || skb->mac.raw > skb->data){

printk(KERN_CRIT "%s: wrong mac.raw ptr, proto=%04x\n",

skb->dev->name, skb->protocol);

kfree_skb(skb);

continue;

}

type = skb->protocol;

pt_prev = NULL;

for (ptype = ptype_all; ptype!=NULL; ptype=ptype->next)

{

if (!ptype->dev || ptype->dev == skb->dev) {

if(pt_prev)

{

struct sk_buff *skb2=skb_clone(skb, GFP_ATOMIC);

if(skb2)

pt_prev->func(skb2,skb->dev, pt_prev);

}

pt_prev=ptype;

}

}

for (ptype = ptype_base[ntohs(type)&15]; ptype != NULL;

ptype = ptype->next)

{

if (ptype->type == type && (!ptype->dev ||

ptype->dev==skb->dev))

{

if(pt_prev)

{

struct sk_buff *skb2;

skb2=skb_clone(skb, GFP_ATOMIC);

if(skb2)

pt_prev->func(skb2, skb->dev, pt_prev);

}

pt_prev=ptype;

}

} /* End of protocol list loop */

if(pt_prev)

pt_prev->func(skb, skb->dev, pt_prev);

else {

kfree_skb(skb);

}

} /* End of queue loop */

if (qdisc_head.forw != &qdisc_head)

qdisc_run_queues();

netdev_dropping = 0;

NET_PROFILE_LEAVE(net_bh);

return;

net_bh_break:

mark_bh(NET_BH);

NET_PROFILE_LEAVE(net_bh);

return;

}

这个函数其实很简单,NET_PROFILE_ENTER当然是一个宏展开了,它其实就是include/net/profile.h里面的net_profile_enter函数,而NET_PROFILE_LEAVE是profile.h文件里面的net_profile_leave函数,有兴趣的看看把.:)帮我解疑.

qdisc_head是一个Qdisc_head类型,是一个全局变量,看名字和处理顺序应该看作是一个Quick DISCovery的队列,如果不为空的话我们就要运行qdisc_run_queues函数进行清理了,不过我并不清楚这个queue的意义,这个变量和函数都在net/sched/sch_generic.c里面获得的.大家看了给我答疑把,xixi

下面的东西挺简单的,我就不说了,值得注意的是:

1.大家还记得ptype_all和ptype_base吗?就是调用dev_add_pack加入的数组啊,最终也调用了pt_prev->func(....)

2.系统先处理ptype_all然后才处理的ptype_base

3.每处理一个sk_buff如果超过1jiffies(x86上为50ms)就再等待下次调用

4.sk_clone是一个快速拷贝,没有拷贝数据,只是复制头部而已

[目录]

packet 函数

看看在net/packet/af_packet.c里面的packet_create函数,这个就是通过packet_proto_init加入的回调函数,假设定义了CONFIG_SOCK_PACKET,代码整理如下,这个函数是在用户创建链路层socket的时候被调用的: static int packet_create(struct socket *sock, int protocol)

{

struct sock *sk;

int err;

if (!capable(CAP_NET_RAW))

return -EPERM;

if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW

&& sock->type != SOCK_PACKET

)

return -ESOCKTNOSUPPORT;

//只有socket(AF_PACKET, [SOCK_DGRAM, SOCK_RAW],

//或者socket(AF_INET, SOCK_PACKET ,才能调用成功

sock->state = SS_UNCONNECTED;

MOD_INC_USE_COUNT;

err = -ENOBUFS;

sk = sk_alloc(PF_PACKET, GFP_KERNEL, 1);

if (sk == NULL)

goto out;

sk->reuse = 1;

sock->ops = &packet_ops;

if (sock->type == SOCK_PACKET)

sock->ops = &packet_ops_spkt;

//如果是old_style的SOCK_PACKET,就使用packet_ops_spkt

//如果是AF_PACKET,就使用packet_ops作为对应的socket的

//回调函数

sock_init_data(sock,sk);

sk->protinfo.af_packet = kmalloc(sizeof(struct packet_opt),

GFP_KERNEL);

//protinfo是一个union

if (sk->protinfo.af_packet == NULL)

goto out_free;

memset(sk->protinfo.af_packet, 0, sizeof(struct packet_opt));

sk->zapped=0;

//这个zapped属性表示一个TCP的socket收到了RST

sk->family = PF_PACKET;

sk->num = protocol;

sk->protinfo.af_packet->prot_hook.func = packet_rcv;

if (sock->type == SOCK_PACKET)

sk->protinfo.af_packet->prot_hook.func = packet_rcv_spkt;

sk->protinfo.af_packet->prot_hook.data = (void *)sk;

if (protocol) {

sk->protinfo.af_packet->prot_hook.type = protocol;

dev_add_pack(&sk->protinfo.af_packet->prot_hook);

//注意到了没有,如果protocol非零的话也可以dev_add_pack

//的,不过当然不能达到phrack55-12的目的,因为这时候你的

//数据已经在用户地址空间了,内核的数据也是改不了的

sk->protinfo.af_packet->running = 1;

}

sklist_insert_socket(&packet_sklist, sk);

//这个函数显然应该实现非常简单,在net/core/sock.c里面.

//packet_sklist是用来给每个socket通知interface状态变化

//的消息的,包括UP/DOWN/MULTICAST_LIST_CHANGE

//这个回调函数的实现是我们说过的register_netdev_notifier

return(0);

out_free:

sk_free(sk);

out:

MOD_DEC_USE_COUNT;

return err;

}

只有在创建了packet socket以后应用程序才能接收链路层的数据包.而只有你设置了一个非零的protocol以后才能dev_add_pack,你的socket才能接收数据的.现在看来,dev_add_pack确实是实现底层数据改写的一个重要的函数.所以下面我们

将注意dev_add_pack设置的回调函数func的使用.

[目录]

packet_rcv

我们已经知道了,如果使用socket(AF_SOCKET, ..)产生一个PACKET SOCKET的话,dev_add_pack加入的函数是packet_rcv,下面是这个在net/packet/af_packet.c里面的函数: static int packet_rcv(struct sk_buff *skb, struct device *dev,

struct packet_type *pt)

{

struct sock *sk;

struct sockaddr_ll *sll = (struct sockaddr_ll*)skb->cb;

sk = (struct sock *) pt->data;

//我们在packet_create中令data = sk了,remember?

if (skb->pkt_type == PACKET_LOOPBACK) {

kfree_skb(skb);

return 0;

}

skb->dev = dev;

sll->sll_family = AF_PACKET;

sll->sll_hatype = dev->type;

sll->sll_protocol = skb->protocol;

sll->sll_pkttype = skb->pkt_type;

sll->sll_ifindex = dev->ifindex;

sll->sll_halen = 0;

if (dev->hard_header_parse)

sll->sll_halen = dev->hard_header_parse(skb, sll->sll_addr);

if (dev->hard_header)

if (sk->type != SOCK_DGRAM)

skb_push(skb, skb->data - skb->mac.raw);

else if (skb->pkt_type == PACKET_OUTGOING)

skb_pull(skb, skb->nh.raw - skb->data);

if (sock_queue_rcv_skb(sk,skb)<0)

{

kfree_skb(skb);

return 0;

}

return(0);

}

pkt_type属性是什么地方确定的?

这里还有几个函数要说明:

skb_pull在include/linux/skbuff.h中间:

extern __inline__ char *__skb_pull(struct sk_buff *skb,

unsigned int len)

{

skb->len-=len;

return skb->data+=len;

}

extern __inline__ unsigned char * skb_pull(struct sk_buff *skb,

unsigned int len)

{

if (len > skb->len)

return NULL;

return __skb_pull(skb,len);

}

不过是把头部的数据空出来,相应调整数据头部data的地址和长度.

同样skb_push在include/linux/skbuff.h中间:

extern __inline__ unsigned char *__skb_push(struct sk_buff *skb,

unsigned int len)

{

skb->data-=len;

skb->len+=len;

return skb->data;

}

extern __inline__ unsigned char *skb_push(struct sk_buff *skb,

unsigned int len)

{

skb->data-=len;

skb->len+=len;

if(skb->data head)

{

__label__ here;

skb_under_panic(skb, len, &&here);

here: ;

}

return skb->data;

}

这个调整使数据长度加长,和skb_pull相反,不过skb_push显然更加安全一点.

在上面的程序中间,如果设备有一个明确的link_level_header,就考虑要不要调整数据长度和地址,如果sk->type不是SOCK_DGRAM的话,说明程序对整个数据包包括ll地址都感兴趣.这样需要加长数据段使得数据包含ll头部.不然如果数据是向外走的,则需要把数据裁减到只包含从网络层数据包头开始的地方.所以是从nh.raw剪掉data,这就是差值.(nh=network header)

经过了这些处理以后,现在的skb已经是可以提交的了,这样就调用sock_queue_rcv_skb函数将这个skb加入到相应socket的接收缓冲区中去.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有