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
}
到此为止,上下都打通了,该是大家读源码的时候了。
标题: 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.
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 *