Linux的TCP/IP协议栈阅读笔记

以下的文章假定你有TCP/IP的知识,看过TCPv1或者Comer的Vol1

然后编过一些Linux下面的socket程序,好,Come on

Linux内核的启动我就不说了,反正我的汇编也快忘光了,也没有
学习过GAS。

1 从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不过说实话除了CONFIG_WAN_ROUTER和CONFIG_FIREWALL可以看出来是什么意思以外,其它的我都是因为没有见过所以认为不会配置的。
去掉了这些编译选项以后就剩下这样的代码:
for (i = 0; i < NPROTO; i++)
net_families = 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

2 首先看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);

其中的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数组+链表的结构,用来接收特定类型的包
。那个fastroute不知道是什么东西。

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转发信息的意思,我有点糊涂为什么要叫这个名字而不用route什么的更明白的//sigh

下面是分析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里面提取了出来
 

4下面是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的意义,不过这两个问题应该在以后看多了就知道了.下面我就打算一个个分析每个协议的处理了,包括状态转化/数据发送/接收

5 一个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= x\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的func函数处理把

6 看看在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的使用.

7 们已经知道了,如果使用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属性,不知道这个属性是什么地方确定的,从网卡驱动 ->netif_rx->net_bh中间都没有,而且甚至在net_bh函数中间就有if(skb->pkt_type==...) 这样的语句,说明在驱动中间就设置了,但是没有找到,我faint了.这个属性以后还会大量使用,还是很重要的.

这里还有几个函数要说明:
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->datahead)
{
__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的接收缓冲区中去. 这个
函数以后再说把.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值