Commit c4d3efaf authored by Yasuyuki Kozakai's avatar Yasuyuki Kozakai Committed by David S. Miller

[IPV6] IP6TUNNEL: Add support to IPv4 over IPv6 tunnel.

Some notes
- Protocol number IPPROTO_IPIP is used for IPv4 over IPv6 packets.
- If IP6_TNL_F_USE_ORIG_TCLASS is set, TOS in IPv4 header is copied to
  Traffic Class in outer IPv6 header on xmit.
- IP6_TNL_F_USE_ORIG_FLOWLABEL is ignored on xmit of IPv4 packets, because
  IPv4 header does not have flow label.
- Kernel sends ICMP error if IPv4 packet is too big on xmit, even if
  DF flag is not set.
Signed-off-by: default avatarYasuyuki Kozakai <yasuyuki.kozakai@toshiba.co.jp>
Signed-off-by: default avatarYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 61ec2aec
/* /*
* IPv6 over IPv6 tunnel device * IPv6 tunneling device
* Linux INET6 implementation * Linux INET6 implementation
* *
* Authors: * Authors:
* Ville Nuorvala <vnuorval@tcs.hut.fi> * Ville Nuorvala <vnuorval@tcs.hut.fi>
* Yasuyuki Kozakai <kozakai@linux-ipv6.org>
* *
* $Id$ * $Id$
* *
* Based on: * Based on:
* linux/net/ipv6/sit.c * linux/net/ipv6/sit.c and linux/net/ipv4/ipip.c
* *
* RFC 2473 * RFC 2473
* *
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/sockios.h> #include <linux/sockios.h>
#include <linux/icmp.h>
#include <linux/if.h> #include <linux/if.h>
#include <linux/in.h> #include <linux/in.h>
#include <linux/ip.h> #include <linux/ip.h>
...@@ -41,6 +43,7 @@ ...@@ -41,6 +43,7 @@
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/atomic.h> #include <asm/atomic.h>
#include <net/icmp.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/ipv6.h> #include <net/ipv6.h>
#include <net/ip6_route.h> #include <net/ip6_route.h>
...@@ -51,7 +54,7 @@ ...@@ -51,7 +54,7 @@
#include <net/inet_ecn.h> #include <net/inet_ecn.h>
MODULE_AUTHOR("Ville Nuorvala"); MODULE_AUTHOR("Ville Nuorvala");
MODULE_DESCRIPTION("IPv6-in-IPv6 tunnel"); MODULE_DESCRIPTION("IPv6 tunneling device");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
#define IPV6_TLV_TEL_DST_SIZE 8 #define IPV6_TLV_TEL_DST_SIZE 8
...@@ -63,6 +66,7 @@ MODULE_LICENSE("GPL"); ...@@ -63,6 +66,7 @@ MODULE_LICENSE("GPL");
#endif #endif
#define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK) #define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK)
#define IPV6_TCLASS_SHIFT 20
#define HASH_SIZE 32 #define HASH_SIZE 32
...@@ -469,6 +473,104 @@ ip6_tnl_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -469,6 +473,104 @@ ip6_tnl_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
return err; return err;
} }
static int
ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
int rel_msg = 0;
int rel_type = type;
int rel_code = code;
__u32 rel_info = info;
int err;
struct sk_buff *skb2;
struct iphdr *eiph;
struct flowi fl;
struct rtable *rt;
err = ip6_tnl_err(skb, opt, &rel_type, &rel_code, &rel_msg, &rel_info,
offset);
if (err < 0)
return err;
if (rel_msg == 0)
return 0;
switch (rel_type) {
case ICMPV6_DEST_UNREACH:
if (rel_code != ICMPV6_ADDR_UNREACH)
return 0;
rel_type = ICMP_DEST_UNREACH;
rel_code = ICMP_HOST_UNREACH;
break;
case ICMPV6_PKT_TOOBIG:
if (rel_code != 0)
return 0;
rel_type = ICMP_DEST_UNREACH;
rel_code = ICMP_FRAG_NEEDED;
break;
default:
return 0;
}
if (!pskb_may_pull(skb, offset + sizeof(struct iphdr)))
return 0;
skb2 = skb_clone(skb, GFP_ATOMIC);
if (!skb2)
return 0;
dst_release(skb2->dst);
skb2->dst = NULL;
skb_pull(skb2, offset);
skb2->nh.raw = skb2->data;
eiph = skb2->nh.iph;
/* Try to guess incoming interface */
memset(&fl, 0, sizeof(fl));
fl.fl4_dst = eiph->saddr;
fl.fl4_tos = RT_TOS(eiph->tos);
fl.proto = IPPROTO_IPIP;
if (ip_route_output_key(&rt, &fl))
goto out;
skb2->dev = rt->u.dst.dev;
/* route "incoming" packet */
if (rt->rt_flags & RTCF_LOCAL) {
ip_rt_put(rt);
rt = NULL;
fl.fl4_dst = eiph->daddr;
fl.fl4_src = eiph->saddr;
fl.fl4_tos = eiph->tos;
if (ip_route_output_key(&rt, &fl) ||
rt->u.dst.dev->type != ARPHRD_TUNNEL) {
ip_rt_put(rt);
goto out;
}
} else {
ip_rt_put(rt);
if (ip_route_input(skb2, eiph->daddr, eiph->saddr, eiph->tos,
skb2->dev) ||
skb2->dst->dev->type != ARPHRD_TUNNEL)
goto out;
}
/* change mtu on this route */
if (rel_type == ICMP_DEST_UNREACH && rel_code == ICMP_FRAG_NEEDED) {
if (rel_info > dst_mtu(skb2->dst))
goto out;
skb2->dst->ops->update_pmtu(skb2->dst, rel_info);
rel_info = htonl(rel_info);
}
icmp_send(skb2, rel_type, rel_code, rel_info);
out:
kfree_skb(skb2);
return 0;
}
static int static int
ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info) int type, int code, int offset, __u32 info)
...@@ -513,6 +615,19 @@ ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -513,6 +615,19 @@ ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
return 0; return 0;
} }
static void ip4ip6_dscp_ecn_decapsulate(struct ip6_tnl *t,
struct ipv6hdr *ipv6h,
struct sk_buff *skb)
{
__u8 dsfield = ipv6_get_dsfield(ipv6h) & ~INET_ECN_MASK;
if (t->parms.flags & IP6_TNL_F_RCV_DSCP_COPY)
ipv4_change_dsfield(skb->nh.iph, INET_ECN_MASK, dsfield);
if (INET_ECN_is_ce(dsfield))
IP_ECN_set_ce(skb->nh.iph);
}
static void ip6ip6_dscp_ecn_decapsulate(struct ip6_tnl *t, static void ip6ip6_dscp_ecn_decapsulate(struct ip6_tnl *t,
struct ipv6hdr *ipv6h, struct ipv6hdr *ipv6h,
struct sk_buff *skb) struct sk_buff *skb)
...@@ -605,6 +720,11 @@ static int ip6_tnl_rcv(struct sk_buff *skb, __u16 protocol, ...@@ -605,6 +720,11 @@ static int ip6_tnl_rcv(struct sk_buff *skb, __u16 protocol,
return 0; return 0;
} }
static int ip4ip6_rcv(struct sk_buff *skb)
{
return ip6_tnl_rcv(skb, ETH_P_IP, ip4ip6_dscp_ecn_decapsulate);
}
static int ip6ip6_rcv(struct sk_buff *skb) static int ip6ip6_rcv(struct sk_buff *skb)
{ {
return ip6_tnl_rcv(skb, ETH_P_IPV6, ip6ip6_dscp_ecn_decapsulate); return ip6_tnl_rcv(skb, ETH_P_IPV6, ip6ip6_dscp_ecn_decapsulate);
...@@ -691,7 +811,7 @@ static inline int ip6_tnl_xmit_ctl(struct ip6_tnl *t) ...@@ -691,7 +811,7 @@ static inline int ip6_tnl_xmit_ctl(struct ip6_tnl *t)
* it. * it.
* *
* Return: * Return:
* 0 * 0 on success
* -1 fail * -1 fail
* %-EMSGSIZE message too big. return mtu in this case. * %-EMSGSIZE message too big. return mtu in this case.
**/ **/
...@@ -808,6 +928,44 @@ static int ip6_tnl_xmit2(struct sk_buff *skb, ...@@ -808,6 +928,44 @@ static int ip6_tnl_xmit2(struct sk_buff *skb,
return err; return err;
} }
static inline int
ip4ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
struct iphdr *iph = skb->nh.iph;
int encap_limit = -1;
struct flowi fl;
__u8 dsfield;
__u32 mtu;
int err;
if (!ip6_tnl_xmit_ctl(t))
return -1;
if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT))
encap_limit = t->parms.encap_limit;
memcpy(&fl, &t->fl, sizeof (fl));
fl.proto = IPPROTO_IPIP;
dsfield = ipv4_get_dsfield(iph);
if ((t->parms.flags & IP6_TNL_F_USE_ORIG_TCLASS))
fl.fl6_flowlabel |= ntohl(((__u32)iph->tos << IPV6_TCLASS_SHIFT)
& IPV6_TCLASS_MASK);
err = ip6_tnl_xmit2(skb, dev, dsfield, &fl, encap_limit, &mtu);
if (err != 0) {
/* XXX: send ICMP error even if DF is not set. */
if (err == -EMSGSIZE)
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
return -1;
}
return 0;
}
static inline int static inline int
ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
{ {
...@@ -867,6 +1025,9 @@ ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -867,6 +1025,9 @@ ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
} }
switch (skb->protocol) { switch (skb->protocol) {
case __constant_htons(ETH_P_IP):
ret = ip4ip6_tnl_xmit(skb, dev);
break;
case __constant_htons(ETH_P_IPV6): case __constant_htons(ETH_P_IPV6):
ret = ip6ip6_tnl_xmit(skb, dev); ret = ip6ip6_tnl_xmit(skb, dev);
break; break;
...@@ -1199,6 +1360,12 @@ ip6ip6_fb_tnl_dev_init(struct net_device *dev) ...@@ -1199,6 +1360,12 @@ ip6ip6_fb_tnl_dev_init(struct net_device *dev)
return 0; return 0;
} }
static struct xfrm6_tunnel ip4ip6_handler = {
.handler = ip4ip6_rcv,
.err_handler = ip4ip6_err,
.priority = 1,
};
static struct xfrm6_tunnel ip6ip6_handler = { static struct xfrm6_tunnel ip6ip6_handler = {
.handler = ip6ip6_rcv, .handler = ip6ip6_rcv,
.err_handler = ip6ip6_err, .err_handler = ip6ip6_err,
...@@ -1215,9 +1382,16 @@ static int __init ip6_tunnel_init(void) ...@@ -1215,9 +1382,16 @@ static int __init ip6_tunnel_init(void)
{ {
int err; int err;
if (xfrm6_tunnel_register(&ip4ip6_handler, AF_INET)) {
printk(KERN_ERR "ip4ip6 init: can't register tunnel\n");
err = -EAGAIN;
goto out;
}
if (xfrm6_tunnel_register(&ip6ip6_handler, AF_INET6)) { if (xfrm6_tunnel_register(&ip6ip6_handler, AF_INET6)) {
printk(KERN_ERR "ip6ip6 init: can't register tunnel\n"); printk(KERN_ERR "ip6ip6 init: can't register tunnel\n");
return -EAGAIN; err = -EAGAIN;
goto unreg_ip4ip6;
} }
ip6ip6_fb_tnl_dev = alloc_netdev(sizeof(struct ip6_tnl), "ip6tnl0", ip6ip6_fb_tnl_dev = alloc_netdev(sizeof(struct ip6_tnl), "ip6tnl0",
ip6ip6_tnl_dev_setup); ip6ip6_tnl_dev_setup);
...@@ -1235,6 +1409,9 @@ static int __init ip6_tunnel_init(void) ...@@ -1235,6 +1409,9 @@ static int __init ip6_tunnel_init(void)
return 0; return 0;
fail: fail:
xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6); xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6);
unreg_ip4ip6:
xfrm6_tunnel_deregister(&ip4ip6_handler, AF_INET);
out:
return err; return err;
} }
...@@ -1258,6 +1435,9 @@ static void __exit ip6ip6_destroy_tunnels(void) ...@@ -1258,6 +1435,9 @@ static void __exit ip6ip6_destroy_tunnels(void)
static void __exit ip6_tunnel_cleanup(void) static void __exit ip6_tunnel_cleanup(void)
{ {
if (xfrm6_tunnel_deregister(&ip4ip6_handler, AF_INET))
printk(KERN_INFO "ip4ip6 close: can't deregister tunnel\n");
if (xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6)) if (xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6))
printk(KERN_INFO "ip6ip6 close: can't deregister tunnel\n"); printk(KERN_INFO "ip6ip6 close: can't deregister tunnel\n");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment