diff mbox

[RFT] geneve: implement support for IPv6-based tunnels

Message ID 1443119682-14236-1-git-send-email-linville@tuxdriver.com
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

John W. Linville Sept. 24, 2015, 6:34 p.m. UTC
Signed-off-by: John W. Linville <linville@tuxdriver.com>
---
The IPv6 tunnel_info/metadata paths are untested due to lack of OVS
infrastructure for testing.  The traditional netdev paths have had
reasonable testing by me with some local virt guests over the past
few weeks.

NOTE -- this patch requires the earlier geneve patches merged in the
net tree:

7bbe33ff1896 geneve: use network byte order for destination port config parameter
08399efc6319 geneve: ensure ECN info is handled properly in all tx/rx paths
5eb8f289ac30 geneve: remove vlan-related feature assignment

 drivers/net/geneve.c         | 368 +++++++++++++++++++++++++++++++++++++------
 include/uapi/linux/if_link.h |   1 +
 2 files changed, 318 insertions(+), 51 deletions(-)

Comments

Jiri Benc Sept. 25, 2015, 12:08 p.m. UTC | #1
On Thu, 24 Sep 2015 14:34:42 -0400, John W. Linville wrote:
> +#if IS_ENABLED(CONFIG_IPV6)
> +static netdev_tx_t geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev)
> +{
> +	struct geneve_dev *geneve = netdev_priv(dev);
> +	struct geneve_sock *gs = geneve->sock;
> +	struct ip_tunnel_info *info = NULL;
> +	struct dst_entry *dst = NULL;
> +	struct flowi6 fl6;
> +	__u8 ttl;
> +	__be16 sport;
> +	bool udp_csum;
> +	int err;
> +	bool xnet = !net_eq(geneve->net, dev_net(geneve->dev));
> +
> +	if (geneve->collect_md) {
> +		info = skb_tunnel_info(skb);
> +		if (unlikely(info && info->mode != IP_TUNNEL_INFO_TX)) {
> +			netdev_dbg(dev, "no tunnel metadata\n");
> +			goto tx_error;
> +		}
> +	}

You may get IPv4 tunnel info here. Either a check whether it's really
IPv6 is needed or, better, decide whether to use IPv4 or IPv6 for xmit
based on the tunnel info. See below.

> +static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> +#if IS_ENABLED(CONFIG_IPV6)
> +	struct geneve_dev *geneve = netdev_priv(dev);
> +
> +	if (geneve->remote.sa.sa_family == AF_INET6)
> +		return geneve6_xmit_skb(skb, dev);
> +#endif
> +	return geneve_xmit_skb(skb, dev);
> +}

For metadata based tunnels, there should be no requirement for the
remote to be specified. As the consequence, you cannot decide based on
the remote type.

To be really useful, geneve should open both IPv4 and IPv6 socket when
it's metadata based. Take a look at my recent patchset that does this
for vxlan: http://thread.gmane.org/gmane.linux.network/379282

 Jiri
John W. Linville Sept. 28, 2015, 7:20 p.m. UTC | #2
On Fri, Sep 25, 2015 at 02:08:44PM +0200, Jiri Benc wrote:
> On Thu, 24 Sep 2015 14:34:42 -0400, John W. Linville wrote:
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +static netdev_tx_t geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev)
> > +{
> > +	struct geneve_dev *geneve = netdev_priv(dev);
> > +	struct geneve_sock *gs = geneve->sock;
> > +	struct ip_tunnel_info *info = NULL;
> > +	struct dst_entry *dst = NULL;
> > +	struct flowi6 fl6;
> > +	__u8 ttl;
> > +	__be16 sport;
> > +	bool udp_csum;
> > +	int err;
> > +	bool xnet = !net_eq(geneve->net, dev_net(geneve->dev));
> > +
> > +	if (geneve->collect_md) {
> > +		info = skb_tunnel_info(skb);
> > +		if (unlikely(info && info->mode != IP_TUNNEL_INFO_TX)) {
> > +			netdev_dbg(dev, "no tunnel metadata\n");
> > +			goto tx_error;
> > +		}
> > +	}
> 
> You may get IPv4 tunnel info here. Either a check whether it's really
> IPv6 is needed or, better, decide whether to use IPv4 or IPv6 for xmit
> based on the tunnel info. See below.
> 
> > +static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
> > +{
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +	struct geneve_dev *geneve = netdev_priv(dev);
> > +
> > +	if (geneve->remote.sa.sa_family == AF_INET6)
> > +		return geneve6_xmit_skb(skb, dev);
> > +#endif
> > +	return geneve_xmit_skb(skb, dev);
> > +}
> 
> For metadata based tunnels, there should be no requirement for the
> remote to be specified. As the consequence, you cannot decide based on
> the remote type.

Sure, that makes sense.  I'm testing something now...

> To be really useful, geneve should open both IPv4 and IPv6 socket when
> it's metadata based. Take a look at my recent patchset that does this
> for vxlan: http://thread.gmane.org/gmane.linux.network/379282

OK, that seems simple enough.  So we should just assume that a metadata
tunnel could do either protocol at any time?  Or are there more rules
than that?

John
Jiri Benc Sept. 29, 2015, 4:10 p.m. UTC | #3
On Mon, 28 Sep 2015 15:20:33 -0400, John W. Linville wrote:
> > To be really useful, geneve should open both IPv4 and IPv6 socket when
> > it's metadata based. Take a look at my recent patchset that does this
> > for vxlan: http://thread.gmane.org/gmane.linux.network/379282
> 
> OK, that seems simple enough.  So we should just assume that a metadata
> tunnel could do either protocol at any time?  Or are there more rules
> than that?

That should be it, on egress. On ingress, udp_tun_rx_dst needs to be
called with the appropriate family which seems to be missing from your
patch, too (there's AF_INET unconditionally, currently).

 Jiri
diff mbox

Patch

diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index 8f5c02eed47d..9fa0580b1f7c 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -46,6 +46,16 @@  struct geneve_net {
 
 static int geneve_net_id;
 
+union geneve_addr {
+	struct sockaddr_in sin;
+	struct sockaddr_in6 sin6;
+	struct sockaddr sa;
+};
+
+union geneve_addr geneve_remote_unspec = { .sa.sa_family = AF_UNSPEC, };
+
+#define GENEVE_F_IPV6		0x00000001
+
 /* Pseudo network device */
 struct geneve_dev {
 	struct hlist_node  hlist;	/* vni hash table */
@@ -55,7 +65,8 @@  struct geneve_dev {
 	u8                 vni[3];	/* virtual network ID for tunnel */
 	u8                 ttl;		/* TTL override */
 	u8                 tos;		/* TOS override */
-	struct sockaddr_in remote;	/* IPv4 address for link partner */
+	u32                flags;	/* GENEVE_F_* above */
+	union geneve_addr  remote;	/* IP address for link partner */
 	struct list_head   next;	/* geneve's per namespace list */
 	__be16		   dst_port;
 	bool		   collect_md;
@@ -103,11 +114,32 @@  static struct geneve_dev *geneve_lookup(struct geneve_sock *gs,
 	vni_list_head = &gs->vni_list[hash];
 	hlist_for_each_entry_rcu(geneve, vni_list_head, hlist) {
 		if (!memcmp(vni, geneve->vni, sizeof(geneve->vni)) &&
-		    addr == geneve->remote.sin_addr.s_addr)
+		    addr == geneve->remote.sin.sin_addr.s_addr)
+			return geneve;
+	}
+	return NULL;
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct geneve_dev *geneve6_lookup(struct geneve_sock *gs,
+					 struct in6_addr addr6, u8 vni[])
+{
+	struct hlist_head *vni_list_head;
+	struct geneve_dev *geneve;
+	__u32 hash;
+
+	/* Find the device for this VNI */
+	hash = geneve_net_vni_hash(vni);
+	vni_list_head = &gs->vni_list[hash];
+	hlist_for_each_entry_rcu(geneve, vni_list_head, hlist) {
+		if (!memcmp(vni, geneve->vni, sizeof(geneve->vni)) &&
+		    !memcmp(&addr6, &geneve->remote.sin6.sin6_addr,
+			    sizeof(addr6)))
 			return geneve;
 	}
 	return NULL;
 }
+#endif
 
 static inline struct genevehdr *geneve_hdr(const struct sk_buff *skb)
 {
@@ -121,24 +153,44 @@  static void geneve_rx(struct geneve_sock *gs, struct sk_buff *skb)
 	struct metadata_dst *tun_dst = NULL;
 	struct geneve_dev *geneve = NULL;
 	struct pcpu_sw_netstats *stats;
-	struct iphdr *iph;
-	u8 *vni;
+	struct iphdr *iph = NULL;
 	__be32 addr;
-	int err;
+	static u8 zero_vni[3];
+	u8 *vni;
+	int err = 0;
+#if IS_ENABLED(CONFIG_IPV6)
+	struct ipv6hdr *ip6h = NULL;
+	struct in6_addr addr6;
+	static struct in6_addr zero_addr6;
+#endif
 
-	iph = ip_hdr(skb); /* outer IP header... */
+	if (gs->sock->sk->sk_family == AF_INET) {
+		iph = ip_hdr(skb); /* outer IP header... */
 
-	if (gs->collect_md) {
-		static u8 zero_vni[3];
+		if (gs->collect_md) {
+			vni = zero_vni;
+			addr = 0;
+		} else {
+			vni = gnvh->vni;
 
-		vni = zero_vni;
-		addr = 0;
-	} else {
-		vni = gnvh->vni;
-		addr = iph->saddr;
-	}
+			addr = iph->saddr;
+		}
+
+		geneve = geneve_lookup(gs, addr, vni);
+	} else if (gs->sock->sk->sk_family == AF_INET6) {
+		ip6h = ipv6_hdr(skb); /* outer IPv6 header... */
+
+		if (gs->collect_md) {
+			vni = zero_vni;
+			addr6 = zero_addr6;
+		} else {
+			vni = gnvh->vni;
 
-	geneve = geneve_lookup(gs, addr, vni);
+			addr6 = ip6h->saddr;
+		}
+
+		geneve = geneve6_lookup(gs, addr6, vni);
+	}
 	if (!geneve)
 		goto drop;
 
@@ -179,12 +231,21 @@  static void geneve_rx(struct geneve_sock *gs, struct sk_buff *skb)
 
 	skb_reset_network_header(skb);
 
-	err = IP_ECN_decapsulate(iph, skb);
+	if (iph)
+		err = IP_ECN_decapsulate(iph, skb);
+	if (ip6h)
+		err = IP6_ECN_decapsulate(ip6h, skb);
 
 	if (unlikely(err)) {
-		if (log_ecn_error)
-			net_info_ratelimited("non-ECT from %pI4 with TOS=%#x\n",
-					     &iph->saddr, iph->tos);
+		if (log_ecn_error) {
+			if (iph)
+				net_info_ratelimited("non-ECT from %pI4 "
+						     "with TOS=%#x\n",
+						     &iph->saddr, iph->tos);
+			if (ip6h)
+				net_info_ratelimited("non-ECT from %pI6\n",
+						     &ip6h->saddr);
+		}
 		if (err > 1) {
 			++geneve->dev->stats.rx_frame_errors;
 			++geneve->dev->stats.rx_errors;
@@ -284,6 +345,7 @@  static struct socket *geneve_create_sock(struct net *net, bool ipv6,
 
 	if (ipv6) {
 		udp_conf.family = AF_INET6;
+		udp_conf.ipv6_v6only = 1;
 	} else {
 		udp_conf.family = AF_INET;
 		udp_conf.local_ip.s_addr = htonl(INADDR_ANY);
@@ -470,13 +532,14 @@  static void geneve_sock_release(struct geneve_sock *gs)
 }
 
 static struct geneve_sock *geneve_find_sock(struct geneve_net *gn,
+					    sa_family_t family,
 					    __be16 dst_port)
 {
 	struct geneve_sock *gs;
 
 	list_for_each_entry(gs, &gn->sock_list, list) {
 		if (inet_sk(gs->sock->sk)->inet_sport == dst_port &&
-		    inet_sk(gs->sock->sk)->sk.sk_family == AF_INET) {
+		    inet_sk(gs->sock->sk)->sk.sk_family == family) {
 			return gs;
 		}
 	}
@@ -490,14 +553,15 @@  static int geneve_open(struct net_device *dev)
 	struct geneve_net *gn = net_generic(net, geneve_net_id);
 	struct geneve_sock *gs;
 	__u32 hash;
+	bool ipv6 = !!(geneve->flags & GENEVE_F_IPV6);
 
-	gs = geneve_find_sock(gn, geneve->dst_port);
+	gs = geneve_find_sock(gn, ipv6 ? AF_INET6 : AF_INET, geneve->dst_port);
 	if (gs) {
 		gs->refcnt++;
 		goto out;
 	}
 
-	gs = geneve_socket_create(net, geneve->dst_port, false);
+	gs = geneve_socket_create(net, geneve->dst_port, ipv6);
 	if (IS_ERR(gs))
 		return PTR_ERR(gs);
 
@@ -521,6 +585,22 @@  static int geneve_stop(struct net_device *dev)
 	return 0;
 }
 
+static void geneve_build_header(struct genevehdr *geneveh,
+				__be16 tun_flags, u8 vni[3],
+				u8 options_len, u8 *options)
+{
+	geneveh->ver = GENEVE_VER;
+	geneveh->opt_len = options_len / 4;
+	geneveh->oam = !!(tun_flags & TUNNEL_OAM);
+	geneveh->critical = !!(tun_flags & TUNNEL_CRIT_OPT);
+	geneveh->rsvd1 = 0;
+	memcpy(geneveh->vni, vni, 3);
+	geneveh->proto_type = htons(ETH_P_TEB);
+	geneveh->rsvd2 = 0;
+
+	memcpy(geneveh->options, options, options_len);
+}
+
 static int geneve_build_skb(struct rtable *rt, struct sk_buff *skb,
 			    __be16 tun_flags, u8 vni[3], u8 opt_len, u8 *opt,
 			    bool csum)
@@ -544,15 +624,7 @@  static int geneve_build_skb(struct rtable *rt, struct sk_buff *skb,
 	}
 
 	gnvh = (struct genevehdr *)__skb_push(skb, sizeof(*gnvh) + opt_len);
-	gnvh->ver = GENEVE_VER;
-	gnvh->opt_len = opt_len / 4;
-	gnvh->oam = !!(tun_flags & TUNNEL_OAM);
-	gnvh->critical = !!(tun_flags & TUNNEL_CRIT_OPT);
-	gnvh->rsvd1 = 0;
-	memcpy(gnvh->vni, vni, 3);
-	gnvh->proto_type = htons(ETH_P_TEB);
-	gnvh->rsvd2 = 0;
-	memcpy(gnvh->options, opt, opt_len);
+	geneve_build_header(gnvh, tun_flags, vni, opt_len, opt);
 
 	skb_set_inner_protocol(skb, htons(ETH_P_TEB));
 	return 0;
@@ -562,6 +634,79 @@  free_rt:
 	return err;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static int geneve6_build_skb(struct dst_entry *dst, struct sk_buff *skb,
+			     __be16 tun_flags, u8 vni[3], u8 opt_len, u8 *opt,
+			     bool csum, bool xnet)
+{
+	struct genevehdr *gnvh;
+	int min_headroom;
+	int err;
+
+	skb_scrub_packet(skb, xnet);
+
+	min_headroom = LL_RESERVED_SPACE(dst->dev) + dst->header_len
+			+ GENEVE_BASE_HLEN + opt_len + sizeof(struct ipv6hdr);
+	err = skb_cow_head(skb, min_headroom);
+	if (unlikely(err)) {
+		kfree_skb(skb);
+		goto free_dst;
+	}
+
+	skb = udp_tunnel_handle_offloads(skb, csum);
+	if (IS_ERR(skb)) {
+		err = PTR_ERR(skb);
+		goto free_dst;
+	}
+
+	gnvh = (struct genevehdr *)__skb_push(skb, sizeof(*gnvh) + opt_len);
+	geneve_build_header(gnvh, tun_flags, vni, opt_len, opt);
+
+	skb_set_inner_protocol(skb, htons(ETH_P_TEB));
+	return 0;
+
+free_dst:
+	dst_release(dst);
+	return err;
+}
+#endif
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct dst_entry *geneve_get_dst(struct sk_buff *skb,
+					struct net_device *dev,
+					struct flowi6 *fl6,
+					struct ip_tunnel_info *info)
+{
+	struct geneve_dev *geneve = netdev_priv(dev);
+	struct geneve_sock *gs = geneve->sock;
+	struct dst_entry *dst = NULL;
+
+	memset(fl6, 0, sizeof(*fl6));
+	fl6->flowi6_mark = skb->mark;
+	fl6->flowi6_proto = IPPROTO_UDP;
+
+	if (info) {
+		fl6->daddr = info->key.u.ipv6.dst;
+		fl6->saddr = info->key.u.ipv6.src;
+	} else {
+		fl6->daddr = geneve->remote.sin6.sin6_addr;
+	}
+
+	if (ipv6_stub->ipv6_dst_lookup(geneve->net, gs->sock->sk, &dst, fl6)) {
+		netdev_dbg(dev, "no route to %pI6\n", &fl6->daddr);
+		dev->stats.tx_carrier_errors++;
+		return ERR_PTR(-EHOSTUNREACH);
+	}
+	if (dst->dev == dev) { /* is this necessary? */
+		netdev_dbg(dev, "circular route to %pI6\n", &fl6->daddr);
+		dev->stats.collisions++;
+		dst_release(dst);
+		return ERR_PTR(-EINVAL);
+	}
+	return dst;
+}
+#endif
+
 static struct rtable *geneve_get_rt(struct sk_buff *skb,
 				    struct net_device *dev,
 				    struct flowi4 *fl4,
@@ -588,7 +733,7 @@  static struct rtable *geneve_get_rt(struct sk_buff *skb,
 		}
 
 		fl4->flowi4_tos = RT_TOS(tos);
-		fl4->daddr = geneve->remote.sin_addr.s_addr;
+		fl4->daddr = geneve->remote.sin.sin_addr.s_addr;
 	}
 
 	rt = ip_route_output_key(geneve->net, fl4);
@@ -620,7 +765,7 @@  static void tunnel_id_to_vni(__be64 tun_id, __u8 *vni)
 #endif
 }
 
-static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev)
 {
 	struct geneve_dev *geneve = netdev_priv(dev);
 	struct geneve_sock *gs = geneve->sock;
@@ -703,6 +848,93 @@  err:
 	return NETDEV_TX_OK;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static netdev_tx_t geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev)
+{
+	struct geneve_dev *geneve = netdev_priv(dev);
+	struct geneve_sock *gs = geneve->sock;
+	struct ip_tunnel_info *info = NULL;
+	struct dst_entry *dst = NULL;
+	struct flowi6 fl6;
+	__u8 ttl;
+	__be16 sport;
+	bool udp_csum;
+	int err;
+	bool xnet = !net_eq(geneve->net, dev_net(geneve->dev));
+
+	if (geneve->collect_md) {
+		info = skb_tunnel_info(skb);
+		if (unlikely(info && info->mode != IP_TUNNEL_INFO_TX)) {
+			netdev_dbg(dev, "no tunnel metadata\n");
+			goto tx_error;
+		}
+	}
+
+	dst = geneve_get_dst(skb, dev, &fl6, info);
+	if (IS_ERR(dst)) {
+		netdev_dbg(dev, "no route to %pI6\n", &fl6.daddr);
+		dev->stats.tx_carrier_errors++;
+		goto tx_error;
+	}
+
+	sport = udp_flow_src_port(geneve->net, skb, 1, USHRT_MAX, true);
+	skb_reset_mac_header(skb);
+
+	if (info) {
+		const struct ip_tunnel_key *key = &info->key;
+		u8 *opts = NULL;
+		u8 vni[3];
+
+		tunnel_id_to_vni(key->tun_id, vni);
+		if (key->tun_flags & TUNNEL_GENEVE_OPT)
+			opts = ip_tunnel_info_opts(info);
+
+		udp_csum = !!(key->tun_flags & TUNNEL_CSUM);
+		err = geneve6_build_skb(dst, skb, key->tun_flags, vni,
+					info->options_len, opts,
+					udp_csum, xnet);
+		if (unlikely(err))
+			goto err;
+
+		ttl = key->ttl;
+	} else {
+		udp_csum = false;
+		err = geneve6_build_skb(dst, skb, 0, geneve->vni,
+					0, NULL, udp_csum, xnet);
+		if (unlikely(err))
+			goto err;
+
+		ttl = geneve->ttl;
+		if (!ttl && ipv6_addr_is_multicast(&fl6.daddr))
+			ttl = 1;
+		ttl = ttl ? : ip6_dst_hoplimit(dst);
+	}
+	err = udp_tunnel6_xmit_skb(dst, gs->sock->sk, skb, dev,
+				   &fl6.saddr, &fl6.daddr, 0, ttl,
+				   sport, geneve->dst_port, !udp_csum);
+
+	iptunnel_xmit_stats(err, &dev->stats, dev->tstats);
+	return NETDEV_TX_OK;
+
+tx_error:
+	dev_kfree_skb(skb);
+err:
+	dev->stats.tx_errors++;
+	return NETDEV_TX_OK;
+}
+#endif
+
+static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+	struct geneve_dev *geneve = netdev_priv(dev);
+
+	if (geneve->remote.sa.sa_family == AF_INET6)
+		return geneve6_xmit_skb(skb, dev);
+#endif
+	return geneve_xmit_skb(skb, dev);
+}
+
 static const struct net_device_ops geneve_netdev_ops = {
 	.ndo_init		= geneve_init,
 	.ndo_uninit		= geneve_uninit,
@@ -759,6 +991,7 @@  static void geneve_setup(struct net_device *dev)
 static const struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = {
 	[IFLA_GENEVE_ID]		= { .type = NLA_U32 },
 	[IFLA_GENEVE_REMOTE]		= { .len = FIELD_SIZEOF(struct iphdr, daddr) },
+	[IFLA_GENEVE_REMOTE6]		= { .len = sizeof(struct in6_addr) },
 	[IFLA_GENEVE_TTL]		= { .type = NLA_U8 },
 	[IFLA_GENEVE_TOS]		= { .type = NLA_U8 },
 	[IFLA_GENEVE_PORT]		= { .type = NLA_U16 },
@@ -790,7 +1023,7 @@  static int geneve_validate(struct nlattr *tb[], struct nlattr *data[])
 
 static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
 					  __be16 dst_port,
-					  __be32 rem_addr,
+					  union geneve_addr *remote,
 					  u8 vni[],
 					  bool *tun_on_same_port,
 					  bool *tun_collect_md)
@@ -806,7 +1039,7 @@  static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
 			*tun_on_same_port = true;
 		}
 		if (!memcmp(vni, geneve->vni, sizeof(geneve->vni)) &&
-		    rem_addr == geneve->remote.sin_addr.s_addr &&
+		    !memcmp(remote, &geneve->remote, sizeof(geneve->remote)) &&
 		    dst_port == geneve->dst_port)
 			t = geneve;
 	}
@@ -814,8 +1047,9 @@  static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
 }
 
 static int geneve_configure(struct net *net, struct net_device *dev,
-			    __be32 rem_addr, __u32 vni, __u8 ttl, __u8 tos,
-			    __be16 dst_port, bool metadata)
+			    union geneve_addr *remote,
+			    __u32 vni, __u8 ttl, __u8 tos, __be16 dst_port,
+			    bool metadata)
 {
 	struct geneve_net *gn = net_generic(net, geneve_net_id);
 	struct geneve_dev *t, *geneve = netdev_priv(dev);
@@ -823,9 +1057,11 @@  static int geneve_configure(struct net *net, struct net_device *dev,
 	int err;
 
 	if (metadata) {
-		if (rem_addr || vni || tos || ttl)
+		if (remote != &geneve_remote_unspec || vni || tos || ttl)
 			return -EINVAL;
 	}
+	if (!remote)
+		return -EINVAL;
 
 	geneve->net = net;
 	geneve->dev = dev;
@@ -834,16 +1070,22 @@  static int geneve_configure(struct net *net, struct net_device *dev,
 	geneve->vni[1] = (vni & 0x0000ff00) >> 8;
 	geneve->vni[2] =  vni & 0x000000ff;
 
-	geneve->remote.sin_addr.s_addr = rem_addr;
-	if (IN_MULTICAST(ntohl(geneve->remote.sin_addr.s_addr)))
+	if ((remote->sa.sa_family == AF_INET &&
+	     IN_MULTICAST(ntohl(remote->sin.sin_addr.s_addr))) ||
+	    (remote->sa.sa_family == AF_INET6 &&
+	     ipv6_addr_is_multicast(&remote->sin6.sin6_addr)))
 		return -EINVAL;
+	geneve->remote = *remote;
+
+	if (geneve->remote.sa.sa_family == AF_INET6)
+		geneve->flags |= GENEVE_F_IPV6;
 
 	geneve->ttl = ttl;
 	geneve->tos = tos;
 	geneve->dst_port = dst_port;
 	geneve->collect_md = metadata;
 
-	t = geneve_find_dev(gn, dst_port, rem_addr, geneve->vni,
+	t = geneve_find_dev(gn, dst_port, remote, geneve->vni,
 			    &tun_on_same_port, &tun_collect_md);
 	if (t)
 		return -EBUSY;
@@ -870,14 +1112,29 @@  static int geneve_newlink(struct net *net, struct net_device *dev,
 	__be16 dst_port = htons(GENEVE_UDP_PORT);
 	__u8 ttl = 0, tos = 0;
 	bool metadata = false;
-	__be32 rem_addr;
+	union geneve_addr remote;
 	__u32 vni;
 
-	if (!data[IFLA_GENEVE_ID] || !data[IFLA_GENEVE_REMOTE])
+	if (!data[IFLA_GENEVE_ID] ||
+	    (data[IFLA_GENEVE_REMOTE] && data[IFLA_GENEVE_REMOTE6]) ||
+	    (!data[IFLA_GENEVE_REMOTE] && !data[IFLA_GENEVE_REMOTE6]))
 		return -EINVAL;
 
 	vni = nla_get_u32(data[IFLA_GENEVE_ID]);
-	rem_addr = nla_get_in_addr(data[IFLA_GENEVE_REMOTE]);
+
+	memset(&remote, 0, sizeof(remote));
+	if (data[IFLA_GENEVE_REMOTE]) {
+		remote.sa.sa_family = AF_INET;
+		remote.sin.sin_addr.s_addr =
+			nla_get_in_addr(data[IFLA_GENEVE_REMOTE]);
+	} else if (data[IFLA_GENEVE_REMOTE6]) {
+		if (!IS_ENABLED(CONFIG_IPV6))
+			return -EPFNOSUPPORT;
+
+		remote.sa.sa_family = AF_INET6;
+		remote.sin6.sin6_addr =
+			nla_get_in6_addr(data[IFLA_GENEVE_REMOTE6]);
+	}
 
 	if (data[IFLA_GENEVE_TTL])
 		ttl = nla_get_u8(data[IFLA_GENEVE_TTL]);
@@ -891,8 +1148,8 @@  static int geneve_newlink(struct net *net, struct net_device *dev,
 	if (data[IFLA_GENEVE_COLLECT_METADATA])
 		metadata = true;
 
-	return geneve_configure(net, dev, rem_addr, vni,
-				ttl, tos, dst_port, metadata);
+	return geneve_configure(net, dev, &remote, vni, ttl, tos, dst_port,
+				metadata);
 }
 
 static void geneve_dellink(struct net_device *dev, struct list_head *head)
@@ -906,7 +1163,7 @@  static void geneve_dellink(struct net_device *dev, struct list_head *head)
 static size_t geneve_get_size(const struct net_device *dev)
 {
 	return nla_total_size(sizeof(__u32)) +	/* IFLA_GENEVE_ID */
-		nla_total_size(sizeof(struct in_addr)) + /* IFLA_GENEVE_REMOTE */
+		nla_total_size(sizeof(struct in6_addr)) + /* IFLA_GENEVE_REMOTE{6} */
 		nla_total_size(sizeof(__u8)) +  /* IFLA_GENEVE_TTL */
 		nla_total_size(sizeof(__u8)) +  /* IFLA_GENEVE_TOS */
 		nla_total_size(sizeof(__be16)) +  /* IFLA_GENEVE_PORT */
@@ -923,9 +1180,17 @@  static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
 	if (nla_put_u32(skb, IFLA_GENEVE_ID, vni))
 		goto nla_put_failure;
 
-	if (nla_put_in_addr(skb, IFLA_GENEVE_REMOTE,
-			    geneve->remote.sin_addr.s_addr))
-		goto nla_put_failure;
+	if (geneve->remote.sa.sa_family == AF_INET) {
+		if (nla_put_in_addr(skb, IFLA_GENEVE_REMOTE,
+				    geneve->remote.sin.sin_addr.s_addr))
+			goto nla_put_failure;
+#if IS_ENABLED(CONFIG_IPV6)
+	} else {
+		if (nla_put_in6_addr(skb, IFLA_GENEVE_REMOTE6,
+				     &geneve->remote.sin6.sin6_addr))
+			goto nla_put_failure;
+#endif
+	}
 
 	if (nla_put_u8(skb, IFLA_GENEVE_TTL, geneve->ttl) ||
 	    nla_put_u8(skb, IFLA_GENEVE_TOS, geneve->tos))
@@ -971,7 +1236,8 @@  struct net_device *geneve_dev_create_fb(struct net *net, const char *name,
 	if (IS_ERR(dev))
 		return dev;
 
-	err = geneve_configure(net, dev, 0, 0, 0, 0, htons(dst_port), true);
+	err = geneve_configure(net, dev, &geneve_remote_unspec,
+			       0, 0, 0, htons(dst_port), true);
 	if (err) {
 		free_netdev(dev);
 		return ERR_PTR(err);
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 3a5f263cfc2f..b0cd0498ce5a 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -412,6 +412,7 @@  enum {
 	IFLA_GENEVE_TOS,
 	IFLA_GENEVE_PORT,	/* destination port */
 	IFLA_GENEVE_COLLECT_METADATA,
+	IFLA_GENEVE_REMOTE6,
 	__IFLA_GENEVE_MAX
 };
 #define IFLA_GENEVE_MAX	(__IFLA_GENEVE_MAX - 1)