gue: Receive side for Generic UDP Encapsulation
This patch adds support receiving for GUE packets in the fou module. The fou module now supports direct foo-over-udp (no encapsulation header) and GUE. To support this a type parameter is added to the fou netlink parameters. For a GUE socket we define gue_udp_recv, gue_gro_receive, and gue_gro_complete to handle the specifics of the GUE protocol. Most of the code to manage and configure sockets is common with the fou. Signed-off-by: Tom Herbert <therbert@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
					parent
					
						
							
								efc98d08e1
							
						
					
				
			
			
				commit
				
					
						37dd024779
					
				
			
		
					 3 changed files with 217 additions and 9 deletions
				
			
		
							
								
								
									
										23
									
								
								include/net/gue.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								include/net/gue.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | #ifndef __NET_GUE_H | ||||||
|  | #define __NET_GUE_H | ||||||
|  | 
 | ||||||
|  | struct guehdr { | ||||||
|  | 	union { | ||||||
|  | 		struct { | ||||||
|  | #if defined(__LITTLE_ENDIAN_BITFIELD) | ||||||
|  | 			__u8	hlen:4, | ||||||
|  | 			version:4; | ||||||
|  | #elif defined (__BIG_ENDIAN_BITFIELD) | ||||||
|  | 			__u8	version:4, | ||||||
|  | 				hlen:4; | ||||||
|  | #else | ||||||
|  | #error  "Please fix <asm/byteorder.h>" | ||||||
|  | #endif | ||||||
|  | 			__u8    next_hdr; | ||||||
|  | 			__u16   flags; | ||||||
|  | 		}; | ||||||
|  | 		__u32 word; | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
|  | @ -13,6 +13,7 @@ enum { | ||||||
| 	FOU_ATTR_PORT,				/* u16 */ | 	FOU_ATTR_PORT,				/* u16 */ | ||||||
| 	FOU_ATTR_AF,				/* u8 */ | 	FOU_ATTR_AF,				/* u8 */ | ||||||
| 	FOU_ATTR_IPPROTO,			/* u8 */ | 	FOU_ATTR_IPPROTO,			/* u8 */ | ||||||
|  | 	FOU_ATTR_TYPE,				/* u8 */ | ||||||
| 
 | 
 | ||||||
| 	__FOU_ATTR_MAX, | 	__FOU_ATTR_MAX, | ||||||
| }; | }; | ||||||
|  | @ -27,6 +28,12 @@ enum { | ||||||
| 	__FOU_CMD_MAX, | 	__FOU_CMD_MAX, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | enum { | ||||||
|  | 	FOU_ENCAP_UNSPEC, | ||||||
|  | 	FOU_ENCAP_DIRECT, | ||||||
|  | 	FOU_ENCAP_GUE, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| #define FOU_CMD_MAX	(__FOU_CMD_MAX - 1) | #define FOU_CMD_MAX	(__FOU_CMD_MAX - 1) | ||||||
| 
 | 
 | ||||||
| #endif /* _UAPI_LINUX_FOU_H */ | #endif /* _UAPI_LINUX_FOU_H */ | ||||||
|  |  | ||||||
							
								
								
									
										196
									
								
								net/ipv4/fou.c
									
										
									
									
									
								
							
							
						
						
									
										196
									
								
								net/ipv4/fou.c
									
										
									
									
									
								
							|  | @ -7,6 +7,7 @@ | ||||||
| #include <linux/types.h> | #include <linux/types.h> | ||||||
| #include <linux/kernel.h> | #include <linux/kernel.h> | ||||||
| #include <net/genetlink.h> | #include <net/genetlink.h> | ||||||
|  | #include <net/gue.h> | ||||||
| #include <net/ip.h> | #include <net/ip.h> | ||||||
| #include <net/protocol.h> | #include <net/protocol.h> | ||||||
| #include <net/udp.h> | #include <net/udp.h> | ||||||
|  | @ -27,6 +28,7 @@ struct fou { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct fou_cfg { | struct fou_cfg { | ||||||
|  | 	u16 type; | ||||||
| 	u8 protocol; | 	u8 protocol; | ||||||
| 	struct udp_port_cfg udp_config; | 	struct udp_port_cfg udp_config; | ||||||
| }; | }; | ||||||
|  | @ -64,6 +66,41 @@ static int fou_udp_recv(struct sock *sk, struct sk_buff *skb) | ||||||
| 					  sizeof(struct udphdr)); | 					  sizeof(struct udphdr)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int gue_udp_recv(struct sock *sk, struct sk_buff *skb) | ||||||
|  | { | ||||||
|  | 	struct fou *fou = fou_from_sock(sk); | ||||||
|  | 	size_t len; | ||||||
|  | 	struct guehdr *guehdr; | ||||||
|  | 	struct udphdr *uh; | ||||||
|  | 
 | ||||||
|  | 	if (!fou) | ||||||
|  | 		return 1; | ||||||
|  | 
 | ||||||
|  | 	len = sizeof(struct udphdr) + sizeof(struct guehdr); | ||||||
|  | 	if (!pskb_may_pull(skb, len)) | ||||||
|  | 		goto drop; | ||||||
|  | 
 | ||||||
|  | 	uh = udp_hdr(skb); | ||||||
|  | 	guehdr = (struct guehdr *)&uh[1]; | ||||||
|  | 
 | ||||||
|  | 	len += guehdr->hlen << 2; | ||||||
|  | 	if (!pskb_may_pull(skb, len)) | ||||||
|  | 		goto drop; | ||||||
|  | 
 | ||||||
|  | 	if (guehdr->version != 0) | ||||||
|  | 		goto drop; | ||||||
|  | 
 | ||||||
|  | 	if (guehdr->flags) { | ||||||
|  | 		/* No support yet */ | ||||||
|  | 		goto drop; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fou_udp_encap_recv_deliver(skb, guehdr->next_hdr, len); | ||||||
|  | drop: | ||||||
|  | 	kfree_skb(skb); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static struct sk_buff **fou_gro_receive(struct sk_buff **head, | static struct sk_buff **fou_gro_receive(struct sk_buff **head, | ||||||
| 					struct sk_buff *skb) | 					struct sk_buff *skb) | ||||||
| { | { | ||||||
|  | @ -107,6 +144,112 @@ out_unlock: | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static struct sk_buff **gue_gro_receive(struct sk_buff **head, | ||||||
|  | 					struct sk_buff *skb) | ||||||
|  | { | ||||||
|  | 	const struct net_offload **offloads; | ||||||
|  | 	const struct net_offload *ops; | ||||||
|  | 	struct sk_buff **pp = NULL; | ||||||
|  | 	struct sk_buff *p; | ||||||
|  | 	u8 proto; | ||||||
|  | 	struct guehdr *guehdr; | ||||||
|  | 	unsigned int hlen, guehlen; | ||||||
|  | 	unsigned int off; | ||||||
|  | 	int flush = 1; | ||||||
|  | 
 | ||||||
|  | 	off = skb_gro_offset(skb); | ||||||
|  | 	hlen = off + sizeof(*guehdr); | ||||||
|  | 	guehdr = skb_gro_header_fast(skb, off); | ||||||
|  | 	if (skb_gro_header_hard(skb, hlen)) { | ||||||
|  | 		guehdr = skb_gro_header_slow(skb, hlen, off); | ||||||
|  | 		if (unlikely(!guehdr)) | ||||||
|  | 			goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	proto = guehdr->next_hdr; | ||||||
|  | 
 | ||||||
|  | 	rcu_read_lock(); | ||||||
|  | 	offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads; | ||||||
|  | 	ops = rcu_dereference(offloads[proto]); | ||||||
|  | 	if (WARN_ON(!ops || !ops->callbacks.gro_receive)) | ||||||
|  | 		goto out_unlock; | ||||||
|  | 
 | ||||||
|  | 	guehlen = sizeof(*guehdr) + (guehdr->hlen << 2); | ||||||
|  | 
 | ||||||
|  | 	hlen = off + guehlen; | ||||||
|  | 	if (skb_gro_header_hard(skb, hlen)) { | ||||||
|  | 		guehdr = skb_gro_header_slow(skb, hlen, off); | ||||||
|  | 		if (unlikely(!guehdr)) | ||||||
|  | 			goto out_unlock; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	flush = 0; | ||||||
|  | 
 | ||||||
|  | 	for (p = *head; p; p = p->next) { | ||||||
|  | 		const struct guehdr *guehdr2; | ||||||
|  | 
 | ||||||
|  | 		if (!NAPI_GRO_CB(p)->same_flow) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		guehdr2 = (struct guehdr *)(p->data + off); | ||||||
|  | 
 | ||||||
|  | 		/* Compare base GUE header to be equal (covers
 | ||||||
|  | 		 * hlen, version, next_hdr, and flags. | ||||||
|  | 		 */ | ||||||
|  | 		if (guehdr->word != guehdr2->word) { | ||||||
|  | 			NAPI_GRO_CB(p)->same_flow = 0; | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/* Compare optional fields are the same. */ | ||||||
|  | 		if (guehdr->hlen && memcmp(&guehdr[1], &guehdr2[1], | ||||||
|  | 					   guehdr->hlen << 2)) { | ||||||
|  | 			NAPI_GRO_CB(p)->same_flow = 0; | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	skb_gro_pull(skb, guehlen); | ||||||
|  | 
 | ||||||
|  | 	/* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/ | ||||||
|  | 	skb_gro_postpull_rcsum(skb, guehdr, guehlen); | ||||||
|  | 
 | ||||||
|  | 	pp = ops->callbacks.gro_receive(head, skb); | ||||||
|  | 
 | ||||||
|  | out_unlock: | ||||||
|  | 	rcu_read_unlock(); | ||||||
|  | out: | ||||||
|  | 	NAPI_GRO_CB(skb)->flush |= flush; | ||||||
|  | 
 | ||||||
|  | 	return pp; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int gue_gro_complete(struct sk_buff *skb, int nhoff) | ||||||
|  | { | ||||||
|  | 	const struct net_offload **offloads; | ||||||
|  | 	struct guehdr *guehdr = (struct guehdr *)(skb->data + nhoff); | ||||||
|  | 	const struct net_offload *ops; | ||||||
|  | 	unsigned int guehlen; | ||||||
|  | 	u8 proto; | ||||||
|  | 	int err = -ENOENT; | ||||||
|  | 
 | ||||||
|  | 	proto = guehdr->next_hdr; | ||||||
|  | 
 | ||||||
|  | 	guehlen = sizeof(*guehdr) + (guehdr->hlen << 2); | ||||||
|  | 
 | ||||||
|  | 	rcu_read_lock(); | ||||||
|  | 	offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads; | ||||||
|  | 	ops = rcu_dereference(offloads[proto]); | ||||||
|  | 	if (WARN_ON(!ops || !ops->callbacks.gro_complete)) | ||||||
|  | 		goto out_unlock; | ||||||
|  | 
 | ||||||
|  | 	err = ops->callbacks.gro_complete(skb, nhoff + guehlen); | ||||||
|  | 
 | ||||||
|  | out_unlock: | ||||||
|  | 	rcu_read_unlock(); | ||||||
|  | 	return err; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int fou_add_to_port_list(struct fou *fou) | static int fou_add_to_port_list(struct fou *fou) | ||||||
| { | { | ||||||
| 	struct fou *fout; | 	struct fou *fout; | ||||||
|  | @ -142,6 +285,28 @@ static void fou_release(struct fou *fou) | ||||||
| 	kfree(fou); | 	kfree(fou); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int fou_encap_init(struct sock *sk, struct fou *fou, struct fou_cfg *cfg) | ||||||
|  | { | ||||||
|  | 	udp_sk(sk)->encap_rcv = fou_udp_recv; | ||||||
|  | 	fou->protocol = cfg->protocol; | ||||||
|  | 	fou->udp_offloads.callbacks.gro_receive = fou_gro_receive; | ||||||
|  | 	fou->udp_offloads.callbacks.gro_complete = fou_gro_complete; | ||||||
|  | 	fou->udp_offloads.port = cfg->udp_config.local_udp_port; | ||||||
|  | 	fou->udp_offloads.ipproto = cfg->protocol; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int gue_encap_init(struct sock *sk, struct fou *fou, struct fou_cfg *cfg) | ||||||
|  | { | ||||||
|  | 	udp_sk(sk)->encap_rcv = gue_udp_recv; | ||||||
|  | 	fou->udp_offloads.callbacks.gro_receive = gue_gro_receive; | ||||||
|  | 	fou->udp_offloads.callbacks.gro_complete = gue_gro_complete; | ||||||
|  | 	fou->udp_offloads.port = cfg->udp_config.local_udp_port; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int fou_create(struct net *net, struct fou_cfg *cfg, | static int fou_create(struct net *net, struct fou_cfg *cfg, | ||||||
| 		      struct socket **sockp) | 		      struct socket **sockp) | ||||||
| { | { | ||||||
|  | @ -164,10 +329,24 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, | ||||||
| 
 | 
 | ||||||
| 	sk = sock->sk; | 	sk = sock->sk; | ||||||
| 
 | 
 | ||||||
| 	/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */ | 	fou->port = cfg->udp_config.local_udp_port; | ||||||
| 	fou->protocol = cfg->protocol; | 
 | ||||||
| 	fou->port =  cfg->udp_config.local_udp_port; | 	/* Initial for fou type */ | ||||||
| 	udp_sk(sk)->encap_rcv = fou_udp_recv; | 	switch (cfg->type) { | ||||||
|  | 	case FOU_ENCAP_DIRECT: | ||||||
|  | 		err = fou_encap_init(sk, fou, cfg); | ||||||
|  | 		if (err) | ||||||
|  | 			goto error; | ||||||
|  | 		break; | ||||||
|  | 	case FOU_ENCAP_GUE: | ||||||
|  | 		err = gue_encap_init(sk, fou, cfg); | ||||||
|  | 		if (err) | ||||||
|  | 			goto error; | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		err = -EINVAL; | ||||||
|  | 		goto error; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	udp_sk(sk)->encap_type = 1; | 	udp_sk(sk)->encap_type = 1; | ||||||
| 	udp_encap_enable(); | 	udp_encap_enable(); | ||||||
|  | @ -179,11 +358,6 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, | ||||||
| 
 | 
 | ||||||
| 	sk->sk_allocation = GFP_ATOMIC; | 	sk->sk_allocation = GFP_ATOMIC; | ||||||
| 
 | 
 | ||||||
| 	fou->udp_offloads.callbacks.gro_receive = fou_gro_receive; |  | ||||||
| 	fou->udp_offloads.callbacks.gro_complete = fou_gro_complete; |  | ||||||
| 	fou->udp_offloads.port = cfg->udp_config.local_udp_port; |  | ||||||
| 	fou->udp_offloads.ipproto = cfg->protocol; |  | ||||||
| 
 |  | ||||||
| 	if (cfg->udp_config.family == AF_INET) { | 	if (cfg->udp_config.family == AF_INET) { | ||||||
| 		err = udp_add_offload(&fou->udp_offloads); | 		err = udp_add_offload(&fou->udp_offloads); | ||||||
| 		if (err) | 		if (err) | ||||||
|  | @ -240,6 +414,7 @@ static struct nla_policy fou_nl_policy[FOU_ATTR_MAX + 1] = { | ||||||
| 	[FOU_ATTR_PORT] = { .type = NLA_U16, }, | 	[FOU_ATTR_PORT] = { .type = NLA_U16, }, | ||||||
| 	[FOU_ATTR_AF] = { .type = NLA_U8, }, | 	[FOU_ATTR_AF] = { .type = NLA_U8, }, | ||||||
| 	[FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, | 	[FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, | ||||||
|  | 	[FOU_ATTR_TYPE] = { .type = NLA_U8, }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static int parse_nl_config(struct genl_info *info, | static int parse_nl_config(struct genl_info *info, | ||||||
|  | @ -267,6 +442,9 @@ static int parse_nl_config(struct genl_info *info, | ||||||
| 	if (info->attrs[FOU_ATTR_IPPROTO]) | 	if (info->attrs[FOU_ATTR_IPPROTO]) | ||||||
| 		cfg->protocol = nla_get_u8(info->attrs[FOU_ATTR_IPPROTO]); | 		cfg->protocol = nla_get_u8(info->attrs[FOU_ATTR_IPPROTO]); | ||||||
| 
 | 
 | ||||||
|  | 	if (info->attrs[FOU_ATTR_TYPE]) | ||||||
|  | 		cfg->type = nla_get_u8(info->attrs[FOU_ATTR_TYPE]); | ||||||
|  | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tom Herbert
				Tom Herbert