ICMP 实现
以下代码取自 kernel-2.6.24 . [数据结构] struct icmp_control { void (*handler)(struct sk_buff *skb); //icmp处理函数,根据icmp的类型字段 short error; /* This ICMP is classed as an error message */ }; static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1]; //每个icmp类型有一个项 [/数据结构] [初始化] 文件net/ipv4/af_inet.c中,函数 static int __init inet_init(void) { ...... if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) //注册协议处理函数,参看下面协议处理实现 printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); ...... icmp_init(&inet_family_ops); //icmp协议初始化 ...... } icmp初始化函数 static DEFINE_PER_CPU(struct socket *, __icmp_socket) = NULL; //每cpu变量 void __init icmp_init(struct net_proto_family *ops) { struct inet_sock *inet; int i; for_each_possible_cpu(i) { //循环所有的cpu int err; //在每个cpu上调用__sock_create函数创建一个 socket实例。 err = sock_create_kern(PF_INET, SOCK_RAW, IPPROTO_ICMP, &per_cpu(__icmp_socket, i)); if (err < 0) panic("Failed to create the ICMP control socket.\n"); per_cpu(__icmp_socket, i)->sk->sk_allocation = GFP_ATOMIC; //指定分配内存方法为atomic /* Enough space for 2 * 64K ICMP packets, including sk_buff struct overhead. */ per_cpu(__icmp_socket, i)->sk->sk_sndbuf = (2 * ((64 * 1024) + sizeof(struct sk_buff))); //指定发送缓冲区大小 inet = inet_sk(per_cpu(__icmp_socket, i)->sk); //获取inet_sock指针, 分配sock结构时空间大小就是inet_sock的大小 inet->uc_ttl = -1; inet->pmtudisc = IP_PMTUDISC_DONT; /* Unhash it so that IP input processing does not even see it, we do not wish this socket to see incoming packets. */ //进入的包看不到这些socket结构 per_cpu(__icmp_socket, i)->sk->sk_prot->unhash(per_cpu(__icmp_socket, i)->sk); } } [/初始化] [协议处理实现] 注册的协议处理函数,当ip向上递交数据包时,如果发现是icmp协议就会调用这个函数。 static struct net_protocol icmp_protocol = { .handler = icmp_rcv, }; 处理进入的icmp包 int icmp_rcv(struct sk_buff *skb) { struct icmphdr *icmph; struct rtable *rt = (struct rtable *)skb->dst; //路由缓存 ICMP_INC_STATS_BH(ICMP_MIB_INMSGS); switch (skb->ip_summed) { //skb的ip校验和标志 case CHECKSUM_COMPLETE: if (!csum_fold(skb->csum)) //没有伪头部的校验和检测 break; /* fall through */ case CHECKSUM_NONE: skb->csum = 0; if (__skb_checksum_complete(skb)) //全部内容的校验和检测 goto error; } if (!pskb_pull(skb, sizeof(struct icmphdr))) //是否有icmp头空间,如果有移动data指针到icmp头后面 goto error; icmph = icmp_hdr(skb); //获取icmp头 ICMPMSGIN_INC_STATS_BH(icmph->type); /* * 18 is the highest 'known' ICMP type. Anything else is a mystery * RFC 1122: 3.2.2 Unknown ICMP messages types MUST be silently discarded. */ if (icmph->type > NR_ICMP_TYPES) goto error; //icmp是发送到本地的多播或广播地址 if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) { /* RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be silently ignored (we let user decide with a sysctl). * RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently discarded if to broadcast/multicast.*/ if ((icmph->type == ICMP_ECHO || icmph->type == ICMP_TIMESTAMP) && sysctl_icmp_echo_ignore_broadcasts) { goto error; } //除了回显和时间截,地址掩码请求和应答,其他到广播和多播的icmp包全部丢弃 if (icmph->type != ICMP_ECHO && icmph->type != ICMP_TIMESTAMP && icmph->type != ICMP_ADDRESS && icmph->type != ICMP_ADDRESSREPLY) { goto error; } } icmp_pointers[icmph->type].handler(skb); //根据icmp类型调用相应的处理函数 drop: kfree_skb(skb); //处理完了释放skb return 0; error: ICMP_INC_STATS_BH(ICMP_MIB_INERRORS); goto drop; } 类型处理函数在内核中被静态的初始化. static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = { [ICMP_ECHOREPLY] = { .handler = icmp_discard, //空函数 }, [1] = { .handler = icmp_discard, .error = 1, }, [2] = { .handler = icmp_discard, .error = 1, }, [ICMP_DEST_UNREACH] = { .handler = icmp_unreach, .error = 1, }, [ICMP_SOURCE_QUENCH] = { .handler = icmp_unreach, .error = 1, }, [ICMP_REDIRECT] = { .handler = icmp_redirect, .error = 1, }, [6] = { .handler = icmp_discard, .error = 1, }, [7] = { .handler = icmp_discard, .error = 1, }, [ICMP_ECHO] = { .handler = icmp_echo, }, [9] = { .handler = icmp_discard, .error = 1, }, [10] = { .handler = icmp_discard, .error = 1, }, [ICMP_TIME_EXCEEDED] = { .handler = icmp_unreach, .error = 1, }, [ICMP_PARAMETERPROB] = { .handler = icmp_unreach, .error = 1, }, [ICMP_TIMESTAMP] = { .handler = icmp_timestamp, }, [ICMP_TIMESTAMPREPLY] = { .handler = icmp_discard, }, [ICMP_INFO_REQUEST] = { .handler = icmp_discard, }, [ICMP_INFO_REPLY] = { .handler = icmp_discard, }, [ICMP_ADDRESS] = { .handler = icmp_address, }, [ICMP_ADDRESSREPLY] = { .handler = icmp_address_reply, }, }; 我们一个一个看。 icmp接收到不可达包的处理,不可达包括ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH. static void icmp_unreach(struct sk_buff *skb) { struct iphdr *iph; struct icmphdr *icmph; int hash, protocol; struct net_protocol *ipprot; struct sock *raw_sk; u32 info = 0; //数据部分包括了携带的ip头吗 if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto out_err; icmph = icmp_hdr(skb); //icmp头 iph = (struct iphdr *)skb->data; //携带的ip头 //ip头损坏 if (iph->ihl < 5) /* Mangled header, drop. */ goto out_err; if (icmph->type == ICMP_DEST_UNREACH) { //icmp类型是目的不可达 switch (icmph->code & 15) { //错误码标识 case ICMP_NET_UNREACH: //网络 case ICMP_HOST_UNREACH: //主机 case ICMP_PROT_UNREACH: //协议 case ICMP_PORT_UNREACH: //端口 break; //不可达 case ICMP_FRAG_NEEDED: //需要分片 if (ipv4_config.no_pmtu_disc) { LIMIT_NETDEBUG(KERN_INFO "ICMP: %u.%u.%u.%u: fragmentation needed and DF set.\n", NIPQUAD(iph->daddr)); } else { //在到那个目的地址的路由缓存中保存mtu的大小,在发送数据时就会根据这个mtu大小进行分片 info = ip_rt_frag_needed(iph, ntohs(icmph->un.frag.mtu)); if (!info) goto out; } case ICMP_SR_FAILED: LIMIT_NETDEBUG(KERN_INFO "ICMP: %u.%u.%u.%u: Source Route Failed.\n", NIPQUAD(iph->daddr)); break; default: break; } if (icmph->code > NR_ICMP_UNREACH) //超过限制,错误的的不可达码 goto out; } else if (icmph->type == ICMP_PARAMETERPROB) info = ntohl(icmph->un.gateway) >> 24; //一些路由器会发送应答到广播地址,可能是用户工具引起的问题 if (!sysctl_icmp_ignore_bogus_error_responses && inet_addr_type(iph->daddr) == RTN_BROADCAST) { if (net_ratelimit()) printk(KERN_WARNING "%u.%u.%u.%u sent an invalid ICMP type %u, code %u " "error to a broadcast: %u.%u.%u.%u on %s\n", NIPQUAD(ip_hdr(skb)->saddr), icmph->type, icmph->code, NIPQUAD(iph->daddr), skb->dev->name); goto out; } /* Checkin full IP header plus 8 bytes of protocol to avoid additional coding at protocol handlers. */ if (!pskb_may_pull(skb, iph->ihl * 4 + 8)) //ip头加8字节的协议 goto out; iph = (struct iphdr *)skb->data; protocol = iph->protocol; //获取协议 hash = protocol & (MAX_INET_PROTOS - 1); //递交icmp信息到 raw socket, why ?????? read_lock(&raw_v4_lock); if ((raw_sk = sk_head(&raw_v4_htable[hash])) != NULL) { while ((raw_sk = __raw_v4_lookup(raw_sk, protocol, iph->daddr, iph->saddr, skb->dev->ifindex)) != NULL) { raw_err(raw_sk, skb, info); raw_sk = sk_next(raw_sk); iph = (struct iphdr *)skb->data; } } read_unlock(&raw_v4_lock); rcu_read_lock(); ipprot = rcu_dereference(inet_protos[hash]); //根据协议查找协议处理结构 if (ipprot && ipprot->err_handler) //如果有,调用相关的协议错误处理函数处理这个icmp不可达包 ipprot->err_handler(skb, info); rcu_read_unlock(); out: return; out_err: ICMP_INC_STATS_BH(ICMP_MIB_INERRORS); goto out; } icmp重定向处理 static void icmp_redirect(struct sk_buff *skb) { struct iphdr *iph; if (skb->len < sizeof(struct iphdr)) 长度检测 goto out_err; /* Get the copied header of the packet that caused the redirect */ if (!pskb_may_pull(skb, sizeof(struct iphdr))) //ip头长度检测 goto out; iph = (struct iphdr *)skb->data; //取出ip头 switch (icmp_hdr(skb)->code & 7) { //编码 case ICMP_REDIR_NET: //网络重定向 case ICMP_REDIR_NETTOS: /* As per RFC recommendations now handle it as a host redirect.*/ case ICMP_REDIR_HOST: //主机重定向 case ICMP_REDIR_HOSTTOS: //在路由告诉缓存中,更新相同缓存项的rt_gateway字段 ip_rt_redirect(ip_hdr(skb)->saddr, iph->daddr, icmp_hdr(skb)->un.gateway, iph->saddr, skb->dev); break; } out: return; out_err: ICMP_INC_STATS_BH(ICMP_MIB_INERRORS); goto out; } icmp回显请求 static void icmp_echo(struct sk_buff *skb) { if (!sysctl_icmp_echo_ignore_all) { //是否忽略回显请求 struct icmp_bxm icmp_param; //保存一些icmp内容 icmp_param.data.icmph = *icmp_hdr(skb); icmp_param.data.icmph.type = ICMP_ECHOREPLY; icmp_param.skb = skb; icmp_param.offset = 0; icmp_param.data_len = skb->len; icmp_param.head_len = sizeof(struct icmphdr); icmp_reply(&icmp_param, skb); } } static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb) { struct sock *sk = icmp_socket->sk; struct inet_sock *inet = inet_sk(sk); struct ipcm_cookie ipc; struct rtable *rt = (struct rtable *)skb->dst; //路由缓存 __be32 daddr; //解析其中的ip选项 if (ip_options_echo(&icmp_param->replyopts, skb)) return; if (icmp_xmit_lock()) //是否可以锁定这个cpu上的icmp_socket. return; icmp_param->data.icmph.checksum = 0; inet->tos = ip_hdr(skb)->tos; daddr = ipc.addr = rt->rt_src; //目的地址 ipc.opt = NULL; if (icmp_param->replyopts.optlen) { //有ip选项 ipc.opt = &icmp_param->replyopts; if (ipc.opt->srr) daddr = icmp_param->replyopts.faddr; } { struct flowi fl = { .nl_u = { .ip4_u = { .daddr = daddr, .saddr = rt->rt_spec_dst, .tos = RT_TOS(ip_hdr(skb)->tos) } }, .proto = IPPROTO_ICMP }; security_skb_classify_flow(skb, &fl); if (ip_route_output_key(&rt, &fl)) //路由查找,如果没找到那么什么也不发送了 goto out_unlock; } //是否立即发送应答 if (icmpv4_xrlim_allow(rt, icmp_param->data.icmph.type, icmp_param->data.icmph.code)) icmp_push_reply(icmp_param, &ipc, rt); //发送应答 ip_rt_put(rt); out_unlock: icmp_xmit_unlock(); } 判断应答是否发送 static inline int icmpv4_xrlim_allow(struct rtable *rt, int type, int code) { struct dst_entry *dst = &rt->u.dst; int rc = 1; if (type > NR_ICMP_TYPES) //类型超过范围, 这应该是个bug,需要添加 rc = 0 goto out; /* Don't limit PMTU discovery. */ //这两个类型不做限制 if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) goto out; /* No rate limit on loopback */ if (dst->dev && (dst->dev->flags & IFF_LOOPBACK)) //回环设备也不限制 goto out; /* Limit if icmp type is enabled in ratemask. */ if ((1 << type) & sysctl_icmp_ratemask) //用户通过/proc配置了限制速度的icmp类型掩码 rc = xrlim_allow(dst, sysctl_icmp_ratelimit); out: return rc; } #define XRLIM_BURST_FACTOR 6 int xrlim_allow(struct dst_entry *dst, int timeout) { unsigned long now; int rc = 0; //不发送 now = jiffies; dst->rate_tokens += now - dst->rate_last; //累加过去的时间 dst->rate_last = now; //最后使用时间 if (dst->rate_tokens > XRLIM_BURST_FACTOR * timeout) //累加时间超过指定的范围 dst->rate_tokens = XRLIM_BURST_FACTOR * timeout; //设为最大值 if (dst->rate_tokens >= timeout) { //超过用户配置的时间限制 dst->rate_tokens -= timeout; //递减配置的时间限制 rc = 1; //发送 } return rc; } 发送icmp应答函数 static void icmp_push_reply(struct icmp_bxm *icmp_param, struct ipcm_cookie *ipc, struct rtable *rt) { struct sk_buff *skb; //分配skb拷贝接收的skb数据到新分配的skb内存中,新skb被链入到icmp_socket->sk->sk_write_queue中. if (ip_append_data(icmp_socket->sk, icmp_glue_bits, icmp_param, icmp_param->data_len+icmp_param->head_len, icmp_param->head_len, ipc, rt, MSG_DONTWAIT) < 0) ip_flush_pending_frames(icmp_socket->sk); //拷贝失败 else if ((skb = skb_peek(&icmp_socket->sk->sk_write_queue)) != NULL) { //提取分配的skb struct icmphdr *icmph = icmp_hdr(skb); __wsum csum = 0; struct sk_buff *skb1; //计算校验和 skb_queue_walk(&icmp_socket->sk->sk_write_queue, skb1) { csum = csum_add(csum, skb1->csum); } csum = csum_partial_copy_nocheck((void *)&icmp_param->data, (char *)icmph, icmp_param->head_len, csum); icmph->checksum = csum_fold(csum); skb->ip_summed = CHECKSUM_NONE; ip_push_pending_frames(icmp_socket->sk); //发送队列中的skb } } static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb) { struct icmp_bxm *icmp_param = (struct icmp_bxm *)from; __wsum csum; //拷贝数据 csum = skb_copy_and_csum_bits(icmp_param->skb, icmp_param->offset + offset, to, len, 0); //添加所有icmp_param->skb的校验和到地一个skb中 skb->csum = csum_block_add(skb->csum, csum, odd); if (icmp_pointers[icmp_param->data.icmph.type].error) nf_ct_attach(skb, icmp_param->skb); return 0; } 拷贝数据到ip数据负载部分,如果需要将所有碎片链入到sk->sk_write_queue队列中 int ip_append_data(struct sock *sk, int getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb), void *from, int length, int transhdrlen, struct ipcm_cookie *ipc, struct rtable *rt, unsigned int flags) { struct inet_sock *inet = inet_sk(sk); struct sk_buff *skb; struct ip_options *opt = NULL; int hh_len; int exthdrlen; int mtu; int copy; int err; int offset = 0; unsigned int maxfraglen, fragheaderlen; int csummode = CHECKSUM_NONE; if (flags & MSG_PROBE) return 0; if (skb_queue_empty(&sk->sk_write_queue)) { //写队列为空 opt = ipc->opt; if (opt) { //有ip选项 if (inet->cork.opt == NULL) { //inet socket中ip选项指针为空,分配一个ip选项+ip最长头空间 inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation); if (unlikely(inet->cork.opt == NULL)) return -ENOBUFS; } //拷贝icmp中携带的ip选项 memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen); inet->cork.flags |= IPCORK_OPT; inet->cork.addr = ipc->addr; //记录发送这个icmp的地址 } //IP_PMTUDISC_PROBE 表示忽略对方的mtu, 如果忽略使用本地设备的mtu,设置分片大小 inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ? rt->u.dst.dev->mtu : dst_mtu(rt->u.dst.path); inet->cork.rt = rt; //保存路由 inet->cork.length = 0; sk->sk_sndmsg_page = NULL; sk->sk_sndmsg_off = 0; if ((exthdrlen = rt->u.dst.header_len) != 0) { //需要额外的头长度 length += exthdrlen; transhdrlen += exthdrlen; } } else { //队列不为空,用保存好的数据初始化一些变量 rt = inet->cork.rt; if (inet->cork.flags & IPCORK_OPT) opt = inet->cork.opt; transhdrlen = 0; exthdrlen = 0; mtu = inet->cork.fragsize; } hh_len = LL_RESERVED_SPACE(rt->u.dst.dev); //足够的硬件头空间 fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0); //每个碎片的ip头长度 maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen; //每个碎片的最大长度 if (inet->cork.length + length > 0xFFFF - fragheaderlen) { //发送来的数据长度超过了允许的最大ip数据长度(65535 - ip头 + ip选项) ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen); return -EMSGSIZE; } /* transhdrlen > 0 means that this is the first fragment and we wish it won't be fragmented in the future. */ if (transhdrlen && length + fragheaderlen <= mtu && rt->u.dst.dev->features & NETIF_F_V4_CSUM && !exthdrlen) csummode = CHECKSUM_PARTIAL; inet->cork.length += length; //累加这个长度 //长度 > mtu ,协议是 udp,且网卡设备支持GSO分片 if (((length > mtu) && (sk->sk_protocol == IPPROTO_UDP)) && (rt->u.dst.dev->features & NETIF_F_UFO)) { err = ip_ufo_append_data(sk, getfrag, from, length, hh_len, fragheaderlen, transhdrlen, mtu, flags); if (err) goto error; return 0; } if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) //队列为空 goto alloc_new_skb; while (length > 0) { /* Check if the remaining data fits into current packet. */ copy = mtu - skb->len;////这的mtu,我认为应该为maxfraglen, 这样就不用 fraggap变量和相关的操作了 if (copy < length) copy = maxfraglen - skb->len; if (copy <= 0) { char *data; unsigned int datalen; unsigned int fraglen; unsigned int fraggap; unsigned int alloclen; struct sk_buff *skb_prev; alloc_new_skb: skb_prev = skb; if (skb_prev) fraggap = skb_prev->len - maxfraglen; else fraggap = 0; /* If remaining data exceeds the mtu, we know we need more fragment(s). */ datalen = length + fraggap; //这的mtu,我认为应该为maxfraglen, 这样就不用 fraggap变量和相关的操作了 if (datalen > mtu - fragheaderlen) //数据长度超过mtu - ip头长度,需要分片 datalen = maxfraglen - fragheaderlen; //设置成合适的长度 fraglen = datalen + fragheaderlen; //一个碎片的完整长度 if ((flags & MSG_MORE) && !(rt->u.dst.dev->features & NETIF_F_SG)) alloclen = mtu; else alloclen = datalen + fragheaderlen; /* The last fragment gets additional space at tail. Note, with MSG_MORE we overallocate on fragments, * because we have no idea what fragment will be the last. */ if (datalen == length + fraggap) //最后一个分片将添加额外的长度 alloclen += rt->u.dst.trailer_len; if (transhdrlen) { //指定了传输层头长度 //分配内存hh_len是硬件地址长度 skb = sock_alloc_send_skb(sk, alloclen + hh_len + 15, (flags & MSG_DONTWAIT), &err); } else { skb = NULL; if (atomic_read(&sk->sk_wmem_alloc) <= 2 * sk->sk_sndbuf) skb = sock_wmalloc(sk, alloclen + hh_len + 15, 1, sk->sk_allocation); if (unlikely(skb == NULL)) err = -ENOBUFS; } if (skb == NULL) //分配失败 goto error; /* Fill in the control structures */ skb->ip_summed = csummode; skb->csum = 0; skb_reserve(skb, hh_len); //保留出硬件地址空间 data和tail向后移动 hh_len /*Find where to start putting bytes. */ data = skb_put(skb, fraglen); //返回data移动tail和增加len skb_set_network_header(skb, exthdrlen);//如果有额外头,移动网络头位置 //传输层头在网络头后面 skb->transport_header = (skb->network_header + fragheaderlen);//fragheaderlen 可能包括ip选项长度 data += fragheaderlen; //data指向传输层头位置 if (fraggap) { //把上一个skb最后几个没有对齐的字节拷贝到这新包的 data + transhdrlen位置 skb->csum = skb_copy_and_csum_bits(skb_prev, maxfraglen, data + transhdrlen, fraggap, 0); skb_prev->csum = csum_sub(skb_prev->csum, skb->csum); data += fraggap; //移动指针 pskb_trim_unique(skb_prev, maxfraglen); //修改上一个skb的数据长度,进行缩小 } //datalen包括传输层头和数据 copy = datalen - transhdrlen - fraggap;//要拷贝的数据长度 //从from拷贝一些传输层头后面的数据到data+transhdrlen的位置 if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) { err = -EFAULT; kfree_skb(skb); goto error; } offset += copy; //偏移累加 length -= datalen - fraggap; //长度递减,包含传输层头长度 transhdrlen = 0; exthdrlen = 0; csummode = CHECKSUM_NONE; /* Put the packet on the pending queue. */ __skb_queue_tail(&sk->sk_write_queue, skb); //链入队列 continue; } if (copy > length) copy = length; if (!(rt->u.dst.dev->features & NETIF_F_SG)) { //设备不支持SG unsigned int off; off = skb->len; if (getfrag(from, skb_put(skb, copy), offset, copy, off, skb) < 0) { __skb_trim(skb, off); err = -EFAULT; goto error; } } else { //按SG分页处理 int i = skb_shinfo(skb)->nr_frags; skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1]; struct page *page = sk->sk_sndmsg_page; int off = sk->sk_sndmsg_off; unsigned int left; if (page && (left = PAGE_SIZE - off) > 0) { if (copy >= left) copy = left; if (page != frag->page) { if (i == MAX_SKB_FRAGS) { err = -EMSGSIZE; goto error; } get_page(page); skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); frag = &skb_shinfo(skb)->frags[i]; } } else if (i < MAX_SKB_FRAGS) { if (copy > PAGE_SIZE) copy = PAGE_SIZE; page = alloc_pages(sk->sk_allocation, 0); if (page == NULL) { err = -ENOMEM; goto error; } sk->sk_sndmsg_page = page; sk->sk_sndmsg_off = 0; skb_fill_page_desc(skb, i, page, 0, 0); frag = &skb_shinfo(skb)->frags[i]; } else { err = -EMSGSIZE; goto error; } if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) { err = -EFAULT; goto error; } sk->sk_sndmsg_off += copy; frag->size += copy; skb->len += copy; skb->data_len += copy; skb->truesize += copy; atomic_add(copy, &sk->sk_wmem_alloc); } offset += copy; length -= copy; } return 0; error: inet->cork.length -= length; IP_INC_STATS(IPSTATS_MIB_OUTDISCARDS); return err; } ip_append_data函数失败就会调用这个函数十分所有skb void ip_flush_pending_frames(struct sock *sk) { struct sk_buff *skb; while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL) kfree_skb(skb); ip_cork_release(inet_sk(sk)); } icmp_push_reply-> 取出队列中的skb,然后添加完整的ip头然后发送出去 int ip_push_pending_frames(struct sock *sk) { struct sk_buff *skb, *tmp_skb; struct sk_buff **tail_skb; struct inet_sock *inet = inet_sk(sk); struct ip_options *opt = NULL; struct rtable *rt = inet->cork.rt; struct iphdr *iph; __be16 df = 0; __u8 ttl; int err = 0; if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL) //取出一个skb goto out; tail_skb = &(skb_shinfo(skb)->frag_list); //指向分片连表头 /* move skb->data to ip header from ext header */ if (skb->data < skb_network_header(skb)) __skb_pull(skb, skb_network_offset(skb)); //移动data指针到ip头位置 while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) { //循环出队所有skb __skb_pull(tmp_skb, skb_network_header_len(skb)); //移动data到传输层头位置 *tail_skb = tmp_skb; //当执行第一次时等于是(skb_shinfo(skb)->frag_list) = tmp_skb tail_skb = &(tmp_skb->next); //指向了tmp_skb的next //累加这个包的长度 skb->len += tmp_skb->len; skb->data_len += tmp_skb->len; skb->truesize += tmp_skb->truesize; __sock_put(tmp_skb->sk); //递减sock的引用计数 tmp_skb->destructor = NULL; tmp_skb->sk = NULL; } //到这就是把所有在sk->sk_write_queue中的skb(所有分片)组合到第一个skb的skb_shinfo(skb)->frag_list连表中了。 /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow to fragment the frame generated here. * No matter, what transforms how transforms change size of the packet, it will come out. */ if (inet->pmtudisc < IP_PMTUDISC_DO) skb->local_df = 1; //不分片 /* DF bit is set when we want to see DF on outgoing frames. * If local_df is set too, we still allow to fragment this frame locally. */ if (inet->pmtudisc >= IP_PMTUDISC_DO || (skb->len <= dst_mtu(&rt->u.dst) && ip_dont_fragment(sk, &rt->u.dst))) df = htons(IP_DF); //设置不分片标志 if (inet->cork.flags & IPCORK_OPT) //有ip选项 opt = inet->cork.opt; if (rt->rt_type == RTN_MULTICAST) //多播ttl ttl = inet->mc_ttl; else ttl = ip_select_ttl(inet, &rt->u.dst); //单播,需要计算 iph = (struct iphdr *)skb->data; //在第一个skb中添加ip头 iph->version = 4; iph->ihl = 5; if (opt) { iph->ihl += opt->optlen>>2; ip_options_build(skb, opt, inet->cork.addr, rt, 0); } iph->tos = inet->tos; iph->tot_len = htons(skb->len); iph->frag_off = df; ip_select_ident(iph, &rt->u.dst, sk); //选择一个ip标识 iph->ttl = ttl; iph->protocol = sk->sk_protocol; iph->saddr = rt->rt_src; iph->daddr = rt->rt_dst; ip_send_check(iph); //校验和 skb->priority = sk->sk_priority; skb->dst = dst_clone(&rt->u.dst); if (iph->protocol == IPPROTO_ICMP) icmp_out_count(((struct icmphdr *)skb_transport_header(skb))->type); //更新一些统计信息 //发送这个skb到netfilter的LOCAL_OUT hook err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output); if (err) { if (err > 0) err = inet->recverr ? net_xmit_errno(err) : 0; if (err) goto error; } out: ip_cork_release(inet); return err; error: IP_INC_STATS(IPSTATS_MIB_OUTDISCARDS); goto out; } 到这需要简单说一下,其实我们看的是icmp回显请求相关的流程,其中什么ip碎片应该就根本不会发生, 但一些函数在ip层使用所以有些看起来十分的复杂。 icmp时间截请求处理 static void icmp_timestamp(struct sk_buff *skb) { struct timeval tv; struct icmp_bxm icmp_param; if (skb->len < 4) //长度不对 goto out_err; /* Fill in the current time as ms since midnight UT: */ do_gettimeofday(&tv); //获取当前时间 icmp_param.data.times[1] = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000); icmp_param.data.times[2] = icmp_param.data.times[1]; //拷贝skb中的数据到 times[0]中 if (skb_copy_bits(skb, 0, &icmp_param.data.times[0], 4)) BUG(); icmp_param.data.icmph = *icmp_hdr(skb); icmp_param.data.icmph.type = ICMP_TIMESTAMPREPLY; //时间截应答 icmp_param.data.icmph.code = 0; icmp_param.skb = skb; icmp_param.offset = 0; icmp_param.data_len = 0; icmp_param.head_len = sizeof(struct icmphdr) + 12; icmp_reply(&icmp_param, skb); out: return; out_err: ICMP_INC_STATS_BH(ICMP_MIB_INERRORS); goto out; } 地址掩码请求,linux没有实现它,参考内核中这函数的注释 static void icmp_address(struct sk_buff *skb) { #if 0 if (net_ratelimit()) printk(KERN_DEBUG "a guy asks for address mask. Who is it?\n"); #endif } 地址掩码应答处理 static void icmp_address_reply(struct sk_buff *skb) { struct rtable *rt = (struct rtable *)skb->dst; //路由缓存 struct net_device *dev = skb->dev; struct in_device *in_dev; struct in_ifaddr *ifa; //长度不对或没有标志重定向源地址 if (skb->len < 4 || !(rt->rt_flags & RTCF_DIRECTSRC)) goto out; in_dev = in_dev_get(dev); if (!in_dev) goto out; rcu_read_lock(); //设备有地址,打开调试项,设备允许转发 if (in_dev->ifa_list && IN_DEV_LOG_MARTIANS(in_dev) && IN_DEV_FORWARD(in_dev)) { __be32 _mask, *mp; //取出掩码 mp = skb_header_pointer(skb, 0, sizeof(_mask), &_mask); BUG_ON(mp == NULL); for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { //循环所有地址,如果掩码匹配且路由地址也匹配 if (*mp == ifa->ifa_mask && inet_ifa_match(rt->rt_src, ifa)) break; } if (!ifa && net_ratelimit()) { //都不匹配 printk(KERN_INFO "Wrong address mask %u.%u.%u.%u from %s/%u.%u.%u.%u\n", NIPQUAD(*mp), dev->name, NIPQUAD(rt->rt_src)); } } rcu_read_unlock(); in_dev_put(in_dev); out:; } [/协议处理实现]
posted on 2013-08-28 10:30 SuperKing 阅读(2715) 评论(0) 编辑 收藏 举报