本文主要是介绍SIT通用隧道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
SIT模块不仅支持IPv6-over-IPv4封装,还支持IPv4-over-IPv4和MPLS-over-IPv4封装报文,其中IPv4-over-IPv4与IPIP隧道功能相同。对于控制接口,注册了每个网络命名空间处理结构sit_net_ops和netlink处理接口sit_link_ops。
static int __init sit_init(void)
{ int err;pr_info("IPv6, IPv4 and MPLS over IPv4 tunneling driver\n");err = register_pernet_device(&sit_net_ops);if (err < 0)return err;err = xfrm4_tunnel_register(&sit_handler, AF_INET6);if (err < 0) {pr_info("%s: can't register ip6ip4\n", __func__);goto xfrm_tunnel_failed;}...err = rtnl_link_register(&sit_link_ops);
对于数据报文处理,由如下函数xfrm4_tunnel_register注册接口,隧道处理结构xfrm_tunnel按照优先级排序,数值小表示高优先级。sit_handler优先级为1。
static struct xfrm_tunnel sit_handler __read_mostly = {.handler = ipip6_rcv,.err_handler = ipip6_err,.priority = 1,
};
int xfrm4_tunnel_register(struct xfrm_tunnel *handler, unsigned short family)
{struct xfrm_tunnel __rcu **pprev;struct xfrm_tunnel *t;int priority = handler->priority;for (pprev = fam_handlers(family); (t = rcu_dereference_protected(*pprev,lockdep_is_held(&tunnel4_mutex))) != NULL; pprev = &t->next) {if (t->priority > priority)break;if (t->priority == priority)goto err;}handler->next = *pprev;rcu_assign_pointer(*pprev, handler);
命名空间fallback设备
如下网络命名空间初始化函数sit_init_net,如果允许为每个命名空间创建单独的fallback隧道设备,sit驱动将创建设备sit0。PROC文件/proc/sys/net/core/fb_tunnels_only_for_init_net用于控制是否创建fallback设备。
sit的fallback隧道设备是命名空间本地属性,不能移动到其它的命名空间。但是,ip命令新创建的sit隧道设备不受此限制。
static struct pernet_operations sit_net_ops = {.init = sit_init_net,.exit_batch = sit_exit_batch_net,.id = &sit_net_id,.size = sizeof(struct sit_net),
};
static int __net_init sit_init_net(struct net *net)
{struct sit_net *sitn = net_generic(net, sit_net_id);struct ip_tunnel *t;sitn->tunnels[0] = sitn->tunnels_wc;sitn->tunnels[1] = sitn->tunnels_l;sitn->tunnels[2] = sitn->tunnels_r;sitn->tunnels[3] = sitn->tunnels_r_l;if (!net_has_fallback_tunnels(net)) return 0;sitn->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "sit0",NET_NAME_UNKNOWN, ipip6_tunnel_setup);if (!sitn->fb_tunnel_dev) {err = -ENOMEM;goto err_alloc_dev;}dev_net_set(sitn->fb_tunnel_dev, net);sitn->fb_tunnel_dev->rtnl_link_ops = &sit_link_ops;sitn->fb_tunnel_dev->features |= NETIF_F_NETNS_LOCAL;
netlink用户接口
以下ip命令增加sit隧道。模式关键字mode的参数为sit,对应于内核结构sit_link_ops的成员kind的值。
# ip tunnel add sit1 mode sit remote 192.168.20.1 local 192.168.20.5static struct rtnl_link_ops sit_link_ops __read_mostly = {.kind = "sit",.maxtype = IFLA_IPTUN_MAX,.policy = ipip6_policy,.priv_size = sizeof(struct ip_tunnel),.setup = ipip6_tunnel_setup,.validate = ipip6_validate,.newlink = ipip6_newlink,.changelink = ipip6_changelink,.get_size = ipip6_get_size,.fill_info = ipip6_fill_info,.dellink = ipip6_dellink,.get_link_net = ip_tunnel_get_link_net,
};
函数ipip6_newlink创建新的sit隧道,首先ipip6_tunnel_locate检测隧道的重复性;之后,有函数ipip6_tunnel_create创建隧道。
static int ipip6_newlink(struct net *src_net, struct net_device *dev,struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack)
{struct net *net = dev_net(dev);struct ip_tunnel *nt;struct ip_tunnel_encap ipencap;nt = netdev_priv(dev);if (ipip6_netlink_encap_parms(data, &ipencap)) {err = ip_tunnel_encap_setup(nt, &ipencap);if (err < 0) return err;}ipip6_netlink_parms(data, &nt->parms, &nt->fwmark);if (ipip6_tunnel_locate(net, &nt->parms, 0)) return -EEXIST;err = ipip6_tunnel_create(dev);if (err < 0) return err;if (tb[IFLA_MTU]) {u32 mtu = nla_get_u32(tb[IFLA_MTU]);if (mtu >= IPV6_MIN_MTU &&mtu <= IP6_MAX_MTU - dev->hard_header_len)dev->mtu = mtu;}
将新注册的隧道设备进行注册(register_netdevice),之后有函数ipip6_tunnel_link将新设备连接到命名空间的链表上。
static int ipip6_tunnel_create(struct net_device *dev)
{ struct ip_tunnel *t = netdev_priv(dev);struct net *net = dev_net(dev);struct sit_net *sitn = net_generic(net, sit_net_id);memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);memcpy(dev->broadcast, &t->parms.iph.daddr, 4);if ((__force u16)t->parms.i_flags & SIT_ISATAP)dev->priv_flags |= IFF_ISATAP;dev->rtnl_link_ops = &sit_link_ops;err = register_netdevice(dev);if (err < 0) goto out;ipip6_tunnel_clone_6rd(dev, sitn);dev_hold(dev);ipip6_tunnel_link(sitn, t);
在以上提到的sit_init_net命名空间初始化函数中,sit_net初始化了4个tunnels成员,其中索引0为fallback隧道使用,另外,索引1,2,3分别对应local、remote、local&remote三种隧道。
在ip命令中同时指定了local和remote参数,所以,函数__ipip6_bucket计算得到的prio为3,即一级索引为3,二级索引h的范围为:[0,15],参加宏定义HASH。
static void ipip6_tunnel_link(struct sit_net *sitn, struct ip_tunnel *t)
{struct ip_tunnel __rcu **tp = ipip6_bucket(sitn, t);rcu_assign_pointer(t->next, rtnl_dereference(*tp));rcu_assign_pointer(*tp, t);
}
static struct ip_tunnel __rcu **__ipip6_bucket(struct sit_net *sitn,struct ip_tunnel_parm *parms)
{__be32 remote = parms->iph.daddr;__be32 local = parms->iph.saddr;unsigned int h = 0;int prio = 0;if (remote) {prio |= 2;h ^= HASH(remote);}if (local) {prio |= 1;h ^= HASH(local);}return &sitn->tunnels[prio][h];
}
#define IP6_SIT_HASH_SIZE 16
#define HASH(addr) (((__force u32)addr^((__force u32)addr>>4))&0xF)
顺便看一下数据平面的隧道查找函数ipip6_tunnel_lookup,其与以上的插入函数正好相反。查找先从高优先级的本地和远端都存在的bucket开始,依次为remote,之后是local,最后是fallback桶。
选择的隧道接口必须是UP状态。
static struct ip_tunnel *ipip6_tunnel_lookup(struct net *net,struct net_device *dev, __be32 remote, __be32 local, int sifindex)
{ unsigned int h0 = HASH(remote);unsigned int h1 = HASH(local);struct ip_tunnel *t; struct sit_net *sitn = net_generic(net, sit_net_id);int ifindex = dev ? dev->ifindex : 0;for_each_ip_tunnel_rcu(t, sitn->tunnels_r_l[h0 ^ h1]) {if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr &&(!dev || !t->parms.link || ifindex == t->parms.link ||sifindex == t->parms.link) && (t->dev->flags & IFF_UP))return t;}for_each_ip_tunnel_rcu(t, sitn->tunnels_r[h0]) {if (remote == t->parms.iph.daddr &&(!dev || !t->parms.link || ifindex == t->parms.link ||sifindex == t->parms.link) && (t->dev->flags & IFF_UP))return t;}for_each_ip_tunnel_rcu(t, sitn->tunnels_l[h1]) {if (local == t->parms.iph.saddr &&(!dev || !t->parms.link || ifindex == t->parms.link ||sifindex == t->parms.link) && (t->dev->flags & IFF_UP))return t;} t = rcu_dereference(sitn->tunnels_wc[0]);if (t && (t->dev->flags & IFF_UP))return t;return NULL;
数据接收
首先查找是否有相应的隧道,没有找到直接返回1。否则,检测隧道配置的协议号,要求protocol必须是IPPROTO_IPV6(41)或者为0。以为sit支持还支持IPv4-over-IPv4和MPLS-over-IPv4,所以要做此检测。
static int ipip6_rcv(struct sk_buff *skb)
{const struct iphdr *iph = ip_hdr(skb);struct ip_tunnel *tunnel;sifindex = netif_is_l3_master(skb->dev) ? IPCB(skb)->iif : 0;tunnel = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev, iph->saddr, iph->daddr, sifindex);if (tunnel) {struct pcpu_sw_netstats *tstats;if (tunnel->parms.iph.protocol != IPPROTO_IPV6 &&tunnel->parms.iph.protocol != 0)goto out;skb->mac_header = skb->network_header;skb_reset_network_header(skb);IPCB(skb)->flags = 0;skb->dev = tunnel->dev;
将当前的IPv4网络头部位置赋值给MAC头部mac_header,重新将网络头部指向IPv6头部。之后,检测是否为欺骗报文。
由于传入的hdr_len参数为0,函数iptunnel_pull_header并不会改变skb的数据区,而是执行更新skb->protocol字段为ETH_P_IPV6,报文清洗,使其像是刚从物理口上接口的报文。由于隧道设备没有GSO功能,对于GSO数据,如果skb被克隆过,执行unclone操作,并清除所有的GSO标志。
if (packet_is_spoofed(skb, iph, tunnel)) {tunnel->dev->stats.rx_errors++;goto out;}if (iptunnel_pull_header(skb, 0, htons(ETH_P_IPV6),!net_eq(tunnel->net, dev_net(tunnel->dev))))goto out;/* skb can be uncloned in iptunnel_pull_header, so old iph is no longer valid*/iph = (const struct iphdr *)skb_mac_header(skb);err = IP_ECN_decapsulate(iph, skb);if (unlikely(err)) {if (log_ecn_error)net_info_ratelimited("non-ECT from %pI4 with TOS=%#x\n", &iph->saddr, iph->tos);if (err > 1) {++tunnel->dev->stats.rx_frame_errors;++tunnel->dev->stats.rx_errors;goto out;}}...netif_rx(skb);return 0;}/* no tunnel matched, let upstream know, ipsec may handle it */return 1;
参加隧道设备初始化函数ipip6_tunnel_bind_dev,如果为隧道指定了目的地址(remote),内核将增加设备标志IFF_POINTOPOINT,对于非ISATAP隧道,packet_is_spoofed总是返回false。
以下函数中的地址欺骗检测主要是对于isatap和6rd隧道的实现。
static bool packet_is_spoofed(struct sk_buff *skb,const struct iphdr *iph, struct ip_tunnel *tunnel)
{const struct ipv6hdr *ipv6h;if (tunnel->dev->priv_flags & IFF_ISATAP) {if (!isatap_chksrc(skb, iph, tunnel))return true;return false;}if (tunnel->dev->flags & IFF_POINTOPOINT)return false;ipv6h = ipv6_hdr(skb);if (unlikely(is_spoofed_6rd(tunnel, iph->saddr, &ipv6h->saddr))) {net_warn_ratelimited("Src spoofed %pI4/%pI6c -> %pI4/%pI6c\n",&iph->saddr, &ipv6h->saddr, &iph->daddr, &ipv6h->daddr);return true;}if (likely(!is_spoofed_6rd(tunnel, iph->daddr, &ipv6h->daddr)))return false;if (only_dnatted(tunnel, &ipv6h->daddr))return false;net_warn_ratelimited("Dst spoofed %pI4/%pI6c -> %pI4/%pI6c\n",&iph->saddr, &ipv6h->saddr, &iph->daddr, &ipv6h->daddr);return true;
数据发送
对于IPv6-over-IPv4类型的sit隧道,实际使用的是ipip6_tunnel_xmit发送函数。进入隧道待发送的报文为IPv6协议报文。
static netdev_tx_t sit_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{if (!pskb_inet_may_pull(skb))goto tx_err;switch (skb->protocol) {case htons(ETH_P_IP):sit_tunnel_xmit__(skb, dev, IPPROTO_IPIP);break;case htons(ETH_P_IPV6):ipip6_tunnel_xmit(skb, dev);break;
以下忽略了isatap和6rd的处理。对于没有指定目标地址(remote)的隧道,skb中必须存在路由缓存,并且路由缓存对应的邻居表现必须存在,即下一跳地址必须存在邻居表中。以下将使用此IPv6地址来获得对应的IPv4地址,如果此IPv6地址为全零地址,将使用报文中的IPv6目的地址。
| 80 bits | 16bits | 32 bits ||-------------------------|-----------|----------------|| 0000…0000 | 0000 | IPv4 address ||-------------------------|-----------|----------------|
如果最终使用的地址不是兼容IPv4地址的IPv6地址,返回错误;否者,由IPv6地址中取出IPv4值,作为隧道的目的地址。IPv6兼容IPv4地址格式如上所示。
static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{struct ip_tunnel *tunnel = netdev_priv(dev);const struct iphdr *tiph = &tunnel->parms.iph;const struct ipv6hdr *iph6 = ipv6_hdr(skb);u8 tos = tunnel->parms.iph.tos;__be16 df = tiph->frag_off;__be32 dst = tiph->daddr;u8 protocol = IPPROTO_IPV6;int t_hlen = tunnel->hlen + sizeof(struct iphdr);if (tos == 1) tos = ipv6_get_dsfield(iph6);/* ISATAP (RFC4214) - must come before 6to4 */...if (!dst) {struct neighbour *neigh = NULL;bool do_tx_error = false;if (skb_dst(skb))neigh = dst_neigh_lookup(skb_dst(skb), &iph6->daddr);if (!neigh) {net_dbg_ratelimited("nexthop == NULL\n");goto tx_error;}addr6 = (const struct in6_addr *)&neigh->primary_key;addr_type = ipv6_addr_type(addr6);if (addr_type == IPV6_ADDR_ANY) {addr6 = &ipv6_hdr(skb)->daddr;addr_type = ipv6_addr_type(addr6);}if ((addr_type & IPV6_ADDR_COMPATv4) != 0)dst = addr6->s6_addr32[3];elsedo_tx_error = true;neigh_release(neigh);if (do_tx_error) goto tx_error;}
之后,检测此隧道是否缓存了路由项,没有的话进行出口路由的查找。要求路由项必须为单播。路由出口设备不能等于隧道自身。
flowi4_init_output(&fl4, tunnel->parms.link, tunnel->fwmark,RT_TOS(tos), RT_SCOPE_UNIVERSE, IPPROTO_IPV6,0, dst, tiph->saddr, 0, 0, sock_net_uid(tunnel->net, NULL));rt = dst_cache_get_ip4(&tunnel->dst_cache, &fl4.saddr);if (!rt) {rt = ip_route_output_flow(tunnel->net, &fl4, NULL);if (IS_ERR(rt)) {dev->stats.tx_carrier_errors++;goto tx_error_icmp;}dst_cache_set_ip4(&tunnel->dst_cache, &rt->dst, fl4.saddr);}if (rt->rt_type != RTN_UNICAST) {ip_rt_put(rt);dev->stats.tx_carrier_errors++;goto tx_error_icmp;}tdev = rt->dst.dev;if (tdev == dev) {ip_rt_put(rt);dev->stats.collisions++;goto tx_error;}
函数iptunnel_handle_offloads用于设置IPXIP4卸载。
如果原始报文设置了禁止分片标志位DF,检测路由出口设备的MTU值,因为接下来要添加隧道头部,这里由MTU中减去隧道头部长度。如果MTU小于IPV4_MIN_MTU(68),返回错误。MTU值最小取IPV6_MIN_MTU(1280),如果小于1280,清除DF位。
如果报文长度大于最终的MTU值,并且没有使用GSO卸载功能,返回code值为ICMPV6_PKT_TOOBIG的icmpv6错误。
if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4)) {ip_rt_put(rt);goto tx_error;}if (df) {mtu = dst_mtu(&rt->dst) - t_hlen;if (mtu < 68) {dev->stats.collisions++;ip_rt_put(rt);goto tx_error;}if (mtu < IPV6_MIN_MTU) {mtu = IPV6_MIN_MTU;df = 0;}if (tunnel->parms.iph.daddr)skb_dst_update_pmtu_no_confirm(skb, mtu);if (skb->len > mtu && !skb_is_gso(skb)) {icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);ip_rt_put(rt);goto tx_error;}}
执行到此,隧道看起来一切正常。如果隧道的错误计数不为零,并且距离发送错误的时刻不超过30秒(IPTUNNEL_ERR_TIMEO),递减错误计数;否则,时间超过30秒,将错误计数清零。错误计数在函数ipip6_err中增加。
if (tunnel->err_count > 0) {if (time_before(jiffies, tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {tunnel->err_count--;dst_link_failure(skb);} elsetunnel->err_count = 0;}
以下为操作skb缓存进行准备,包括确保足够的头部空间,可写性检测等。以及获取ttl值、tos值,并检测是否进行foo或者fou类型的UDP封装。最后,有函数iptunnel_xmit执行隧道头赋值,和发送操作。
/* Okay, now see if we can stuff it in the buffer as-is. */max_headroom = LL_RESERVED_SPACE(tdev) + t_hlen;if (skb_headroom(skb) < max_headroom || skb_shared(skb) ||(skb_cloned(skb) && !skb_clone_writable(skb, 0))) {struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);if (!new_skb) {ip_rt_put(rt);dev->stats.tx_dropped++;kfree_skb(skb);return NETDEV_TX_OK;}if (skb->sk) skb_set_owner_w(new_skb, skb->sk);dev_kfree_skb(skb);skb = new_skb;iph6 = ipv6_hdr(skb);}ttl = tiph->ttl;if (ttl == 0)ttl = iph6->hop_limit;tos = INET_ECN_encapsulate(tos, ipv6_get_dsfield(iph6));if (ip_tunnel_encap(skb, tunnel, &protocol, &fl4) < 0) {ip_rt_put(rt);goto tx_error;}skb_set_inner_ipproto(skb, IPPROTO_IPV6);iptunnel_xmit(NULL, rt, skb, fl4.saddr, fl4.daddr, protocol, tos, ttl,df, !net_eq(tunnel->net, dev_net(dev)));return NETDEV_TX_OK;
错误处理函数
sit隧道结构注册时,还注册了错误处理函数ipip6_err。
static struct xfrm_tunnel sit_handler __read_mostly = {.handler = ipip6_rcv,.err_handler = ipip6_err,.priority = 1,
};
如下函数ipip6_err,如果没有找到对于的隧道,直接返回。对于code为ICMP_FRAG_NEEDED的情况,更新路径MTU值。最后,更新错误计数和时间戳。
static int ipip6_err(struct sk_buff *skb, u32 info)
{const struct iphdr *iph = (const struct iphdr *)skb->data;const int type = icmp_hdr(skb)->type;const int code = icmp_hdr(skb)->code;...sifindex = netif_is_l3_master(skb->dev) ? IPCB(skb)->iif : 0;t = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev,iph->daddr, iph->saddr, sifindex);if (!t) goto out;if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {ipv4_update_pmtu(skb, dev_net(skb->dev), info, t->parms.link, iph->protocol);err = 0;goto out;} if (type == ICMP_REDIRECT) {ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, iph->protocol);err = 0;goto out;}err = 0;if (__in6_dev_get(skb->dev) && !ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type, data_len))goto out;if (t->parms.iph.daddr == 0)goto out;if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)goto out;if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))t->err_count++;elset->err_count = 1;t->err_time = jiffies;
另外,在接收函数tunnel64_rcv中,如果没有找到注册的隧道处理结构(包括sit),使用xfrm4_tunnel_register函数注册。那么将返回code为ICMP_PORT_UNREACH的错误,在实际中遇到此错误,会有些不知所措。
static int tunnel64_rcv(struct sk_buff *skb)
{ struct xfrm_tunnel *handler; if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))goto drop;for_each_tunnel_rcu(tunnel64_handlers, handler)if (!handler->handler(skb)) return 0; icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
IPv6地址配置
对于sit隧道设备,不使用IPv6隐私地址。
static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
{struct inet6_dev *ndev;...INIT_LIST_HEAD(&ndev->tempaddr_list);ndev->desync_factor = U32_MAX;if ((dev->flags&IFF_LOOPBACK) ||dev->type == ARPHRD_TUNNEL ||dev->type == ARPHRD_TUNNEL6 ||dev->type == ARPHRD_SIT ||dev->type == ARPHRD_NONE) {ndev->cnf.use_tempaddr = -1;}
如果sit设备地址配置发生变化,或者接口up/down等,都会出发一下通知函数addrconf_notify。
static int addrconf_notify(struct notifier_block *this, unsigned long event, void *ptr)
{...switch (dev->type) {
#if IS_ENABLED(CONFIG_IPV6_SIT)case ARPHRD_SIT:addrconf_sit_config(dev);break;
#endif
在函数addrconf_sit_config中,核心处理函数为sit_add_v4_addrs。这里先不涉及ISATAP隧道处理。
static void addrconf_sit_config(struct net_device *dev)
{struct inet6_dev *idev;ASSERT_RTNL();/** Configure the tunnel with one of our IPv4 addresses... * we should configure all of our v4 addrs in the tunnel*/idev = ipv6_find_idev(dev);if (IS_ERR(idev)) {pr_debug("%s: add_dev failed\n", __func__);return;}if (dev->priv_flags & IFF_ISATAP) {addrconf_addr_gen(idev, false);return;}sit_add_v4_addrs(idev);if (dev->flags&IFF_POINTOPOINT)addrconf_add_mroute(dev);
对于sit隧道设备,其设备地址dev_addr为隧道的(local)本地IP地址,如192.168.20.5。对于POINTOPOINT设备,使用IPv6本地链路前缀0xfe800000。否则,其它类型使用兼容IPv4地址的IPv6地址格式,前缀长度为96,全部为零。
static void sit_add_v4_addrs(struct inet6_dev *idev)
{struct in6_addr addr;struct net_device *dev;struct net *net = dev_net(idev->dev);int scope, plen;u32 pflags = 0;ASSERT_RTNL();memset(&addr, 0, sizeof(struct in6_addr));memcpy(&addr.s6_addr32[3], idev->dev->dev_addr, 4);if (idev->dev->flags&IFF_POINTOPOINT) {addr.s6_addr32[0] = htonl(0xfe800000);scope = IFA_LINK;plen = 64;} else {scope = IPV6_ADDR_COMPATv4;plen = 96;pflags |= RTF_NONEXTHOP;}
如果IPv6地址的最后4个字节不为空,创建相应的隧道接口地址和路由项,返回结束处理。否则,遍历隧道设备所在命名空间中的所有接口,对于每个UP状态的接口,遍历其所有的地址:
1)这里不考虑本地链路地址(RT_SCOPE_LINK);
2)如果隧道为点到点类型,也不考虑范围scope大于等于RT_SCOPE_HOST的地址;
3)对于RT_SCOPE_UNIVERSE或者RT_SCOPE_SITE地址,将其加上IPv6前缀,配置到隧道接口上。
if (addr.s6_addr32[3]) {add_addr(idev, &addr, plen, scope);addrconf_prefix_route(&addr, plen, 0, idev->dev, 0, pflags, GFP_KERNEL);return;}for_each_netdev(net, dev) {struct in_device *in_dev = __in_dev_get_rtnl(dev);if (in_dev && (dev->flags & IFF_UP)) {struct in_ifaddr *ifa;int flag = scope;in_dev_for_each_ifa_rtnl(ifa, in_dev) {addr.s6_addr32[3] = ifa->ifa_local;if (ifa->ifa_scope == RT_SCOPE_LINK)continue;if (ifa->ifa_scope >= RT_SCOPE_HOST) {if (idev->dev->flags&IFF_POINTOPOINT)continue;flag |= IFA_HOST;}add_addr(idev, &addr, plen, flag);addrconf_prefix_route(&addr, plen, 0, idev->dev, 0, pflags, GFP_KERNEL);
如下fallback设备sit0,其不是点到点设备,前缀使用的为全零,包括lo、ens33、ens34接口的IPv4地址,都转换为IPv6地址,配置在了sit0上。但是,这三个接口上scope为link的地址,并没有配置到sit0上。
而对于点到点隧道设备sit1,其上并没有配置由其它接口IPv4地址生成的IPv6地址。
# ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether 00:0c:29:ea:2e:27 brd ff:ff:ff:ff:ff:ffaltname enp2s1inet 192.168.30.1/24 scope global ens33valid_lft forever preferred_lft foreverinet6 fe80::20c:29ff:feea:2e27/64 scope link valid_lft forever preferred_lft forever
3: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether 00:0c:29:ea:2e:31 brd ff:ff:ff:ff:ff:ffaltname enp2s2inet 192.168.9.177/24 brd 192.168.9.255 scope global dynamic ens34valid_lft 1093sec preferred_lft 1093secinet6 fe80::20c:29ff:feea:2e31/64 scope link valid_lft forever preferred_lft forever
5: sit0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000link/sit 0.0.0.0 brd 0.0.0.0inet6 ::192.168.9.177/96 scope global valid_lft forever preferred_lft foreverinet6 ::192.168.30.1/96 scope global valid_lft forever preferred_lft foreverinet6 ::127.0.0.1/96 scope host valid_lft forever preferred_lft forever
6: sit1@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000link/sit 192.168.20.5 peer 192.168.20.1inet6 fe80::c0a8:1405/64 scope link valid_lft forever preferred_lft forever
对于路由而言,前缀长度为96的地址,都经过sit0隧道设备。
# ip -6 router
::1 dev lo proto kernel metric 256 pref medium
::/96 dev sit0 proto kernel metric 256 pref medium
fe80::/64 dev sit1 proto kernel metric 256 pref medium
在以上函数addrconf_sit_config的最后,将调用addrconf_add_mroute为点到点隧道增加多播路由。
static void addrconf_add_mroute(struct net_device *dev)
{ struct fib6_config cfg = {.fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_LOCAL,.fc_metric = IP6_RT_PRIO_ADDRCONF,.fc_ifindex = dev->ifindex,.fc_dst_len = 8,.fc_flags = RTF_UP,.fc_type = RTN_UNICAST,.fc_nlinfo.nl_net = dev_net(dev),};ipv6_addr_set(&cfg.fc_dst, htonl(0xFF000000), 0, 0, 0);ip6_route_add(&cfg, GFP_KERNEL, NULL);
如下的路由项,如果隧道设备不是l3mdev子设备,多播路由项将位于路由表RT6_TABLE_LOCAL(255)中。
# ip -6 route show table 255
multicast ff00::/8 dev sit1 proto kernel metric 256 pref medium
内核版本 5.10
这篇关于SIT通用隧道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!