Linux核心VPN實現源碼分析(三)

來源:互聯網
上載者:User

     上篇簡單介紹了一下ipip模組和隧道的初始化相關內容,現在開始轉入正題,想些介紹ipip協議收發包的過程。

     在講解收發包之前,有一個問題值得注意,就是mtu的問題,由於資料包經過ipip協議後,會在原始的長度上加一個ipip包頭的長度,所以使用ipip協議的mtu必須減少一個ipip包頭的長度,代碼如下。

     static int ipip_tunnel_change_mtu(struct net_device *dev, int new_mtu)<br />{<br />if (new_mtu < 68 || new_mtu > 0xFFF8 - sizeof(struct iphdr))<br />return -EINVAL;<br />dev->mtu = new_mtu;<br />return 0;<br />}

    new_mtu < 68是因為mtu最小長度為60位元組,小於60位元組的鏈路層包後面需要用0填充。

    接收過程函數,過程比較清晰簡單。

    static int ipip_rcv(struct sk_buff *skb)<br />{<br />struct ip_tunnel *tunnel;<br />const struct iphdr *iph = ip_hdr(skb);<br />read_lock(&ipip_lock);<br />if ((tunnel = ipip_tunnel_lookup(dev_net(skb->dev),<br />iph->saddr, iph->daddr)) != NULL) {<br />if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {<br />read_unlock(&ipip_lock);<br />kfree_skb(skb);<br />return 0;<br />}<br />secpath_reset(skb);<br />skb->mac_header = skb->network_header;<br />skb_reset_network_header(skb);<br />skb->protocol = htons(ETH_P_IP);<br />skb->pkt_type = PACKET_HOST;<br />tunnel->dev->stats.rx_packets++;<br />tunnel->dev->stats.rx_bytes += skb->len;<br />skb->dev = tunnel->dev;<br />skb_dst_drop(skb);<br />nf_reset(skb);<br />ipip_ecn_decapsulate(iph, skb);<br />netif_rx(skb);<br />read_unlock(&ipip_lock);<br />return 0;<br />}<br />read_unlock(&ipip_lock);<br />return -1;<br />}<br />/
 

    首先是利用ipip_tunnel_lookup函數根據源目的地址等資訊尋找所屬隧道,如果查不到就bypass過去。然後就是剝去IPIP頭,但IPIP並未真正剝去,只是調用skb->mac_header = skb->network_header;skb_reset_network_header(skb);“邏輯”上剝去了,剝去後設定成普通的IP協議,接下來調用netif_rx函數提交給上層的IP層處理。至此接收過程結束。

   
尋找隧道的函數ipip_tunnel_lookup
函數實現如下:

    static struct ip_tunnel * ipip_tunnel_lookup(struct net *net,<br />__be32 remote, __be32 local)<br />{<br />unsigned h0 = HASH(remote);<br />unsigned h1 = HASH(local);<br />struct ip_tunnel *t;<br />struct ipip_net *ipn = net_generic(net, ipip_net_id);<br />for (t = ipn->tunnels_r_l[h0^h1]; t; t = t->next) {<br />if (local == t->parms.iph.saddr &&<br /> remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))<br />return t;<br />}<br />for (t = ipn->tunnels_r[h0]; t; t = t->next) {<br />if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))<br />return t;<br />}<br />for (t = ipn->tunnels_l[h1]; t; t = t->next) {<br />if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP))<br />return t;<br />}<br />if ((t = ipn->tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP))<br />return t;<br />return NULL;<br />} 

     其實就是一個根據隧道ip地址尋找hash的過程(隧道ip就是沒剝去IPIP頭的源目的ip地址,不是原來未封裝的資料的源目的IP地址)。

     下面是資料發送函數。

     static int ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)<br />{<br />struct ip_tunnel *tunnel = netdev_priv(dev);<br />struct net_device_stats *stats = &tunnel->dev->stats;<br />struct iphdr *tiph = &tunnel->parms.iph;<br />u8 tos = tunnel->parms.iph.tos;<br />__be16 df = tiph->frag_off;<br />struct rtable *rt; /* Route to the other host */<br />struct net_device *tdev;/* Device to other host */<br />struct iphdr *old_iph = ip_hdr(skb);<br />struct iphdr *iph;/* Our new IP header */<br />unsigned int max_headroom;/* The extra header space needed */<br />__be32 dst = tiph->daddr;<br />int mtu;<br />if (tunnel->recursion++) {<br />stats->collisions++;<br />goto tx_error;<br />}<br />if (skb->protocol != htons(ETH_P_IP))<br />goto tx_error;<br />if (tos&1)<br />tos = old_iph->tos;<br />if (!dst) {<br />/* NBMA tunnel */<br />if ((rt = skb_rtable(skb)) == NULL) {<br />stats->tx_fifo_errors++;<br />goto tx_error;<br />}<br />if ((dst = rt->rt_gateway) == 0)<br />goto tx_error_icmp;<br />}<br />{<br />struct flowi fl = { .oif = tunnel->parms.link,<br /> .nl_u = { .ip4_u =<br /> { .daddr = dst,<br />.saddr = tiph->saddr,<br />.tos = RT_TOS(tos) } },<br /> .proto = IPPROTO_IPIP };<br />if (ip_route_output_key(dev_net(dev), &rt, &fl)) {<br />stats->tx_carrier_errors++;<br />goto tx_error_icmp;<br />}<br />}<br />tdev = rt->u.dst.dev;<br />if (tdev == dev) {<br />ip_rt_put(rt);<br />stats->collisions++;<br />goto tx_error;<br />}<br />if (tiph->frag_off)<br />mtu = dst_mtu(&rt->u.dst) - sizeof(struct iphdr);<br />else<br />mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;<br />if (mtu < 68) {<br />stats->collisions++;<br />ip_rt_put(rt);<br />goto tx_error;<br />}<br />if (skb_dst(skb))<br />skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu);<br />df |= (old_iph->frag_off&htons(IP_DF));<br />if ((old_iph->frag_off&htons(IP_DF)) && mtu < ntohs(old_iph->tot_len)) {<br />icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));<br />ip_rt_put(rt);<br />goto tx_error;<br />}<br />if (tunnel->err_count > 0) {<br />if (time_before(jiffies,<br />tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {<br />tunnel->err_count--;<br />dst_link_failure(skb);<br />} else<br />tunnel->err_count = 0;<br />}<br />/*<br /> * Okay, now see if we can stuff it in the buffer as-is.<br /> */<br />max_headroom = (LL_RESERVED_SPACE(tdev)+sizeof(struct iphdr));<br />if (skb_headroom(skb) < max_headroom || skb_shared(skb) ||<br /> (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {<br />struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);<br />if (!new_skb) {<br />ip_rt_put(rt);<br />stats->tx_dropped++;<br />dev_kfree_skb(skb);<br />tunnel->recursion--;<br />return 0;<br />}<br />if (skb->sk)<br />skb_set_owner_w(new_skb, skb->sk);<br />dev_kfree_skb(skb);<br />skb = new_skb;<br />old_iph = ip_hdr(skb);<br />}<br />skb->transport_header = skb->network_header;<br />skb_push(skb, sizeof(struct iphdr));<br />skb_reset_network_header(skb);<br />memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));<br />IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |<br /> IPSKB_REROUTED);<br />skb_dst_drop(skb);<br />skb_dst_set(skb, &rt->u.dst);<br />/*<br /> *Push down and install the IPIP header.<br /> */<br />iph =ip_hdr(skb);<br />iph->version=4;<br />iph->ihl=sizeof(struct iphdr)>>2;<br />iph->frag_off=df;<br />iph->protocol=IPPROTO_IPIP;<br />iph->tos=INET_ECN_encapsulate(tos, old_iph->tos);<br />iph->daddr=rt->rt_dst;<br />iph->saddr=rt->rt_src;<br />if ((iph->ttl = tiph->ttl) == 0)<br />iph->ttl=old_iph->ttl;<br />nf_reset(skb);<br />IPTUNNEL_XMIT();<br />tunnel->recursion--;<br />return 0;<br />tx_error_icmp:<br />dst_link_failure(skb);<br />tx_error:<br />stats->tx_errors++;<br />dev_kfree_skb(skb);<br />tunnel->recursion--;<br />return 0;<br />}<br />sta

     總得來看,發送過程主要包含兩個方面,一是尋找路由,另外一個按照IPIP協議構造新的IP包。

     尋找路由的過程這裡就不詳細介紹了,夠造新的IPIP包首先使用skb_headroom檢查剩餘的skb buff空間是否能容納IPIP頭,不能容納的話,重新使用skb_realloc_headroom分配一個buf空間,其實這個也可以用skb_copy_expand函數搞定。分配完畢後填充IPIP頭欄位即可。

     至此,IPIP協議大部分邏輯已經描述清楚,下面還有一個函數值的注意。

     static int<br />ipip_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)<br />{<br />int err = 0;<br />struct ip_tunnel_parm p;<br />struct ip_tunnel *t;<br />struct net *net = dev_net(dev);<br />struct ipip_net *ipn = net_generic(net, ipip_net_id);<br />switch (cmd) {<br />case SIOCGETTUNNEL:<br />t = NULL;<br />if (dev == ipn->fb_tunnel_dev) {<br />if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {<br />err = -EFAULT;<br />break;<br />}<br />t = ipip_tunnel_locate(net, &p, 0);<br />}<br />if (t == NULL)<br />t = netdev_priv(dev);<br />memcpy(&p, &t->parms, sizeof(p));<br />if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))<br />err = -EFAULT;<br />break;<br />case SIOCADDTUNNEL:<br />case SIOCCHGTUNNEL:<br />err = -EPERM;<br />if (!capable(CAP_NET_ADMIN))<br />goto done;<br />err = -EFAULT;<br />if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))<br />goto done;<br />err = -EINVAL;<br />if (p.iph.version != 4 || p.iph.protocol != IPPROTO_IPIP ||<br /> p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))<br />goto done;<br />if (p.iph.ttl)<br />p.iph.frag_off |= htons(IP_DF);<br />t = ipip_tunnel_locate(net, &p, cmd == SIOCADDTUNNEL);<br />if (dev != ipn->fb_tunnel_dev && cmd == SIOCCHGTUNNEL) {<br />if (t != NULL) {<br />if (t->dev != dev) {<br />err = -EEXIST;<br />break;<br />}<br />} else {<br />if (((dev->flags&IFF_POINTOPOINT) && !p.iph.daddr) ||<br /> (!(dev->flags&IFF_POINTOPOINT) && p.iph.daddr)) {<br />err = -EINVAL;<br />break;<br />}<br />t = netdev_priv(dev);<br />ipip_tunnel_unlink(ipn, t);<br />t->parms.iph.saddr = p.iph.saddr;<br />t->parms.iph.daddr = p.iph.daddr;<br />memcpy(dev->dev_addr, &p.iph.saddr, 4);<br />memcpy(dev->broadcast, &p.iph.daddr, 4);<br />ipip_tunnel_link(ipn, t);<br />netdev_state_change(dev);<br />}<br />}<br />if (t) {<br />err = 0;<br />if (cmd == SIOCCHGTUNNEL) {<br />t->parms.iph.ttl = p.iph.ttl;<br />t->parms.iph.tos = p.iph.tos;<br />t->parms.iph.frag_off = p.iph.frag_off;<br />if (t->parms.link != p.link) {<br />t->parms.link = p.link;<br />ipip_tunnel_bind_dev(dev);<br />netdev_state_change(dev);<br />}<br />}<br />if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))<br />err = -EFAULT;<br />} else<br />err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);<br />break;<br />case SIOCDELTUNNEL:<br />err = -EPERM;<br />if (!capable(CAP_NET_ADMIN))<br />goto done;<br />if (dev == ipn->fb_tunnel_dev) {<br />err = -EFAULT;<br />if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))<br />goto done;<br />err = -ENOENT;<br />if ((t = ipip_tunnel_locate(net, &p, 0)) == NULL)<br />goto done;<br />err = -EPERM;<br />if (t->dev == ipn->fb_tunnel_dev)<br />goto done;<br />dev = t->dev;<br />}<br />unregister_netdevice(dev);<br />err = 0;<br />break;<br />default:<br />err = -EINVAL;<br />}<br />done:<br />return err;<br />} 

     這個函數就是應用程式互動的地方,說白了,就是與linux系統命令ifconfig, ip命令借口的地方,建立隧道,刪除隧道,改變ip地址,等等,本文就不詳細介紹了。

     IPIP協議的所有內容已經介紹完畢,原理是不是很簡單呢?其實IP_GRE協議與IPIP協議差不多,最先是由思科公司發明的一種協議,在linux實現代碼在ip_gre.c中,具體實現與IPIP協議及其類似,這裡也不多說了。至於open_swan 的IPSec協議實現稍稍複雜一些,多了一些加密過程,已經前面的金鑰交換過程,大家可以仔細研究。《Linux核心VPN實現源碼分析》就此完結,歡迎大家關注後續的內容。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.