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_AF,				/* u8 */ | ||||
| 	FOU_ATTR_IPPROTO,			/* u8 */ | ||||
| 	FOU_ATTR_TYPE,				/* u8 */ | ||||
| 
 | ||||
| 	__FOU_ATTR_MAX, | ||||
| }; | ||||
|  | @ -27,6 +28,12 @@ enum { | |||
| 	__FOU_CMD_MAX, | ||||
| }; | ||||
| 
 | ||||
| enum { | ||||
| 	FOU_ENCAP_UNSPEC, | ||||
| 	FOU_ENCAP_DIRECT, | ||||
| 	FOU_ENCAP_GUE, | ||||
| }; | ||||
| 
 | ||||
| #define FOU_CMD_MAX	(__FOU_CMD_MAX - 1) | ||||
| 
 | ||||
| #endif /* _UAPI_LINUX_FOU_H */ | ||||
|  |  | |||
							
								
								
									
										194
									
								
								net/ipv4/fou.c
									
										
									
									
									
								
							
							
						
						
									
										194
									
								
								net/ipv4/fou.c
									
										
									
									
									
								
							|  | @ -7,6 +7,7 @@ | |||
| #include <linux/types.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <net/genetlink.h> | ||||
| #include <net/gue.h> | ||||
| #include <net/ip.h> | ||||
| #include <net/protocol.h> | ||||
| #include <net/udp.h> | ||||
|  | @ -27,6 +28,7 @@ struct fou { | |||
| }; | ||||
| 
 | ||||
| struct fou_cfg { | ||||
| 	u16 type; | ||||
| 	u8 protocol; | ||||
| 	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)); | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
| 					struct sk_buff *skb) | ||||
| { | ||||
|  | @ -107,6 +144,112 @@ out_unlock: | |||
| 	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) | ||||
| { | ||||
| 	struct fou *fout; | ||||
|  | @ -142,6 +285,28 @@ static void fou_release(struct fou *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, | ||||
| 		      struct socket **sockp) | ||||
| { | ||||
|  | @ -164,10 +329,24 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, | |||
| 
 | ||||
| 	sk = sock->sk; | ||||
| 
 | ||||
| 	/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */ | ||||
| 	fou->protocol = cfg->protocol; | ||||
| 	fou->port = cfg->udp_config.local_udp_port; | ||||
| 	udp_sk(sk)->encap_rcv = fou_udp_recv; | ||||
| 
 | ||||
| 	/* Initial for fou type */ | ||||
| 	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_encap_enable(); | ||||
|  | @ -179,11 +358,6 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, | |||
| 
 | ||||
| 	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) { | ||||
| 		err = udp_add_offload(&fou->udp_offloads); | ||||
| 		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_AF] = { .type = NLA_U8, }, | ||||
| 	[FOU_ATTR_IPPROTO] = { .type = NLA_U8, }, | ||||
| 	[FOU_ATTR_TYPE] = { .type = NLA_U8, }, | ||||
| }; | ||||
| 
 | ||||
| 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]) | ||||
| 		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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tom Herbert
				Tom Herbert