(3)目的端接收syn到发送synack
从前文得知,当数据报每经过一个源路由路径节点时,ip选项中的offset字段的值就加4,那么到达源路由路径的最后一个节点并得到转发后,offset字段的值所表示的偏移地址已经指向选项的末尾了
- void ip_forward_options(struct sk_buff *skb)
- {
- ......
- if (opt->srr_is_hit) {
- int srrptr, srrspace;
- optptr = raw + opt->srr;
- for ( srrptr=optptr[2], srrspace = optptr[1];
- srrptr <= srrspace;
- srrptr += 4
- ) {
- if (srrptr + 3 > srrspace)
- break;
- if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0)
- break;
- }
- if (srrptr + 3 <= srrspace) {
- opt->is_changed = 1;
- ip_rt_get_source(&optptr[srrptr-1], rt);
- skb->nh.iph->daddr = rt->rt_dst;
- optptr[2] = srrptr+4;//偏移量加4,即移动一个ip地址
- } else if (net_ratelimit())
- printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n");
- if (opt->ts_needaddr) {
- optptr = raw + opt->ts;
- ip_rt_get_source(&optptr[optptr[2]-9], rt);
- opt->is_changed = 1;
- }
- }
- if (opt->is_changed) {
- opt->is_changed = 0;
- ip_send_check(skb->nh.iph);
- }
- }
当syn包到达目的地址时,将进入函数
- int ip_options_rcv_srr(struct sk_buff *skb)
- {
- struct ip_options *opt = &(IPCB(skb)->opt);
- int srrspace, srrptr;
- u32 nexthop;
- struct iphdr *iph = skb->nh.iph;
- unsigned char * optptr = skb->nh.raw + opt->srr;
- ......
- for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
- //optptr[1]为选项的长度,optptr[2]为offset;由于在目的端,offset指向选项的末尾,并且大小为options的长度+1(为对齐的选项长度);则srrptr>srrspace,此for循环将不执行
- if (srrptr + 3 > srrspace) {
- icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
- return -EINVAL;
- }
- memcpy(&nexthop, &optptr[srrptr-1], 4);
- rt = (struct rtable*)skb->dst;
- skb->dst = NULL;
- err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev);
- rt2 = (struct rtable*)skb->dst;
- if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
- ip_rt_put(rt2);
- skb->dst = &rt->u.dst;
- return -EINVAL;
- }
- ip_rt_put(rt);
- if (rt2->rt_type != RTN_LOCAL)
- break;
- /* Superfast 8) loopback forward */
- memcpy(&iph->daddr, &optptr[srrptr-1], 4);
- opt->is_changed = 1;
- }
- if (srrptr <= srrspace) {//srrptr>srrspace,不执行
- opt->srr_is_hit = 1;
- opt->is_changed = 1;
- }
- return 0;//直接返回
- }
返回到函数ip_rcv_finish
- static inline int ip_rcv_finish(struct sk_buff *skb)
- {
- struct iphdr *iph = skb->nh.iph;
- struct rtable *rt;
- /*
- * Initialise the virtual path cache for the packet. It describes
- * how the packet travels inside Linux networking.
- */
- if (skb->dst == NULL) {
- int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
- skb->dev);//dst->input注册为ip_local_deliver
- if (unlikely(err)) {
- if (err == -EHOSTUNREACH)
- IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
- else if (err == -ENETUNREACH)
- IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
- goto drop;
- }
- }
- ......
- if (iph->ihl > 5 && ip_rcv_options(skb))//dst->input没有被修改,因为此时数据包已经到达目
- 的端
- goto drop;
- ......
- return dst_input(skb);//本机包的后续处理过程
- ......
- }
此时接收到的数据的ip选项中保存了源路由路径节点ip地址,并且是按数据包通过节点的顺序存储的;选项保存在了skb的cb中可以通过&(IPCB(skb)->opt)得到。
现在我们来分析tcp层函数tcp_v4_conn_reques函数(不考虑syncookies的过程)
- int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
- {
- struct inet_request_sock *ireq;
- struct tcp_options_received tmp_opt;
- struct request_sock *req;
- __u32 saddr = skb->nh.iph->saddr;
- __u32 daddr = skb->nh.iph->daddr;
- __u32 isn = TCP_SKB_CB(skb)->when;
- struct dst_entry *dst = NULL;
- ......
- ireq = inet_rsk(req);
- ireq->loc_addr = daddr;
- ireq->rmt_addr = saddr;
- ireq->opt = tcp_v4_save_options(sk, skb);//将skb中cb保存的ip选项拷贝到ireq->opt中
- ......
- if (tcp_v4_send_synack(sk, req, dst))//发送synack
- goto drop_and_free;
- ......
- }
现分析tcp_v4_save_options函数
- static struct ip_options *tcp_v4_save_options(struct sock *sk,
- struct sk_buff *skb)
- {
- struct ip_options *opt = &(IPCB(skb)->opt);//opt指向skb中的cb
- struct ip_options *dopt = NULL;
- if (opt && opt->optlen) {
- int opt_size = optlength(opt);
- dopt = kmalloc(opt_size, GFP_ATOMIC);
- if (dopt) {
- if (ip_options_echo(dopt, skb)) {//处理skb中保存的ip选项
- kfree(dopt);
- dopt = NULL;
- }
- }
- }
- return dopt;
- }
- int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb)
- {
- struct ip_options *sopt;
- unsigned char *sptr, *dptr;
- int soffset, doffset;
- int optlen;
- u32 daddr;
- memset(dopt, 0, sizeof(struct ip_options));
- dopt->is_data = 1;
- sopt = &(IPCB(skb)->opt);//sopt指向skb的cb
- if (sopt->optlen == 0) {
- dopt->optlen = 0;
- return 0;
- }
- sptr = skb->nh.raw;
- dptr = dopt->__data;
- ......
- if (sopt->srr) {//只分析源路由选项,其他如记录路由选项和时间戳选项可类似分析
- unsigned char * start = sptr+sopt->srr;//start指向选项的一个字节
- u32 faddr;
- optlen = start[1];
- soffset = start[2];//此时为目的端,则相对于选项起始位置的偏移量指向了选项的末尾;譬如,客户端192.168.18.71 源路由路径节点192.168.18.72 目的端192.168.18.73,则此时len=7,offset=8(1个ip地址的长度(18.72)+1字节type+1字节len+1字节offset+1字节对齐)
- doffset = 0;
- if (soffset > optlen)
- soffset = optlen + 1;
- soffset -= 4;//偏移量减4,即向左移动一个ip地址长度(4字节)
- if (soffset > 3) {
- memcpy(&faddr, &start[soffset-1], 4);//现在偏移量标识的是倒数第一个ip地址(源路由路径节点中的最后的节点),譬如客户端A,路由节点B,路由节点C,服务器端D,那么此时的faddr为C的ip地址。faddr为我们发送synack的下一跳地址,也为synack的目的地址
- for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4)
- memcpy(&dptr[doffset-1], &start[soffset-1], 4);//将skb中保存的ip选项中的数据段(ip地址)拷贝到dopt->__data字段中,并且以逆序保存,譬如接收到的options中的ip顺序是18.71
- 18.72 18.73,faddr为18.73,那么现在dopt->__data中的是18.72 18.71
- /*
- * RFC1812 requires to fix illegal source routes.
- */
- if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0)
- doffset -= 4;
- }
- if (doffset > 3) {
- memcpy(&start[doffset-1], &daddr, 4);
- dopt->faddr = faddr;//填充dopt的下一跳地址
- dptr[0] = start[0];//填充dopt的type位,和收到的options一致
- dptr[1] = doffset+3;//填充len位,doffset的值为源路由路径ip的个数*4,3为
- len+type+offset 3字节
- dptr[2] = 4;//指向源路由路径首个ip,也就是到达下一跳后,下一跳将选为目的ip地址的路径节点
- dptr += doffset+3;
- dopt->srr = dopt->optlen + sizeof(struct iphdr);//填充srr,相对于ip头的偏移量
- dopt->optlen += doffset+3;
- dopt->is_strictroute = sopt->is_strictroute;//如果接收的options是严格路由,设定
- 严格路由选项
- }
- }
- ......
- return 0;//返回
- }
现在来分析tcp_v4_send_synack
- static int tcp_v4_send_synack(struct sock *sk, struct request_sock *req,
- struct dst_entry *dst)
- {
- const struct inet_request_sock *ireq = inet_rsk(req);
- int err = -1;
- struct sk_buff * skb;
- /* First, grab a route. */
- if (!dst && (dst = inet_csk_route_req(sk, req)) == NULL)//查找路由,构造路由缓存项。目的
- 地址我faddr
- goto out;
- skb = tcp_make_synack(sk, dst, req);//构造skb并填充tcp头
- if (skb) {
- struct tcphdr *th = skb->h.th;
- th->check = tcp_v4_check(th, skb->len,
- ireq->loc_addr,
- ireq->rmt_addr,
- csum_partial((char *)th, skb->len,
- skb->csum));
- err = ip_build_and_send_pkt(skb, sk, ireq->loc_addr,//填充ip头和ip选项;
- ireq->loc_addr为本地地址,ireq->rmt_addr为目的地址,即syn包的源地址
- ireq->rmt_addr,
- ireq->opt);
- if (err == NET_XMIT_CN)
- err = 0;
- }
- out:
- dst_release(dst);
- return err;
- }
- struct dst_entry* inet_csk_route_req(struct sock *sk,
- const struct request_sock *req)
- {
- struct rtable *rt;
- const struct inet_request_sock *ireq = inet_rsk(req);
- struct ip_options *opt = inet_rsk(req)->opt;//tcp_v4_save_options函数将skb保存的
- options拷贝到inet_rsk(req)->opt
- struct flowi fl = { .oif = sk->sk_bound_dev_if,
- .nl_u = { .ip4_u =
- { .daddr = ((opt && opt->srr) ?//如果有源路由选项,则路由的下一跳为选项中的下一
- 跳地址,即options结构的faddr
- opt->faddr :
- ireq->rmt_addr),
- .saddr = ireq->loc_addr,
- .tos = RT_CONN_FLAGS(sk) } },
- .proto = sk->sk_protocol,
- .uli_u = { .ports =
- { .sport = inet_sk(sk)->sport,
- .dport = ireq->rmt_port } } };
- security_req_classify_flow(req, &fl);
- if (ip_route_output_flow(&rt, &fl, sk, 0)) {
- IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
- return NULL;
- }
- if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway) {//如果是严格路由选
- 项,并且路由的下一跳和网关不一致,就返回
- ip_rt_put(rt);
- IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
- return NULL;
- }
- return &rt->u.dst;
- }
现在我们来看ip头的构造
- int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
- u32 saddr, u32 daddr, struct ip_options *opt)
- {
- struct inet_sock *inet = inet_sk(sk);
- struct rtable *rt = (struct rtable *)skb->dst;
- struct iphdr *iph;
- /* Build the IP header. */
- if (opt)
- iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr) + opt->optlen);
- else
- iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr));
- iph->version = 4;
- iph->ihl = 5;
- iph->tos = inet->tos;
- if (ip_dont_fragment(sk, &rt->u.dst))
- iph->frag_off = htons(IP_DF);
- else
- iph->frag_off = 0;
- iph->ttl = ip_select_ttl(inet, &rt->u.dst);
- iph->daddr = rt->rt_dst;//synack包的目的地址为路由的下一跳地址,即options的faddr
- iph->saddr = rt->rt_src;//本地地址
- iph->protocol = sk->sk_protocol;
- iph->tot_len = htons(skb->len);
- ip_select_ident(iph, &rt->u.dst, sk);
- skb->nh.iph = iph;
- if (opt && opt->optlen) {
- iph->ihl += opt->optlen>>2;
- ip_options_build(skb, opt, daddr, rt, 0);//填充ip选项,daddr为syn包的源地址(客户端
- 地址);opt为inet_rsk(req)->opt,为我们保
- 存的syn包的选项
- }
- ip_send_check(iph);
- skb->priority = sk->sk_priority;
- /* Send it out. */
- return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,//dst_output发送
- dst_output);
- }
- void ip_options_build(struct sk_buff * skb, struct ip_options * opt,
- u32 daddr, struct rtable *rt, int is_frag)
- {
- unsigned char * iph = skb->nh.raw;
- memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
- memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
- opt = &(IPCB(skb)->opt);
- opt->is_data = 0;
- if (opt->srr)
- memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);//如果是源路径路由,则将syn包的源地
- 址放到ip选项源路径的尾部
- if (!is_frag) {//如果不是分片
- if (opt->rr_needaddr)//记录路由的地址部分
- ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt);//通过路由缓存获取源地址
- if (opt->ts_needaddr)//时间戳选项的地址部分
- ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt);
- if (opt->ts_needtime) {//时间戳选项的时间部分
- struct timeval tv;
- __u32 midtime;
- do_gettimeofday(&tv);
- midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
- memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
- }
- return;
- }
- if (opt->rr) {//如果是记录路由选项,并且是分片,将这些选项替换成无操作
- memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
- opt->rr = 0;
- opt->rr_needaddr = 0;
- }
- if (opt->ts) {
- memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
- opt->ts = 0;
- opt->ts_needaddr = opt->ts_needtime = 0;
- }
- }
此时ip选项的填充完成,如果我们的环境是这样:客户端(18.71),路由节点1(18.72),路由节点2(18.72),服务器端(18.76);那么在服务器端收到的syn包的选项中的地址顺序是18.71 18.72,iptions_echo后选项中的数据是18.71,则发送的synack的地址顺序是18.71 18.73
下面是实例分析
实验(一)LSRR
客户端:192.168.18.73 路由节点1:192.168.18.71 路由节点2:192.168.18.72 服务器端:192.168.18.76 18.71上18.0网段有个网关192.168.18.79
在服务器端18.76上抓包:
上图为syn包
上图为synack包
如果设置网关不影响数据包的接收转发和发送
实验(二)SSRR
没有设置网关的时候数据包流程正常
如果在18.76上配置18.79的网关,则synack发送不了。
如是,ip选项基本上分析完了
阅读(4526) | 评论(0) | 转发(1) |