ip dontfrag linux,linux ip选项处理(三)

(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上抓包:

493fcff18e46d1e28451f2ae4d684d50.png

上图为syn包

1354626bd70e5e955e1fe097cda7f431.png

上图为synack包

如果设置网关不影响数据包的接收转发和发送

实验(二)SSRR

没有设置网关的时候数据包流程正常

如果在18.76上配置18.79的网关,则synack发送不了。

如是,ip选项基本上分析完了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值