(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选项基本上分析完了