Linux内核中的IPSEC兑现(5)
Linux内核中的IPSEC实现(5)
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
7. IPV4下的xfrm支持处理 在xfrm中各种和地址相关的操作是和协议族相关的, 因此这部分的具体实现就放在相关的协议族实现中, 然后通过状态和策略信息结构来指引到实际的操作中,完成对普通数据包的IPSEC包装或对IPSEC包的解封装。 7.1 IPV4下的xfrm策略 IPV4下的xfrm策略在net/ipv4/xfrm4_policy.c文件中定义, 主要是定义IPV4的策略信息结构: static struct xfrm_policy_afinfo xfrm4_policy_afinfo = { .family = AF_INET, .dst_ops = &xfrm4_dst_ops, .dst_lookup = xfrm4_dst_lookup, .get_saddr = xfrm4_get_saddr, .find_bundle = __xfrm4_find_bundle, .bundle_create = __xfrm4_bundle_create, .decode_session = _decode_session4, }; 在xfrm_policy_register_afinfo()函数中, 还定义了struct xfrm_policy_afinfo结构的其他几个成员函数,因为这几个函数是和协议无关的, 所以在登记函数中定义了: afinfo->garbage_collect = __xfrm_garbage_collect; 该函数已经在本系列的第3篇中介绍过了. 以下是结构中几个函数的定义: // IPV4的路由查找, 就是普通是路由查找方法 // 返回0成功 static int xfrm4_dst_lookup(struct xfrm_dst **dst, struct flowi *fl) { return __ip_route_output_key((struct rtable**)dst, fl); } // 查找地址, 这个函数是在通道模式下, 源地址没明确指定时调用的,查找获取 // 外部头中的源地址 static int xfrm4_get_saddr(xfrm_address_t *saddr, xfrm_address_t *daddr) { struct rtable *rt; // 通道的流结构定义,用于查找路由 struct flowi fl_tunnel = { .nl_u = { .ip4_u = { .daddr = daddr->a4, }, }, }; // 根据目的地址找路由 if (!xfrm4_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel)) { // 将找到的路由项中的源地址作为通道模式下的外部源地址 saddr->a4 = rt->rt_src; dst_release(&rt->u.dst); return 0; } return -EHOSTUNREACH; } // 查找策略中的安全路由, 查找条件是流结构的定义的参数 static struct dst_entry * __xfrm4_find_bundle(struct flowi *fl, struct xfrm_policy *policy) { struct dst_entry *dst; read_lock_bh(&policy->lock); // 遍历策略的安全路由链表 for (dst = policy->bundles; dst; dst = dst->next) { struct xfrm_dst *xdst = (struct xfrm_dst*)dst; // 比较网卡位置, 目的地址, 源地址, TOS值是否匹配 // 同时检查该安全路由是否可用 if (xdst->u.rt.fl.oif == fl->oif && /*XXX*/ xdst->u.rt.fl.fl4_dst == fl->fl4_dst && xdst->u.rt.fl.fl4_src == fl->fl4_src && xdst->u.rt.fl.fl4_tos == fl->fl4_tos && xfrm_bundle_ok(policy, xdst, fl, AF_INET, 0)) { dst_clone(dst); break; } } read_unlock_bh(&policy->lock); return dst; } // 解码skb数据, 填充流结构 static void _decode_session4(struct sk_buff *skb, struct flowi *fl) { struct iphdr *iph = skb->nh.iph; // xprth是IP头后的上层协议头起始 u8 *xprth = skb->nh.raw + iph->ihl*4; // 先将流结构清零 memset(fl, 0, sizeof(struct flowi)); // 数据包必须不是分片包 if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) { switch (iph->protocol) { // 对UDP(17), TCP(6), SCTP(132)和DCCP(33)协议, 要提取源端口和目的端口 // 头4字节是源端口和目的端口 case IPPROTO_UDP: case IPPROTO_TCP: case IPPROTO_SCTP: case IPPROTO_DCCP: // 要让skb预留出IP头长度加4字节的长度, 在IP层data应该指向最外面的IP头 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { u16 *ports = (u16 *)xprth; // 提取端口参数 fl->fl_ip_sport = ports[0]; fl->fl_ip_dport = ports[1]; } break; case IPPROTO_ICMP: // 对ICMP(1)协议要提取ICMP包的类型和编码, 2字节 if (pskb_may_pull(skb, xprth + 2 - skb->data)) { u8 *icmp = xprth; fl->fl_icmp_type = icmp[0]; fl->fl_icmp_code = icmp[1]; } break; case IPPROTO_ESP: // 对于ESP(50)协议要提取其中的SPI值, 4字节 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { __be32 *ehdr = (__be32 *)xprth; fl->fl_ipsec_spi = ehdr[0]; } break; case IPPROTO_AH: // 对于AH(51)协议要提取其中的SPI值, 4字节 if (pskb_may_pull(skb, xprth + 8 - skb->data)) { __be32 *ah_hdr = (__be32*)xprth; fl->fl_ipsec_spi = ah_hdr[1]; } break; case IPPROTO_COMP: // 对于COMP(108)协议要提取其中CPI值作为SPI值, 2字节 if (pskb_may_pull(skb, xprth + 4 - skb->data)) { __be16 *ipcomp_hdr = (__be16 *)xprth; fl->fl_ipsec_spi = htonl(ntohs(ipcomp_hdr[1])); } break; default: fl->fl_ipsec_spi = 0; break; }; } // 填充协议,源地址,目的地址, TOS参数 fl->proto = iph->protocol; fl->fl4_dst = iph->daddr; fl->fl4_src = iph->saddr; fl->fl4_tos = iph->tos; } /* Allocate chain of dst_entry's, attach known xfrm's, calculate * all the metrics... Shortly, bundle a bundle. */ // 创建安全路由 static int __xfrm4_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx, struct flowi *fl, struct dst_entry **dst_p) { struct dst_entry *dst, *dst_prev; struct rtable *rt0 = (struct rtable*)(*dst_p); struct rtable *rt = rt0; u32 remote = fl->fl4_dst; u32 local = fl->fl4_src; struct flowi fl_tunnel = { .nl_u = { .ip4_u = { .saddr = local, .daddr = remote, .tos = fl->fl4_tos } } }; int i; int err; int header_len = 0; int trailer_len = 0; dst = dst_prev = NULL; dst_hold(&rt->u.dst); // 循环次数为策略中SA的数量, 每个SA对应一个安全路由, 一个安全路由对应对数据包的一个 // 操作: 如压缩, ESP封装, AH封装等 for (i = 0; i < nx; i++) { // 分配安全路由, 安全路由的操作结构是xfrm4_dst_ops // 因为定义了很多不同类型的路由, 每种路由都有各自的操作结构, 这样在上层可用 // 统一的接口进行路由处理 struct dst_entry *dst1 = dst_alloc(&xfrm4_dst_ops); struct xfrm_dst *xdst; int tunnel = 0; if (unlikely(dst1 == NULL)) { err = -ENOBUFS; dst_release(&rt->u.dst); goto error; } if (!dst) // 第一次循环 dst = dst1; else { // 将新分配的安全路由作为前一个路由的child dst_prev->child = dst1; dst1->flags |= DST_NOHASH; dst_clone(dst1); } xdst = (struct xfrm_dst *)dst1; // 安全路由中保留相应的普通路由 xdst->route = &rt->u.dst; xdst->genid = xfrm[i]->genid; // 新节点的next是老节点 dst1->next = dst_prev; // 现在prev节点位新节点 dst_prev = dst1; if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) { remote = xfrm[i]->id.daddr.a4; local = xfrm[i]->props.saddr.a4; tunnel = 1; } header_len += xfrm[i]->props.header_len; trailer_len += xfrm[i]->props.trailer_len; // 如果是通道模式, 需要重新包裹外部IP头, 需要重新寻找外部IP头的路由 if (tunnel) { fl_tunnel.fl4_src = local; fl_tunnel.fl4_dst = remote; err = xfrm_dst_lookup((struct xfrm_dst **)&rt, &fl_tunnel, AF_INET); if (err) goto error; } else dst_hold(&rt->u.dst); } // 将最新节点的child指向最后的普通路由 dst_prev->child = &rt->u.dst; // 最老一个安全路由的path指向最后的普通路由 dst->path = &rt->u.dst; // 将最老安全路由点作为要返回的路由节点链表头 *dst_p = dst; // dst现在是最新节点 dst = dst_prev; // prev现在指向最老安全节点 dst_prev = *dst_p; i = 0; /* 为更好理解上面的操作, 用图来表示. 以上循环形成了下图水平方向的一个链表, 链表中的最左边的路由项节点dst为最老的安全路由项, 新分配的安全路由项通过child链接成链表, child通过next指向老节点, 最后一项是数据包封装完后的最后普通路由项. 垂直方向的链表是在xfrm_lookup()中形成的, 是多个策略同时起作用的情况, 一般情况下就只有一个策略, 本文中可不考虑多策略的情况. rt0.u.dst rt.u.dst rt.u.dst ^ ^ ^ route | route | route | | child | child | bundle +-----+ -----> +-----+ -----> +-----+ child policy -------> | dst | <----- | dst | <----- ... | dst | -----> rt.u.dst +-----+ next +-----+ next +-----+ | |next | V child child +-----+ -----> +-----+ -----> +-----+ child | dst | <----- | dst | <----- ... | dst | -----> rt.u.dst +-----+ next +-----+ next +-----+ | |next | V .... */ // 对新生成的每个安全路由项填充结构参数 for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) { struct xfrm_dst *x = (struct xfrm_dst*)dst_prev; x->u.rt.fl = *fl; dst_prev->xfrm = xfrm[i++]; dst_prev->dev = rt->u.dst.dev; if (rt->u.dst.dev) dev_hold(rt->u.dst.dev); dst_prev->obsolete = -1; dst_prev->flags |= DST_HOST; dst_prev->lastuse = jiffies; dst_prev->header_len = header_len; dst_prev->nfheader_len = 0; dst_prev->trailer_len = trailer_len; memcpy(&dst_prev->metrics, &x->route->metrics, sizeof(dst_prev->metrics)); /* Copy neighbout for reachability confirmation */ dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour); dst_prev->input = rt->u.dst.input; // 注意安全路由的输出函数是xfrm4_output, 在以后分析路由过程时要用到 dst_prev->output = xfrm4_output; if (rt->peer) atomic_inc(&rt->peer->refcnt); x->u.rt.peer = rt->peer; /* Sheit... I remember I did this right. Apparently, * it was magically lost, so this code needs audit */ x->u.rt.rt_flags = rt0->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL); x->u.rt.rt_type = rt->rt_type; x->u.rt.rt_src = rt0->rt_src; x->u.rt.rt_dst = rt0->rt_dst; x->u.rt.rt_gateway = rt->rt_gateway; x->u.rt.rt_spec_dst = rt0->rt_spec_dst; x->u.rt.idev = rt0->idev; in_dev_hold(rt0->idev); header_len -= x->u.dst.xfrm->props.header_len; trailer_len -= x->u.dst.xfrm->props.trailer_len; } // 初始化路由项的MTU值 xfrm_init_pmtu(dst); return 0; error: if (dst) dst_free(dst); return err; } 7.1. 小结 IPV4的策略信息结构中的相关成员函数的被调用关系可如下简单表示: xfrm_lookup: find xfrm_dst for the skb, create dst_list -> xfrm_find_bundle -> afinfo->find_bundle() == __xfrm4_find_bundle -> xfrm_tmpl_resolve -> xfrm_tmpl_resolve_one -> xfrm_get_saddr -> afinfo->get_saddr == xfrm4_get_saddr -> xfrm4_dst_lookup -> xfrm_bundle_create -> afinfo->bundle_create() == __xfrm4_bundle_create -> xfrm_dst_lookup() -> afinfo->dst_lookup() == xfrm4_dst_lookup xfrm4_policy_check -> xfrm_policy_check -> __xfrm_policy_check -> xfrm_decode_session -> afinfo->decode_session() == _decode_session4 7.2 IPV4安全路由操作 路由操作是针对每种类型的路由定义的一个操作结构, 对上层隐藏了不同路由处理内部的处理方法, 对于IPSEC的IPV4安全路由(xfrm_dst)的操作结构定义如下: /* net/ipv4/xfrm4_policy.c */ static struct dst_ops xfrm4_dst_ops = { .family = AF_INET, .protocol = __constant_htons(ETH_P_IP), .gc = xfrm4_garbage_collect, .update_pmtu = xfrm4_update_pmtu, .destroy = xfrm4_dst_destroy, .ifdown = xfrm4_dst_ifdown, .gc_thresh = 1024, .entry_size = sizeof(struct xfrm_dst), }; 在xfrm_policy_register_afinfo()函数中, 还定义了安全路由操作结构的其他几个成员函数,因为这几个函数是和协议无关的, 所以在登记函数中定义了: dst_ops->kmem_cachep = xfrm_dst_cache; dst_ops->check = xfrm_dst_check; dst_ops->negative_advice = xfrm_negative_advice; dst_ops->link_failure = xfrm_link_failure; // 安全路由垃圾搜集, 就是调用安全策略信息结构的垃圾搜集函数 static inline int xfrm4_garbage_collect(void) { xfrm4_policy_afinfo.garbage_collect(); return (atomic_read(&xfrm4_dst_ops.entries) > xfrm4_dst_ops.gc_thresh*2); } // 更新路由的MTU static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu) { struct xfrm_dst *xdst = (struct xfrm_dst *)dst; struct dst_entry *path = xdst->route; // 调用的是安全路由的原始普通路由的MTU更新操作 path->ops->update_pmtu(path, mtu); } // 释放安全路由 static void xfrm4_dst_destroy(struct dst_entry *dst) { struct xfrm_dst *xdst = (struct xfrm_dst *)dst; // 释放inet网卡引用 if (likely(xdst->u.rt.idev)) in_dev_put(xdst->u.rt.idev); // 释放对方IP的引用 if (likely(xdst->u.rt.peer)) inet_putpeer(xdst->u.rt.peer); // 释放安全路由 xfrm_dst_destroy(xdst); } static inline void xfrm_dst_destroy(struct xfrm_dst *xdst) { // 释放和安全路由相关的普通路由 dst_release(xdst->route); // 释放SA if (likely(xdst->u.dst.xfrm)) xfrm_state_put(xdst->u.dst.xfrm); } // 网卡down时的回调操作 static void xfrm4_dst_ifdown(struct dst_entry *dst, struct net_device *dev, int unregister) { struct xfrm_dst *xdst; if (!unregister) return; xdst = (struct xfrm_dst *)dst; // 该安全路由对应的网卡是当前停掉的网卡 if (xdst->u.rt.idev->dev == dev) { struct in_device *loopback_idev = in_dev_get(&loopback_dev); BUG_ON(!loopback_idev); do { // 释放安全路由网卡 in_dev_put(xdst->u.rt.idev); // 安全路由网卡采用自身的回环网卡 xdst->u.rt.idev = loopback_idev; in_dev_hold(loopback_idev); // 子路由 xdst = (struct xfrm_dst *)xdst->u.dst.child; } while (xdst->u.dst.xfrm); __in_dev_put(loopback_idev); } xfrm_dst_ifdown(dst, dev); } 7.3 IPV4下的xfrm状态 IPV4下的xfrm状态在net/ipv4/xfrm4_state.c文件中定义, 主要是定义IPV4的状态信息结构: static struct xfrm_state_afinfo xfrm4_state_afinfo = { .family = AF_INET, .init_flags = xfrm4_init_flags, .init_tempsel = __xfrm4_init_tempsel, }; 该结构中在IPV4下只定义了两个处理函数: // 初始化状态标志 static int xfrm4_init_flags(struct xfrm_state *x) { if (ipv4_config.no_pmtu_disc) x->props.flags |= XFRM_STATE_NOPMTUDISC; return 0; } // 初始化模板选择子 static void __xfrm4_init_tempsel(struct xfrm_state *x, struct flowi *fl, struct xfrm_tmpl *tmpl, xfrm_address_t *daddr, xfrm_address_t *saddr) { // 填写选择子信息 // 源地址 x->sel.daddr.a4 = fl->fl4_dst; // 目的地址 x->sel.saddr.a4 = fl->fl4_src; // 目的端口, 掩码 x->sel.dport = xfrm_flowi_dport(fl); x->sel.dport_mask = htons(0xffff); // 源端口掩码 x->sel.sport = xfrm_flowi_sport(fl); x->sel.sport_mask = htons(0xffff); // 源目的地址长度 x->sel.prefixlen_d = 32; x->sel.prefixlen_s = 32; // 协议 x->sel.proto = fl->proto; // 网卡位置 x->sel.ifindex = fl->oif; // 状态ID值 x->id = tmpl->id; if (x->id.daddr.a4 == 0) x->id.daddr.a4 = daddr->a4; // 支持结构中的参数 // 源地址 x->props.saddr = tmpl->saddr; if (x->props.saddr.a4 == 0) x->props.saddr.a4 = saddr->a4; // 模式 x->props.mode = tmpl->mode; // 请求ID x->props.reqid = tmpl->reqid; // 协议族 x->props.family = AF_INET; } 7.3小结 IPV4的状态信息结构中的相关成员函数的被调用关系可如下简单表示: xfrm_init_state() -> afinfo->init_flags() == xfrm4_init_flags xfrm_state_find() -> xfrm_init_tempsel() -> afinfo->init_tempsel() == __xfrm4_init_tempsel 7.4 模式 xfrm4支持3种模式: 通道, 传输和BEET模式, 分别在xfrm4_mode_tunnel.c, xfrm4_mode_transport.c和xfrm4_mode_beet.c中定义. 每个模式都通过结构struct xfrm_mode定义: struct xfrm_mode { int (*input)(struct xfrm_state *x, struct sk_buff *skb); int (*output)(struct xfrm_state *x,struct sk_buff *skb); struct module *owner; unsigned int encap; }; 其中input函数在数据接收时调用, output函数数据发出时调用, encap参数表示是否封装. 7.4.1 通道 通道模式通过以下结构定义: /* net/ipv4/xfrm4_mode_transport.c */ static struct xfrm_mode xfrm4_tunnel_mode = { .input = xfrm4_tunnel_input, .output = xfrm4_tunnel_output, .owner = THIS_MODULE, .encap = XFRM_MODE_TUNNEL, }; // 通道模式下的接收函数, 解封装 static int xfrm4_tunnel_input(struct xfrm_state *x, struct sk_buff *skb) { struct iphdr *iph = skb->nh.iph; int err = -EINVAL; // IP协议为IPPROTO_IPIP(4) if (iph->protocol != IPPROTO_IPIP) goto out; // 需要在skb头留出IP头的长度(20字节) if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto out; // 如果是clone包,重新拷贝一个 if (skb_cloned(skb) && (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC))) goto out; // 复制dscp字段 if (x->props.flags & XFRM_STATE_DECAP_DSCP) ipv4_copy_dscp(iph, skb->h.ipiph); // 非XFRM_STATE_NOECN时进行ECN解封装 if (!(x->props.flags & XFRM_STATE_NOECN)) ipip_ecn_decapsulate(skb); // 将硬件地址挪到数据包缓冲区前 skb->mac.raw = memmove(skb->data - skb->mac_len, skb->mac.raw, skb->mac_len); // 网络部分数据头 skb->nh.raw = skb->data; err = 0; out: return err; } // 通道模式下的数据发出函数, 进行封装 static int xfrm4_tunnel_output(struct xfrm_state *x, struct sk_buff *skb) { struct dst_entry *dst = skb->dst; struct iphdr *iph, *top_iph; int flags; iph = skb->nh.iph; skb->h.ipiph = iph; // 数据头部增加外部IP头的长度 skb->nh.raw = skb_push(skb, x->props.header_len); top_iph = skb->nh.iph; // 填写外部IP头参数 top_iph->ihl = 5; top_iph->version = 4; /* DS disclosed */ // 重新计算TOS top_iph->tos = INET_ECN_encapsulate(iph->tos, iph->tos); flags = x->props.flags; if (flags & XFRM_STATE_NOECN) IP_ECN_clear(top_iph); // 处理分片包情况 top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) ? 0 : (iph->frag_off & htons(IP_DF)); if (!top_iph->frag_off) __ip_select_ident(top_iph, dst->child, 0); // TTL top_iph->ttl = dst_metric(dst->child, RTAX_HOPLIMIT); // 外部源地址用proposal中的源地址 top_iph->saddr = x->props.saddr.a4; // 外部目的地址是SA中的目的地址 top_iph->daddr = x->id.daddr.a4; // 外部IP头内的协议号为IPIP(4) top_iph->protocol = IPPROTO_IPIP; // IP选项部分设置为0 memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options)); return 0; } 7.4.2 传输 传输模式下不添加新的IP头, 其实几乎什么都不用做, 老点的2.6内核中就没有专门为传输模式定义. 传输模式结构定义为: /* net/ipv4/xfrm4_mode_transport.c */ static struct xfrm_mode xfrm4_transport_mode = { .input = xfrm4_transport_input, .output = xfrm4_transport_output, .owner = THIS_MODULE, .encap = XFRM_MODE_TRANSPORT, }; /* Remove encapsulation header. * * The IP header will be moved over the top of the encapsulation header. * * On entry, skb->h shall point to where the IP header should be and skb->nh * shall be set to where the IP header currently is. skb->data shall point * to the start of the payload. */ // 传输模式下的数据输入函数 static int xfrm4_transport_input(struct xfrm_state *x, struct sk_buff *skb) { // data指向负载头, h指向IP头, 但很多情况下两者相同 int ihl = skb->data - skb->h.raw; // 如果h和nh不同, 将nh所指向IP头部分移动到h处 if (skb->h.raw != skb->nh.raw) skb->nh.raw = memmove(skb->h.raw, skb->nh.raw, ihl); // 增加数据包长度, 重新对数据包长度赋值 skb->nh.iph->tot_len = htons(skb->len + ihl); skb->h.raw = skb->data; return 0; } /* Add encapsulation header. * * The IP header will be moved forward to make space for the encapsulation * header. * * On exit, skb->h will be set to the start of the payload to be processed * by x->type->output and skb->nh will be set to the top IP header. */ // 传输模式下的数据发出函数 static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb) { struct iphdr *iph; int ihl; // nh和赋值给h iph = skb->nh.iph; skb->h.ipiph = iph; // ip头长度 ihl = iph->ihl * 4; // 重新计算h位置 skb->h.raw += ihl; // 重新计算新的nh位置,增加proposal中的头长度, 拷贝原来的IP头数据 skb->nh.raw = memmove(skb_push(skb, x->props.header_len), iph, ihl); return 0; } 7.4.3 BEET 封装成BEETPH(94)包, 非标准IPSEC, 略. 7.4.4 小结 和xfrm_mode相关的xfrm函数有: 登记: int xfrm_register_mode(struct xfrm_mode *mode, int family); 撤销: int xfrm_unregister_mode(struct xfrm_mode *mode, int family) 获取: struct xfrm_mode *xfrm_get_mode(unsigned int encap, int family) 释放: void xfrm_put_mode(struct xfrm_mode *mode) xfrm_mode的输入输出函数调用: xfrm4_rcv_encap() -> x->mode->input xfrm4_output_one() -> x->mode->output 7.5 数据接收 IPV4的IPSEC数据接收处理在net/ipv4/xfrm4_input.c中定义, 作为AH和ESP协议数据接收处理函数. /* net/ipv4/xfrm4_input.c */ int xfrm4_rcv(struct sk_buff *skb) { return xfrm4_rcv_encap(skb, 0); } 实际就是xfrm4_rcv_encap,封装类型参数设置为0,在NAT-T时IPSEC数据被封装在UDP包中时, 该参数才非0. int xfrm4_rcv_encap(struct sk_buff *skb, __u16 encap_type) { int err; __be32 spi, seq; struct xfrm_state *xfrm_vec[XFRM_MAX_DEPTH]; struct xfrm_state *x; int xfrm_nr = 0; int decaps = 0; // 获取skb中的spi和序列号信息 if ((err = xfrm4_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) != 0) goto drop; // 进入循环进行解包操作 do { struct iphdr *iph = skb->nh.iph; // 循环解包次数太深的话放弃 if (xfrm_nr == XFRM_MAX_DEPTH) goto drop; // 根据地址, SPI和协议查找SA x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, iph->protocol, AF_INET); if (x == NULL) goto drop; // 以下根据SA定义的操作对数据解码 spin_lock(&x->lock); if (unlikely(x->km.state != XFRM_STATE_VALID)) goto drop_unlock; // 检查由SA指定的封装类型是否和函数指定的封装类型相同 if ((x->encap ? x->encap->encap_type : 0) != encap_type) goto drop_unlock; // SA重放窗口检查 if (x->props.replay_window && xfrm_replay_check(x, seq)) goto drop_unlock; // SA生存期检查 if (xfrm_state_check_expire(x)) goto drop_unlock; // type可为esp,ah,ipcomp, ipip等, 对输入数据解密 if (x->type->input(x, skb)) goto drop_unlock; /* only the first xfrm gets the encap type */ encap_type = 0; // 更新重放窗口 if (x->props.replay_window) xfrm_replay_advance(x, seq); // 包数,字节数统计 x->curlft.bytes += skb->len; x->curlft.packets++; spin_unlock(&x->lock); // 保存数据解封用的SA, 增加SA数量计数 xfrm_vec[xfrm_nr++] = x; // mode可为通道,传输等模式, 对输入数据解封装 if (x->mode->input(x, skb)) goto drop; // 如果是IPSEC通道模式,将decaps参数置1,否则表示是传输模式 if (x->props.mode == XFRM_MODE_TUNNEL) { decaps = 1; break; } // 看内层协议是否还要继续解包, 不需要解时返回1, 需要解时返回0, 错误返回负数 // 协议类型可以多层封装的,比如用AH封装ESP, 就得先解完AH再解ESP if ((err = xfrm_parse_spi(skb, skb->nh.iph->protocol, &spi, &seq)) < 0) goto drop; } while (!err); /* Allocate new secpath or COW existing one. */ // 为skb包建立新的安全路径(struct sec_path) if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) { struct sec_path *sp; sp = secpath_dup(skb->sp); if (!sp) goto drop; if (skb->sp) secpath_put(skb->sp); skb->sp = sp; } if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH) goto drop; // 将刚才循环解包用到的SA拷贝到安全路径 // 因此检查一个数据包是否是普通明文包还是解密后的明文包就看skb->sp参数是否为空 memcpy(skb->sp->xvec + skb->sp->len, xfrm_vec, xfrm_nr * sizeof(xfrm_vec[0])); skb->sp->len += xfrm_nr; nf_reset(skb); if (decaps) { // 通道模式 if (!(skb->dev->flags&IFF_LOOPBACK)) { dst_release(skb->dst); skb->dst = NULL; } // 重新进入网卡接收函数 netif_rx(skb); return 0; } else { // 传输模式 #ifdef CONFIG_NETFILTER // 如果定义NETFILTER, 进入PRE_ROUTING链处理,然后进入路由选择处理 // 其实现在已经处于INPUT点, 但解码后需要将该包作为一个新包看待 // 可能需要进行目的NAT操作, 这时候可能目的地址就会改变不是到自身 // 的了, 因此需要将其相当于是放回PRE_PROUTING点去操作, 重新找路由 // 这也说明可以制定针对解码后明文包的NAT规则,在还是加密包的时候不匹配 // 但解码后能匹配上 __skb_push(skb, skb->data - skb->nh.raw); skb->nh.iph->tot_len = htons(skb->len); ip_send_check(skb->nh.iph); NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL, xfrm4_rcv_encap_finish); return 0; #else // 内核不支持NETFILTER, 该包肯定就是到自身的了 // 返回IP协议的负值, 表示重新进行IP层协议的处理 // 用解码后的内层协议来处理数据 return -skb->nh.iph->protocol; #endif } drop_unlock: spin_unlock(&x->lock); xfrm_state_put(x); drop: while (--xfrm_nr >= 0) xfrm_state_put(xfrm_vec[xfrm_nr]); kfree_skb(skb); return 0; } // 解析AH,ESP数据包中的SPI和序号 static int xfrm4_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq) { switch (nexthdr) { // 如果只是普通的IPIP包, SPI为源地址, 序号位0 case IPPROTO_IPIP: *spi = skb->nh.iph->saddr; *seq = 0; return 0; } // 否则解析AH/ESP/COMP协议头中的SPI和序号 return xfrm_parse_spi(skb, nexthdr, spi, seq); } // 接收封装完成处理函数 static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb) { struct iphdr *iph = skb->nh.iph; // 如果没有路由, 重新查找路由 if (skb->dst == NULL) { if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev)) goto drop; } // 调用相关的路由输入函数 return dst_input(skb); drop: kfree_skb(skb); return NET_RX_DROP; } 调用关系: ip_rcv -> (AH/ESP) net_protocol->handler == xfrm4_rcv -> xfrm4_rcv_encap -> xfrm4_parse_spi -> xfrm_parse_spi -> xfrm4_rcv_encap_finish 7.6 数据发送 IPV4的IPSEC数据发送处理在net/ipv4/xfrm4_output.c中定义,作为安全路由的输出函数: int xfrm4_output(struct sk_buff *skb) { // 就是一个条件HOOK, 当skb包不带IPSKB_REROUTED标志时进入POSTROUTING点的NAT操作 // 这是数据在xfrm策略中多个bundle时会多次调用, 也就是数据在封装完成前可以进行 // 源NAT操作 // HOOK出口函数为xfrm4_output_finish return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, xfrm4_output_finish, !(IPCB(skb)->flags & IPSKB_REROUTED)); } // 发送结束处理 static int xfrm4_output_finish(struct sk_buff *skb) { struct sk_buff *segs; #ifdef CONFIG_NETFILTER // 如果内核定义了NETFILTER, 当到达最后一个路由(普通路由)时, 设置IPSKB_REROUTED // 标志, 进行普通路由发出函数(ip_output), 设置该标志后不进行源NAT操作 if (!skb->dst->xfrm) { IPCB(skb)->flags |= IPSKB_REROUTED; return dst_output(skb); } #endif // 如果skb包不是是gso, 转xfrm4_output_finish2 // gso是什么意思现在还不知道, 以后再仔细分析 if (!skb_is_gso(skb)) return xfrm4_output_finish2(skb); // 处理gso数据包, 最终也是使用xfrm4_output_finish2处理数据包 skb->protocol = htons(ETH_P_IP); segs = skb_gso_segment(skb, 0); kfree_skb(skb); if (unlikely(IS_ERR(segs))) return PTR_ERR(segs); do { struct sk_buff *nskb = segs->next; int err; segs->next = NULL; err = xfrm4_output_finish2(segs); if (unlikely(err)) { while ((segs = nskb)) { nskb = segs->next; segs->next = NULL; kfree_skb(segs); } return err; } segs = nskb; } while (segs); return 0; } // 第2级发送结束处理 static int xfrm4_output_finish2(struct sk_buff *skb) { int err; // 根据安全路由包装要发送数据 while (likely((err = xfrm4_output_one(skb)) == 0)) { // 处理成功 // 释放skb中的netfilter信息 nf_reset(skb); // 重新将该包作为初始发送包, 进入OUTPUT点处理, 注意这是个函数而不是宏 // 如果内核没定义NETFILTER, 该函数只是个空函数 // 返回1表示NF_ACCEPT err = nf_hook(PF_INET, NF_IP_LOCAL_OUT, &skb, NULL, skb->dst->dev, dst_output); if (unlikely(err != 1)) break; // 如果已经没有SA, 就只是个普通包了, 路由发送(ip_output)返回, 退出循环 if (!skb->dst->xfrm) return dst_output(skb); // 如果还有SA, 目前还只是中间状态, 还可以进行SNAT操作, 进入POSTROUTING点处理 err = nf_hook(PF_INET, NF_IP_POST_ROUTING, &skb, NULL, skb->dst->dev, xfrm4_output_finish2); if (unlikely(err != 1)) break; } return err; } // 按安全路由链表的安全路由处理数据, 该链表反映了多个SA对数据包进行处理 // 链表是在__xfrm4_bundle_create函数中建立的 static int xfrm4_output_one(struct sk_buff *skb) { // 安全路由 struct dst_entry *dst = skb->dst; // 相关SA struct xfrm_state *x = dst->xfrm; int err; // skb包校验和 检查 if (skb->ip_summed == CHECKSUM_PARTIAL) { err = skb_checksum_help(skb); if (err) goto error_nolock; } // 如果是通道模式, 检查skb数据长度, 并进行相关处理, 通道模式下封装后的数据包长度可能 // 会超过1500字节的 if (x->props.mode == XFRM_MODE_TUNNEL) { err = xfrm4_tunnel_check_size(skb); if (err) goto error_nolock; } do { spin_lock_bh(&x->lock); // SA合法性检查 err = xfrm_state_check(x, skb); if (err) goto error; // 调用模式输出函数, 如通道封装, 此时外部IP头协议为IPIP err = x->mode->output(x, skb); if (err) goto error; // 调用协议输出, 如对应ESP协议来说是esp4_output, 此时外部IP头协议会改为ESP err = x->type->output(x, skb); if (err) goto error; // 更新SA中的当前生命期结构中的包和字节计数 x->curlft.bytes += skb->len; x->curlft.packets++; spin_unlock_bh(&x->lock); // 转移到下一个子路由 if (!(skb->dst = dst_pop(dst))) { err = -EHOSTUNREACH; goto error_nolock; } // dst和x参数更新为子路由中的安全路由和SA dst = skb->dst; x = dst->xfrm; // 循环条件是SA非空, 而且SA提议模式不是通道模式 } while (x && (x->props.mode != XFRM_MODE_TUNNEL)); // skb中设置IPSKB_XFRM_TRANSFORMED标志 // 有该标志的数据包将NAT操作后将不进行一些特殊检查 IPCB(skb)->flags |= IPSKB_XFRM_TRANSFORMED; err = 0; out_exit: return err; error: spin_unlock_bh(&x->lock); error_nolock: kfree_skb(skb); goto out_exit; } IPSEC输出函数调用关系: dst_output -> xfrm_dst->output == xfrm4_output -> NF_HOOK(POSTROUTING) -> xfrm4_output_finish -> xfrm4_output_finish2 -> xfrm4_output_one 7.7 NAT-T支持 在支持NAT穿越的IPSEC处理中,是通过UDP数据包来封装IPSEC数据(ESP数据包),因此在对UDP处理时需要进行特殊处理。由于IKE同样是用UDP处理的, 区分是IKE包还是封装的ESP包就看数据头部头4字节表示的SPI值, SPI为0表示是IKE包, 由IKE用户空间程序接收进行处理, SPI非0表示是UDP封装的ESP包, 需进行ESP解封。 7.7.1 接收数据 被UDP封装的IPSEC包在接收时会先按普通UDP包接收,在UDP处理中再解开该包后进行IPSEC处理 /* net/ipv4/udp.c */ // 正常接收的UDP包都将进入该函数 static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb) { struct udp_sock *up = udp_sk(sk); int rc; /* * Charge it to the socket, dropping if the queue is full. */ // 检查针对该sock,skb包的输入方法上的是否有安全策略 if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) { kfree_skb(skb); return -1; } nf_reset(skb); // 检查该SOCK是否是IPSEC封装的,该参数通过setsockopt系统调用的UDP_ENCAP选项设置 // 一般是IKE程序在打开UDP4500端口时设置的 if (up->encap_type) { /* * This is an encapsulation socket, so let's see if this is * an encapsulated packet. * If it's a keepalive packet, then just eat it. * If it's an encapsulateed packet, then pass it to the * IPsec xfrm input and return the response * appropriately. Otherwise, just fall through and * pass this up the UDP socket. */ int ret; // 进入UDP封装接收, 判断是否是ESP包 // 返回值小于0表示是IPSEC包, 大于0表示是普通UDP包, 等于0表示是错误包 ret = udp_encap_rcv(sk, skb); if (ret == 0) { /* Eat the packet .. */ kfree_skb(skb); return 0; } if (ret < 0) { // 进行IPSEC接收处理 /* process the ESP packet */ ret = xfrm4_rcv_encap(skb, up->encap_type); UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS); return -ret; } /* FALLTHROUGH -- it's a UDP Packet */ } // 以下按普通UDP包接收处理, 分析略 if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) { if (__udp_checksum_complete(skb)) { UDP_INC_STATS_BH(UDP_MIB_INERRORS); kfree_skb(skb); return -1; } skb->ip_summed = CHECKSUM_UNNECESSARY; } if ((rc = sock_queue_rcv_skb(sk,skb)) < 0) { /* Note that an ENOMEM error is charged twice */ if (rc == -ENOMEM) UDP_INC_STATS_BH(UDP_MIB_RCVBUFERRORS); UDP_INC_STATS_BH(UDP_MIB_INERRORS); kfree_skb(skb); return -1; } UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS); return 0; } /* return: * 1 if the the UDP system should process it * 0 if we should drop this packet * -1 if it should get processed by xfrm4_rcv_encap */ static int udp_encap_rcv(struct sock * sk, struct sk_buff *skb) { #ifndef CONFIG_XFRM // 在内核不支持IPSEC情况下直接返回1 return 1; #else struct udp_sock *up = udp_sk(sk); struct udphdr *uh; struct iphdr *iph; int iphlen, len; __u8 *udpdata; __be32 *udpdata32; // sock的封装标志值 __u16 encap_type = up->encap_type; /* if we're overly short, let UDP handle it */ // UDP数据包中数据部分的长度 len = skb->len - sizeof(struct udphdr); if (len <= 0) return 1; /* if this is not encapsulated socket, then just return now */ // 没定义封装处理, 返回1, 普通处理 if (!encap_type) return 1; /* If this is a paged skb, make sure we pull up * whatever data we need to look at. */ if (!pskb_may_pull(skb, sizeof(struct udphdr) + min(len, 8))) return 1; /* Now we can get the pointers */ uh = skb->h.uh; udpdata = (__u8 *)uh + sizeof(struct udphdr); udpdata32 = (__be32 *)udpdata; switch (encap_type) { default: // 在UDP中封装ESP case UDP_ENCAP_ESPINUDP: /* Check if this is a keepalive packet. If so, eat it. */ if (len == 1 && udpdata[0] == 0xff) { // 只是普通UDP的IPSEC通道保活包, 直接丢弃 return 0; } else if (len > sizeof(struct ip_esp_hdr) && udpdata32[0] != 0 ) { // 头4字节非零, ESP包,需要下一步解析 /* ESP Packet without Non-ESP header */ len = sizeof(struct udphdr); } else // 这是IKE包,按普通UDP接收处理 /* Must be an IKE packet.. pass it through */ return 1; break; case UDP_ENCAP_ESPINUDP_NON_IKE: /* Check if this is a keepalive packet. If so, eat it. */ if (len == 1 && udpdata[0] == 0xff) { // IPSEC通道保活包, 丢弃 return 0; } else if (len > 2 * sizeof(u32) + sizeof(struct ip_esp_hdr) && udpdata32[0] == 0 && udpdata32[1] == 0) { // 头4字节非零, ESP包,需要下一步解析 /* ESP Packet with Non-IKE marker */ len = sizeof(struct udphdr) + 2 * sizeof(u32); } else // 这是IKE数据包,由 /* Must be an IKE packet.. pass it through */ return 1; break; } /* At this point we are sure that this is an ESPinUDP packet, * so we need to remove 'len' bytes from the packet (the UDP * header and optional ESP marker bytes) and then modify the * protocol to ESP, and then call into the transform receiver. */ // 如果是clone包需要复制成独立包 if (skb_cloned(skb) && pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) return 0; // 检查数据长度 /* Now we can update and verify the packet length... */ iph = skb->nh.iph; iphlen = iph->ihl << 2; iph->tot_len = htons(ntohs(iph->tot_len) - len); if (skb->len < iphlen + len) { /* packet is too small!?! */ return 0; } /* pull the data buffer up to the ESP header and set the * transport header to point to ESP. Keep UDP on the stack * for later. */ // 修改IP上层头位置 skb->h.raw = skb_pull(skb, len); // 更改IP头协议类型为ESP包, 返回-1 /* modify the protocol (it's ESP!) */ iph->protocol = IPPROTO_ESP; /* and let the caller know to send this into the ESP processor... */ return -1; #endif } 函数调用关系: udp_rcv ->udp_queue_rcv_skb -> udp_encap_rcv -> xfrm4_policy_check -> xfrm_policy_check -> __xfrm_policy_check 7.7.2 ESP包的UDP封装 对于ESP包的UDP封装处理, 在下一节ESP协议数据包的输出处理中介绍. ...... 待续 ......