From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:30
Created a new BPF program type, BPF_PROG_TYPE_SOCK_OPS, and a corresponding
struct that allows BPF programs of this type to access some of the
socket's fields (such as IP addresses, ports, etc.) and setting
connection parameters such as buffer sizes, initial window, SYN/SYN-ACK
RTOs, etc.
Unlike current BPF program types that expect to be called at a particular
place in the network stack code, SOCK_OPS program can be called at
different places and use an "op" field to indicate the context. There
are currently two types of operations, those whose effect is through
their return value and those whose effect is through the new
bpf_setsocketop BPF helper function.
Example operands of the first type are:
BPF_SOCK_OPS_TIMEOUT_INIT
BPF_SOCK_OPS_RWND_INIT
BPF_SOCK_OPS_NEEDS_ECN
Example operands of the secont type are:
BPF_SOCK_OPS_TCP_CONNECT_CB
BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB
BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB
Current operands are only called during connection establishment so
there should not be any BPF overheads after connection establishment. The
main idea is to use connection information form both hosts, such as IP
addresses and ports to allow setting of per connection parameters to
optimize the connection's peformance.
Alghough there are already 3 mechanisms to set parameters (sysctls,
route metrics and setsockopts), this new mechanism provides some
disticnt advantages. Unlike sysctls, it can set parameters per
connection. In contrast to route metrics, it can also use port numbers
and information provided by a user level program. In addition, it could
set parameters probabilistically for evaluation purposes (i.e. do
something different on 10% of the flows and compare results with the
other 90% of the flows). Also, in cases where IPv6 addresses contain
geographic information, the rules to make changes based on the distance
(or RTT) between the hosts are much easier than route metric rules and
can be global. Finally, unlike setsockopt, it does not require
application changes and it can be updated easily at any time.
It uses the existing bpf cgroups infrastructure so the programs can be
attached per cgroup with full inheritance support. Although the bpf cgroup
framework already contains a sock related program type (BPF_PROG_TYPE_CGROUP_SOCK),
I created the new type (BPF_PROG_TYPE_SOCK_OPS) beccause the existing type
expects to be called only once during the connections's lifetime. In contrast,
the new program type will be called multiple times from different places in the
network stack code. For example, before sending SYN and SYN-ACKs to set
an appropriate timeout, when the connection is established to set congestion
control, etc. As a result it has "op" field to specify the type of operation
requested.
This patch set also includes sample BPF programs to demostrate the differnet
features.
v2: Formatting changes, rebased to latest net-next
v3: Fixed build issues, changed socket_ops to sock_ops throught,
fixed formatting issues, removed the syscall to load sock_ops
program and added functionality to use existing bpf attach and
bpf detach system calls, removed reader/writer locks in
sock_bpfops.c (used when saving sock_ops global program)
and fixed missing module refcount increment.
v4: Removed global sock_ops program and instead used existing cgroup bpf
infrastructure to support a new BPF_CGROUP_ATTCH type.
Consists of the following patches:
include/linux/bpf-cgroup.h | 18 ++++
include/linux/bpf_types.h | 1 +
include/linux/filter.h | 10 ++
include/net/tcp.h | 67 +++++++++++-
include/uapi/linux/bpf.h | 66 +++++++++++-
kernel/bpf/cgroup.c | 37 +++++++
kernel/bpf/syscall.c | 5 +
net/core/filter.c | 271 +++++++++++++++++++++++++++++++++++++++++++++++
net/ipv4/tcp.c | 2 +-
net/ipv4/tcp_cong.c | 32 ++++--
net/ipv4/tcp_fastopen.c | 1 +
net/ipv4/tcp_input.c | 10 +-
net/ipv4/tcp_minisocks.c | 9 +-
net/ipv4/tcp_output.c | 18 +++-
samples/bpf/Makefile | 9 ++
samples/bpf/bpf_helpers.h | 3 +
samples/bpf/bpf_load.c | 13 ++-
samples/bpf/load_sock_ops.c | 97 +++++++++++++++++
samples/bpf/tcp_bufs_kern.c | 77 ++++++++++++++
samples/bpf/tcp_clamp_kern.c | 94 ++++++++++++++++
samples/bpf/tcp_cong_kern.c | 74 +++++++++++++
samples/bpf/tcp_iw_kern.c | 79 ++++++++++++++
samples/bpf/tcp_rwnd_kern.c | 61 +++++++++++
samples/bpf/tcp_synrto_kern.c | 60 +++++++++++
tools/include/uapi/linux/bpf.h | 66 +++++++++++-
25 files changed, 1154 insertions(+), 26 deletions(-)
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:30
Created a new BPF program type, BPF_PROG_TYPE_SOCK_OPS, and a corresponding
struct that allows BPF programs of this type to access some of the
socket's fields (such as IP addresses, ports, etc.). It uses the
existing bpf cgroups infrastructure so the programs can be attached per
cgroup with full inheritance support. The program will be called at
appropriate times to set relevant connections parameters such as buffer
sizes, SYN and SYN-ACK RTOs, etc., based on connection information such
as IP addresses, port numbers, etc.
Alghough there are already 3 mechanisms to set parameters (sysctls,
route metrics and setsockopts), this new mechanism provides some
distinct advantages. Unlike sysctls, it can set parameters per
connection. In contrast to route metrics, it can also use port numbers
and information provided by a user level program. In addition, it could
set parameters probabilistically for evaluation purposes (i.e. do
something different on 10% of the flows and compare results with the
other 90% of the flows). Also, in cases where IPv6 addresses contain
geographic information, the rules to make changes based on the distance
(or RTT) between the hosts are much easier than route metric rules and
can be global. Finally, unlike setsockopt, it oes not require
application changes and it can be updated easily at any time.
Although the bpf cgroup framework already contains a sock related
program type (BPF_PROG_TYPE_CGROUP_SOCK), I created the new type
(BPF_PROG_TYPE_SOCK_OPS) beccause the existing type expects to be called
only once during the connections's lifetime. In contrast, the new
program type will be called multiple times from different places in the
network stack code. For example, before sending SYN and SYN-ACKs to set
an appropriate timeout, when the connection is established to set
congestion control, etc. As a result it has "op" field to specify the
type of operation requested.
The purpose of this new program type is to simplify setting connection
parameters, such as buffer sizes, TCP's SYN RTO, etc. For example, it is
easy to use facebook's internal IPv6 addresses to determine if both hosts
of a connection are in the same datacenter. Therefore, it is easy to
write a BPF program to choose a small SYN RTO value when both hosts are
in the same datacenter.
This patch only contains the framework to support the new BPF program
type, following patches add the functionality to set various connection
parameters.
This patch defines a new BPF program type: BPF_PROG_TYPE_SOCKET_OPS
and a new bpf syscall command to load a new program of this type:
BPF_PROG_LOAD_SOCKET_OPS.
Two new corresponding structs (one for the kernel one for the user/BPF
program):
/* kernel version */
struct bpf_sock_ops_kern {
struct sock *sk;
bool is_req_sock:1;
__u32 op;
union {
__u32 reply;
__u32 replylong[4];
};
};
/* user version */
struct bpf_sock_ops {
__u32 op;
union {
__u32 reply;
__u32 replylong[4];
};
__u32 family;
__u32 remote_ip4;
__u32 local_ip4;
__u32 remote_ip6[4];
__u32 local_ip6[4];
__u32 remote_port;
__u32 local_port;
};
Currently there are two types of ops. The first type expects the BPF
program to return a value which is then used by the caller (or a
negative value to indicate the operation is not supported). The second
type expects state changes to be done by the BPF program, for example
through a setsockopt BPF helper function, and they ignore the return
value.
The reply fields of the bpf_sockt_ops struct are there in case a bpf
program needs to return a value larger than an integer.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/linux/bpf-cgroup.h | 18 +++++
include/linux/bpf_types.h | 1 +
include/linux/filter.h | 10 +++
include/net/tcp.h | 37 ++++++++++
include/uapi/linux/bpf.h | 28 ++++++++
kernel/bpf/cgroup.c | 37 ++++++++++
kernel/bpf/syscall.c | 5 ++
net/core/filter.c | 170 +++++++++++++++++++++++++++++++++++++++++++++
samples/bpf/bpf_load.c | 13 +++-
9 files changed, 316 insertions(+), 3 deletions(-)
@@ -2021,4 +2025,37 @@ int tcp_set_ulp(struct sock *sk, const char *name);voidtcp_get_available_ulp(char*buf,size_tlen);voidtcp_cleanup_ulp(structsock*sk);+/* Call BPF_SOCK_OPS program that returns an int. If the return value+*is<0,thentheBPFopfailed(forexampleiftheloadedBPF+*programdoesnotsupportthechosenoperationorthereisnoBPF+*programloaded).+*/+#ifdef CONFIG_BPF+staticinlineinttcp_call_bpf(structsock*sk,boolis_req_sock,intop)+{+structbpf_sock_ops_kernsock_ops;+intret;++if(!is_req_sock)+sock_owned_by_me(sk);++memset(&sock_ops,0,sizeof(sock_ops));+sock_ops.sk=sk;+sock_ops.is_req_sock=is_req_sock;+sock_ops.op=op;++ret=BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);+if(ret==0)+ret=sock_ops.reply;+else+ret=-1;+returnret;+}+#else+staticinlineinttcp_call_bpf(structsock*sk,boolis_req_sock,intop)+{+return-EPERM;+}+#endif+#endif /* _TCP_H */
@@ -720,4 +722,30 @@ struct bpf_map_info {__u32map_flags;}__attribute__((aligned(8)));+/* User bpf_sock_ops struct to access socket values and specify request ops+*andtheirreplies.+*Newfieldscanonlybeaddedattheendofthisstructure+*/+structbpf_sock_ops{+__u32op;+union{+__u32reply;+__u32replylong[4];+};+__u32family;+__u32remote_ip4;+__u32local_ip4;+__u32remote_ip6[4];+__u32local_ip6[4];+__u32remote_port;+__u32local_port;+};++/* List of known BPF sock_ops operators.+*Newentriescanonlybeaddedattheend+*/+enum{+BPF_SOCK_OPS_VOID,+};+#endif /* _UAPI__LINUX_BPF_H__ */
@@ -1069,6 +1069,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)caseBPF_CGROUP_INET_SOCK_CREATE:ptype=BPF_PROG_TYPE_CGROUP_SOCK;break;+caseBPF_CGROUP_SOCK_OPS:+ptype=BPF_PROG_TYPE_SOCK_OPS;+break;default:return-EINVAL;}
@@ -1109,6 +1112,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)caseBPF_CGROUP_INET_INGRESS:caseBPF_CGROUP_INET_EGRESS:caseBPF_CGROUP_INET_SOCK_CREATE:+caseBPF_CGROUP_SOCK_OPS:cgrp=cgroup_get_from_fd(attr->target_fd);if(IS_ERR(cgrp))returnPTR_ERR(cgrp);
@@ -1123,6 +1127,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)returnret;}+#endif /* CONFIG_CGROUP_BPF */#define BPF_PROG_TEST_RUN_LAST_FIELD test.duration
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:30
The sample BPF program, tcp_synrto_kern.c, sets the SYN and SYN-ACK
RTOs to 10ms when both hosts are within the same datacenter (i.e.
small RTTs) in an environment where common IPv6 prefixes indicate
both hosts are in the same data center.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_synrto_kern.c | 60 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
create mode 100644 samples/bpf/tcp_synrto_kern.c
@@ -0,0 +1,60 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*BPFprogramtosetSYNandSYN-ACKRTOsto10mswhenusingIPv6addresses+*andthefirst5.5bytesoftheIPv6addressesarethesame(inthisexample+*thatmeansbothhostsareinthesamedatacenter.+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_synrto(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+intrv=-1;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Check for TIMEOUT_INIT operation and IPv6 addresses */+if(op==BPF_SOCK_OPS_TIMEOUT_INIT&&+skops->family==AF_INET6){++/* If the first 5.5 bytes of the IPv6 address are the same+*thenbothhostsareinthesamedatacenter+*souseanRTOof10ms+*/+if(skops->local_ip6[0]==skops->remote_ip6[0]&&+(skops->local_ip6[1]&0xfff00000)==+(skops->remote_ip6[1]&0xfff00000))+rv=10;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:31
The program load_sock_ops can be used to load sock_ops bpf programs and
to attach it to an existing (v2) cgroup. It can also be used to detach
sock_ops programs.
Examples:
load_sock_ops [-l] <cg-path> <prog filename>
Load and attaches a sock_ops program at the specified cgroup.
If "-l" is used, the program will continue to run to output the
BPF log buffer.
If the specified filename does not end in ".o", it appends
"_kern.o" to the name.
load_sock_ops -r <cg-path>
Detaches the currently attached sock_ops program from the
specified cgroup.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 3 ++
samples/bpf/load_sock_ops.c | 97 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+)
create mode 100644 samples/bpf/load_sock_ops.c
@@ -0,0 +1,97 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*/+#include<stdio.h>+#include<stdlib.h>+#include<string.h>+#include<linux/bpf.h>+#include"libbpf.h"+#include"bpf_load.h"+#include<unistd.h>+#include<errno.h>+#include<fcntl.h>+#include<linux/unistd.h>++staticvoidusage(char*pname)+{+printf("USAGE:\n %s [-l] <cg-path> <prog filename>\n",pname);+printf("\tLoad and attach a sock_ops program to the specified "+"cgroup\n");+printf("\tIf \"-l\" is used, the program will continue to run\n");+printf("\tprinting the BPF log buffer\n");+printf("\tIf the specified filename does not end in \".o\", it\n");+printf("\tappends \"_kern.o\" to the name\n");+printf("\n");+printf(" %s -r <cg-path>\n",pname);+printf("\tDetaches the currently attached sock_ops program\n");+printf("\tfrom the specified cgroup\n");+printf("\n");+exit(0);+}++intmain(intargc,char**argv)+{+intlogFlag=0;+interror=0;+char*cg_path;+charfn[500];+char*prog;+intcg_fd;++if(argc<3)+usage(argv[0]);++if(!strcmp(argv[1],"-r")){+cg_path=argv[2];+cg_fd=open(cg_path,O_DIRECTORY,O_RDONLY);+error=bpf_prog_detach(cg_fd,BPF_CGROUP_SOCK_OPS);+if(error){+printf("ERROR: bpf_prog_detach: %d (%s)\n",+error,strerror(errno));+return1;+}+return0;+}elseif(!strcmp(argv[1],"-h")){+usage(argv[0]);+}elseif(!strcmp(argv[1],"-l")){+logFlag=1;+if(argc<4)+usage(argv[0]);+}++prog=argv[argc-1];+cg_path=argv[argc-2];+if(strlen(prog)>480){+fprintf(stderr,"ERROR: program name too long (> 480 chars)\n");+exit(2);+}+cg_fd=open(cg_path,O_DIRECTORY,O_RDONLY);++if(!strcmp(prog+strlen(prog)-2,".o"))+strcpy(fn,prog);+else+sprintf(fn,"%s_kern.o",prog);+if(logFlag)+printf("loading bpf file:%s\n",fn);+if(load_bpf_file(fn)){+printf("ERROR: load_bpf_file failed for: %s\n",fn);+printf("%s",bpf_log_buf);+return1;+}+if(logFlag)+printf("TCP BPF Loaded %s\n",fn);++error=bpf_prog_attach(prog_fd[0],cg_fd,BPF_CGROUP_SOCK_OPS,0);+if(error){+printf("ERROR: bpf_prog_attach: %d (%s)\n",+error,strerror(errno));+return4;+}elseif(logFlag){+read_trace_pipe();+}++returnerror;+}
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:31
This patch adds suppport for setting the initial advertized window from
within a BPF_SOCK_OPS program. This can be used to support larger
initial cwnd values in environments where it is known to be safe.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/net/tcp.h | 10 ++++++++++
include/uapi/linux/bpf.h | 4 ++++
net/ipv4/tcp_minisocks.c | 9 ++++++++-
net/ipv4/tcp_output.c | 7 ++++++-
4 files changed, 28 insertions(+), 2 deletions(-)
@@ -749,6 +749,10 @@ enum {BPF_SOCK_OPS_TIMEOUT_INIT,/* Should return SYN-RTO value to use or*-1ifdefaultvalueshouldbeused*/+BPF_SOCK_OPS_RWND_INIT,/* Should return initial advertized+*window(inpackets)or-1ifdefault+*valueshouldbeused+*/};#endif /* _UAPI__LINUX_BPF_H__ */
@@ -363,6 +364,12 @@ void tcp_openreq_init_rwin(struct request_sock *req,(req->rsk_window_clamp>full_space||req->rsk_window_clamp==0))req->rsk_window_clamp=full_space;+rcv_wnd=tcp_rwnd_init_bpf((structsock*)req,true);+if(rcv_wnd==0)+rcv_wnd=dst_metric(dst,RTAX_INITRWND);+elseif(full_space<rcv_wnd*mss)+full_space=rcv_wnd*mss;+/* tcp_full_space because it is guaranteed to be the first packet */tcp_select_initial_window(full_space,mss-(ireq->tstamp_ok?TCPOLEN_TSTAMP_ALIGNED:0),
@@ -3267,6 +3267,7 @@ static void tcp_connect_init(struct sock *sk)conststructdst_entry*dst=__sk_dst_get(sk);structtcp_sock*tp=tcp_sk(sk);__u8rcv_wscale;+u32rcv_wnd;/* We'll fix this up when we get a response from the other end.*Seetcp_input.c:tcp_rcv_state_processcaseTCP_SYN_SENT.
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:32
The sample bpf program, tcp_rwnd_kern.c, sets the initial
advertized window to 40 packets in an environment where
distinct IPv6 prefixes indicate that both hosts are not
in the same data center.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_rwnd_kern.c | 61 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+)
create mode 100644 samples/bpf/tcp_rwnd_kern.c
@@ -0,0 +1,61 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*BPFprogramtosetinitialreceivewindowto40packetswhenusingIPv6+*andthefirst5.5bytesoftheIPv6addressesarenotthesame(inthis+*examplethatmeansbothhostsarenotthesamedatacenter.+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_rwnd(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+intrv=-1;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Check for RWND_INIT operation and IPv6 addresses */+if(op==BPF_SOCK_OPS_RWND_INIT&&+skops->family==AF_INET6){++/* If the first 5.5 bytes of the IPv6 address are not the same+*thenbothhostsarenotinthesamedatacenter+*sousealargerinitialadvertizedwindow(40packets)+*/+if(skops->local_ip6[0]!=skops->remote_ip6[0]||+(skops->local_ip6[1]&0xfffff000)!=+(skops->remote_ip6[1]&0xfffff000))+bpf_trace_printk(fmt2,sizeof(fmt2),-1);+rv=40;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:32
This patch adds support for setting a per connection SYN and
SYN_ACK RTOs from within a BPF_SOCK_OPS program. For example,
to set small RTOs when it is known both hosts are within a
datacenter.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/net/tcp.h | 11 +++++++++++
include/uapi/linux/bpf.h | 3 +++
net/ipv4/tcp_input.c | 3 ++-
net/ipv4/tcp_output.c | 2 +-
4 files changed, 17 insertions(+), 2 deletions(-)
@@ -746,6 +746,9 @@ struct bpf_sock_ops {*/enum{BPF_SOCK_OPS_VOID,+BPF_SOCK_OPS_TIMEOUT_INIT,/* Should return SYN-RTO value to use or+*-1ifdefaultvalueshouldbeused+*/};#endif /* _UAPI__LINUX_BPF_H__ */
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:32
Added callbacks to BPF SOCK_OPS type program before an active
connection is intialized and after a passive or active connection is
established.
The following patch demostrates how they can be used to set send and
receive buffer sizes.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/uapi/linux/bpf.h | 11 +++++++++++
net/ipv4/tcp_fastopen.c | 1 +
net/ipv4/tcp_input.c | 4 +++-
net/ipv4/tcp_output.c | 1 +
4 files changed, 16 insertions(+), 1 deletion(-)
@@ -765,6 +765,17 @@ enum {*window(inpackets)or-1ifdefault*valueshouldbeused*/+BPF_SOCK_OPS_TCP_CONNECT_CB,/* Calls BPF program right before an+*activeconnectionisinitialized+*/+BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB,/* Calls BPF program when an+*activeconnectionis+*established+*/+BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,/* Calls BPF program when a+*passiveconnectionis+*established+*/};#endif /* _UAPI__LINUX_BPF_H__ */
@@ -5571,7 +5571,7 @@ void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)icsk->icsk_af_ops->rebuild_header(sk);tcp_init_metrics(sk);-+tcp_call_bpf(sk,false,BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB);tcp_init_congestion_control(sk);/* Prevent spurious tcp_cwnd_restart() on first data
@@ -5977,6 +5977,8 @@ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)}else{/* Make sure socket is routed, for correct metrics. */icsk->icsk_af_ops->rebuild_header(sk);+tcp_call_bpf(sk,false,+BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB);tcp_init_congestion_control(sk);tcp_mtup_init(sk);
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:33
Added support for calling a subset of socket setsockopts from
BPF_PROG_TYPE_SOCK_OPS programs. The code was duplicated rather
than making the changes to call the socket setsockopt function because
the changes required would have been larger.
The ops supported are:
SO_RCVBUF
SO_SNDBUF
SO_MAX_PACING_RATE
SO_PRIORITY
SO_RCVLOWAT
SO_MARK
Signed-off-by: Lawrence Brakmo <redacted>
---
include/uapi/linux/bpf.h | 14 ++++++++-
net/core/filter.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++-
samples/bpf/bpf_helpers.h | 3 ++
3 files changed, 92 insertions(+), 2 deletions(-)
@@ -520,6 +520,17 @@ union bpf_attr {*Setfullskb->hash.*@skb:pointertoskb*@hash:hashtoset+*+*intbpf_setsockopt(bpf_socket,level,optname,optval,optlen)+*Callssetsockopt.Notalloptsareavailable,onlythosewith+*integeroptvalsplusTCP_CONGESTION.+*Supportedlevels:SOL_SOCKETandIPROTO_TCP+*@bpf_socket:pointertobpf_socket+*@level:SOL_SOCKETorIPROTO_TCP+*@optname:optionname+*@optval:pointertooptionvalue+*@optlen:lengthofoptvalinbyes+*Return:0ornegativeerror*/#define __BPF_FUNC_MAPPER(FN) \FN(unspec),\
@@ -570,7 +581,8 @@ union bpf_attr {FN(probe_read_str),\FN(get_socket_cookie),\FN(get_socket_uid),\-FN(set_hash),+FN(set_hash),\+FN(setsockopt),/* integer value in 'imm' field of BPF_CALL instruction selects which helper*functioneBPFprogramintendstocall
@@ -2672,6 +2673,69 @@ static const struct bpf_func_proto bpf_get_socket_uid_proto = {.arg1_type=ARG_PTR_TO_CTX,};+BPF_CALL_5(bpf_setsockopt,structbpf_sock_ops_kern*,bpf_sock,+int,level,int,optname,char*,optval,int,optlen)+{+structsock*sk=bpf_sock->sk;+intret=0;+intval;++if(bpf_sock->is_req_sock)+return-EINVAL;++if(level==SOL_SOCKET){+/* Only some socketops are supported */+val=*((int*)optval);++switch(optname){+caseSO_RCVBUF:+sk->sk_userlocks|=SOCK_RCVBUF_LOCK;+sk->sk_rcvbuf=max_t(int,val*2,SOCK_MIN_RCVBUF);+break;+caseSO_SNDBUF:+sk->sk_userlocks|=SOCK_SNDBUF_LOCK;+sk->sk_sndbuf=max_t(int,val*2,SOCK_MIN_SNDBUF);+break;+caseSO_MAX_PACING_RATE:+sk->sk_max_pacing_rate=val;+sk->sk_pacing_rate=min(sk->sk_pacing_rate,+sk->sk_max_pacing_rate);+break;+caseSO_PRIORITY:+sk->sk_priority=val;+break;+caseSO_RCVLOWAT:+if(val<0)+val=INT_MAX;+sk->sk_rcvlowat=val?:1;+break;+caseSO_MARK:+sk->sk_mark=val;+break;+default:+ret=-EINVAL;+}+}elseif(level==SOL_TCP&&+sk->sk_prot->setsockopt==tcp_setsockopt){+/* Place holder */+ret=-EINVAL;+}else{+ret=-EINVAL;+}+returnret;+}++staticconststructbpf_func_protobpf_setsockopt_proto={+.func=bpf_setsockopt,+.gpl_only=true,+.ret_type=RET_INTEGER,+.arg1_type=ARG_PTR_TO_CTX,+.arg2_type=ARG_ANYTHING,+.arg3_type=ARG_ANYTHING,+.arg4_type=ARG_PTR_TO_MEM,+.arg5_type=ARG_CONST_SIZE_OR_ZERO,+};+staticconststructbpf_func_proto*bpf_base_func_proto(enumbpf_func_idfunc_id){
@@ -60,6 +60,9 @@ static unsigned long long (*bpf_get_prandom_u32)(void) =(void*)BPF_FUNC_get_prandom_u32;staticint(*bpf_xdp_adjust_head)(void*ctx,intoffset)=(void*)BPF_FUNC_xdp_adjust_head;+staticint(*bpf_setsockopt)(void*ctx,intlevel,intoptname,void*optval,+intoptlen)=+(void*)BPF_FUNC_setsockopt;/* llvm builtin functions that eBPF C program may use to*emitBPF_LD_ABSandBPF_LD_INDinstructions
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:33
This patch contains a BPF program to set initial receive window to
40 packets and send and receive buffers to 1.5MB. This would usually
be done after doing appropriate checks that indicate the hosts are
far enough away (i.e. large RTT).
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_bufs_kern.c | 77 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+)
create mode 100644 samples/bpf/tcp_bufs_kern.c
@@ -0,0 +1,77 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*BPFprogramtosetinitialreceivewindowto40packetsandsend+*andreceivebuffersto1.5MB.Thiswouldusuallybedoneafter+*doingappropriatechecksthatindicatethehostsarefarenough+*away(i.e.largeRTT).+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_bufs(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+intbufsize=1500000;+intrwnd_init=40;+intrv=0;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Usually there would be a check to insure the hosts are far+*fromeachothersoitmakessensetoincreasebuffersizes+*/+switch(op){+caseBPF_SOCK_OPS_RWND_INIT:+rv=rwnd_init;+break;+caseBPF_SOCK_OPS_TCP_CONNECT_CB:+/* Set sndbuf and rcvbuf of active connections */+rv=bpf_setsockopt(skops,SOL_SOCKET,SO_SNDBUF,&bufsize,+sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,SO_RCVBUF,+&bufsize,sizeof(bufsize));+break;+caseBPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:+/* Nothing to do */+break;+caseBPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:+/* Set sndbuf and rcvbuf of passive connections */+rv=bpf_setsockopt(skops,SOL_SOCKET,SO_SNDBUF,&bufsize,+sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,SO_RCVBUF,+&bufsize,sizeof(bufsize));+break;+default:+rv=-1;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:33
Added support for changing congestion control for SOCK_OPS bpf
programs through the setsockopt bpf helper function. It also adds
a new SOCK_OPS op, BPF_SOCK_OPS_NEEDS_ECN, that is needed for
congestion controls, like dctcp, that need to enable ECN in the
SYN packets.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/net/tcp.h | 9 ++++++++-
include/uapi/linux/bpf.h | 3 +++
net/core/filter.c | 11 +++++++++--
net/ipv4/tcp.c | 2 +-
net/ipv4/tcp_cong.c | 32 ++++++++++++++++++++++----------
net/ipv4/tcp_input.c | 3 ++-
net/ipv4/tcp_output.c | 8 +++++---
7 files changed, 50 insertions(+), 18 deletions(-)
@@ -333,8 +333,12 @@ int tcp_set_allowed_congestion_control(char *val)returnret;}-/* Change congestion control for socket */-inttcp_set_congestion_control(structsock*sk,constchar*name)+/* Change congestion control for socket. If load is false, then it is the+*responsibilityofthecallertocalltcp_init_congestion_controlor+*tcp_reinit_congestion_control(ifthecurrentcongestioncontrolwas+*alreadyinitialized.+*/+inttcp_set_congestion_control(structsock*sk,constchar*name,boolload){structinet_connection_sock*icsk=inet_csk(sk);conststructtcp_congestion_ops*ca;
@@ -344,21 +348,29 @@ int tcp_set_congestion_control(struct sock *sk, const char *name)return-EPERM;rcu_read_lock();-ca=__tcp_ca_find_autoload(name);+if(!load)+ca=tcp_ca_find(name);+else+ca=__tcp_ca_find_autoload(name);/* No change asking for existing value */if(ca==icsk->icsk_ca_ops){icsk->icsk_ca_setsockopt=1;gotoout;}-if(!ca)+if(!ca){err=-ENOENT;-elseif(!((ca->flags&TCP_CONG_NON_RESTRICTED)||-ns_capable(sock_net(sk)->user_ns,CAP_NET_ADMIN)))+}elseif(!load){+icsk->icsk_ca_ops=ca;+if(!try_module_get(ca->owner))+err=-EBUSY;+}elseif(!((ca->flags&TCP_CONG_NON_RESTRICTED)||+ns_capable(sock_net(sk)->user_ns,CAP_NET_ADMIN))){err=-EPERM;-elseif(!try_module_get(ca->owner))+}elseif(!try_module_get(ca->owner)){err=-EBUSY;-else+}else{tcp_reinit_congestion_control(sk,ca);+}out:rcu_read_unlock();returnerr;
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:34
Sample BPF program that sets congestion control to dctcp when both hosts
are within the same datacenter. In this example that is assumed to be
when they have the first 5.5 bytes of their IPv6 address are the same.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_cong_kern.c | 74 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
create mode 100644 samples/bpf/tcp_cong_kern.c
@@ -0,0 +1,74 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*BPFprogramtosetcongestioncontroltodctcpwhenbothhostsare+*inthesamedatacenter(asdetereminedbyIPv6prefix).+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/tcp.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_cong(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+charcong[]="dctcp";+intrv=0;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Check if both hosts are in the same datacenter. For this+*exampletheyareifthe1st5.5bytesintheIPv6address+*arethesame.+*/+if(skops->family==AF_INET6&&+skops->local_ip6[0]==skops->remote_ip6[0]&&+(skops->local_ip6[1]&0xfff00000)==+(skops->remote_ip6[1]&0xfff00000)){+switch(op){+caseBPF_SOCK_OPS_NEEDS_ECN:+rv=1;+break;+caseBPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:+rv=bpf_setsockopt(skops,SOL_TCP,TCP_CONGESTION,+cong,sizeof(cong));+break;+caseBPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:+rv=bpf_setsockopt(skops,SOL_TCP,TCP_CONGESTION,+cong,sizeof(cong));+break;+default:+rv=-1;+}+}else{+rv=-1;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:34
Adds a new bpf_setsockopt for TCP sockets, TCP_BPF_IW, which sets the
initial congestion window. This can be used when the hosts are far
apart (large RTTs) and it is safe to start with a large inital cwnd.
Signed-off-by: Lawrence Brakmo <redacted>
---
include/uapi/linux/bpf.h | 2 ++
net/core/filter.c | 14 +++++++++++++-
2 files changed, 15 insertions(+), 1 deletion(-)
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:34
Sample BPF program that assumes hosts are far away (i.e. large RTTs)
and sets initial cwnd and initial receive window to 40 packets,
send and receive buffers to 1.5MB.
In practice there would be a test to insure the hosts are actually
far enough away.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_iw_kern.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 80 insertions(+)
create mode 100644 samples/bpf/tcp_iw_kern.c
@@ -0,0 +1,79 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*BPFprogramtosetinitialcongestionwindowandinitialreceive+*windowto40packetsandsendandreceivebuffersto1.5MB.This+*wouldusuallybedoneafterdoingappropriatechecksthatindicate+*thehostsarefarenoughaway(i.e.largeRTT).+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_iw(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+intbufsize=1500000;+intrwnd_init=40;+intiw=40;+intrv=0;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Usually there would be a check to insure the hosts are far+*fromeachothersoitmakessensetoincreasebuffersizes+*/+switch(op){+caseBPF_SOCK_OPS_RWND_INIT:+rv=rwnd_init;+break;+caseBPF_SOCK_OPS_TCP_CONNECT_CB:+/* Set sndbuf and rcvbuf of active connections */+rv=bpf_setsockopt(skops,SOL_SOCKET,SO_SNDBUF,&bufsize,+sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,SO_RCVBUF,+&bufsize,sizeof(bufsize));+break;+caseBPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:+rv=bpf_setsockopt(skops,SOL_TCP,TCP_BPF_IW,&iw,+sizeof(iw));+break;+caseBPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:+/* Set sndbuf and rcvbuf of passive connections */+rv=bpf_setsockopt(skops,SOL_SOCKET,SO_SNDBUF,&bufsize,+sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,SO_RCVBUF,+&bufsize,sizeof(bufsize));+break;+default:+rv=-1;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:35
Adds a new bpf_setsockopt for TCP sockets, TCP_BPF_SNDCWND_CLAMP, which
sets the initial congestion window. It is useful to limit the sndcwnd
when the host are close to each other (small RTT).
Signed-off-by: Lawrence Brakmo <redacted>
---
include/uapi/linux/bpf.h | 1 +
net/core/filter.c | 7 +++++++
2 files changed, 8 insertions(+)
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:35
Sample BPF program, tcp_clamp_kern.c, to demostrate the use
of setting the sndcwnd clamp. This program assumes that if the
first 5.5 bytes of the host's IPv6 addresses are the same, then
the hosts are in the same datacenter and sets sndcwnd clamp to
100 packets, SYN and SYN-ACK RTOs to 10ms and send/receive buffer
sizes to 150KB.
Signed-off-by: Lawrence Brakmo <redacted>
---
samples/bpf/Makefile | 1 +
samples/bpf/tcp_clamp_kern.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)
create mode 100644 samples/bpf/tcp_clamp_kern.c
@@ -0,0 +1,94 @@+/* Copyright (c) 2017 Facebook+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsofversion2oftheGNUGeneralPublic+*LicenseaspublishedbytheFreeSoftwareFoundation.+*+*SampleBPFprogramtosetsendandreceivebuffersto150KB,sndcwndclamp+*to100packetsandSYNandSYN_ACKRTOsto10mswhenbothhostsarewithin+*thesamedatacenter.Forhisexample,weassumetheyarewithinthesame+*datacenterwhenthefirst5.5bytesoftheirIPv6addressesarethesame.+*/++#include<uapi/linux/bpf.h>+#include<uapi/linux/if_ether.h>+#include<uapi/linux/if_packet.h>+#include<uapi/linux/ip.h>+#include<linux/socket.h>+#include"bpf_helpers.h"++#define DEBUG 1++SEC("sockops")+intbpf_clamp(structbpf_sock_ops*skops)+{+charfmt1[]="BPF command: %d\n";+charfmt2[]=" Returning %d\n";+intbufsize=150000;+intto_init=10;+intclamp=100;+intrv=0;+intop;++/* For testing purposes, only execute rest of BPF program+*ifneitherportnumberis55601+*/+if(skops->remote_port!=55601&&skops->local_port!=55601)+return-1;++op=(int)skops->op;++#ifdef DEBUG+bpf_trace_printk(fmt1,sizeof(fmt1),op);+#endif++/* Check that both hosts are within same datacenter. For this example+*itisthecasewhenthefirst5.5bytesoftheirIPv6addressesare+*thesame.+*/+if(skops->family==AF_INET6&&+skops->local_ip6[0]==skops->remote_ip6[0]&&+(skops->local_ip6[1]&0xfff00000)==+(skops->remote_ip6[1]&0xfff00000)){+switch(op){+caseBPF_SOCK_OPS_TIMEOUT_INIT:+rv=to_init;+break;+caseBPF_SOCK_OPS_TCP_CONNECT_CB:+/* Set sndbuf and rcvbuf of active connections */+rv=bpf_setsockopt(skops,SOL_SOCKET,SO_SNDBUF,+&bufsize,sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,+SO_RCVBUF,&bufsize,+sizeof(bufsize));+break;+caseBPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:+rv=bpf_setsockopt(skops,SOL_TCP,+TCP_BPF_SNDCWND_CLAMP,+&clamp,sizeof(clamp));+break;+caseBPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:+/* Set sndbuf and rcvbuf of passive connections */+rv=bpf_setsockopt(skops,SOL_TCP,+TCP_BPF_SNDCWND_CLAMP,+&clamp,sizeof(clamp));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,+SO_SNDBUF,&bufsize,+sizeof(bufsize));+rv=rv*100+bpf_setsockopt(skops,SOL_SOCKET,+SO_RCVBUF,&bufsize,+sizeof(bufsize));+break;+default:+rv=-1;+}+}else{+rv=-1;+}+#ifdef DEBUG+bpf_trace_printk(fmt2,sizeof(fmt2),rv);+#endif+skops->reply=rv;+return1;+}+char_license[]SEC("license")="GPL";
From: Lawrence Brakmo <hidden> Date: 2017-06-28 17:31:35
Update tools/include/uapi/linux/bpf.h to include changes related to new
bpf sock_ops program type.
Signed-off-by: Lawrence Brakmo <redacted>
---
tools/include/uapi/linux/bpf.h | 66 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 65 insertions(+), 1 deletion(-)
@@ -518,6 +520,17 @@ union bpf_attr {*Setfullskb->hash.*@skb:pointertoskb*@hash:hashtoset+*+*intbpf_setsockopt(bpf_socket,level,optname,optval,optlen)+*Callssetsockopt.Notalloptsareavailable,onlythosewith+*integeroptvalsplusTCP_CONGESTION.+*Supportedlevels:SOL_SOCKETandIPROTO_TCP+*@bpf_socket:pointertobpf_socket+*@level:SOL_SOCKETorIPROTO_TCP+*@optname:optionname+*@optval:pointertooptionvalue+*@optlen:lengthofoptvalinbyes+*Return:0ornegativeerror*/#define __BPF_FUNC_MAPPER(FN) \FN(unspec),\
@@ -568,7 +581,8 @@ union bpf_attr {FN(probe_read_str),\FN(get_socket_cookie),\FN(get_socket_uid),\-FN(set_hash),+FN(set_hash),\+FN(setsockopt),/* integer value in 'imm' field of BPF_CALL instruction selects which helper*functioneBPFprogramintendstocall
@@ -720,4 +734,54 @@ struct bpf_map_info {__u32map_flags;}__attribute__((aligned(8)));+/* User bpf_sock_ops struct to access socket values and specify request ops+*andtheirreplies.+*Newfieldscanonlybeaddedattheendofthisstructure+*/+structbpf_sock_ops{+__u32op;+union{+__u32reply;+__u32replylong[4];+};+__u32family;+__u32remote_ip4;+__u32local_ip4;+__u32remote_ip6[4];+__u32local_ip6[4];+__u32remote_port;+__u32local_port;+};++/* List of known BPF sock_ops operators.+*Newentriescanonlybeaddedattheend+*/+enum{+BPF_SOCK_OPS_VOID,+BPF_SOCK_OPS_TIMEOUT_INIT,/* Should return SYN-RTO value to use or+*-1ifdefaultvalueshouldbeused+*/+BPF_SOCK_OPS_RWND_INIT,/* Should return initial advertized+*window(inpackets)or-1ifdefault+*valueshouldbeused+*/+BPF_SOCK_OPS_TCP_CONNECT_CB,/* Calls BPF program right before an+*activeconnectionisinitialized+*/+BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB,/* Calls BPF program when an+*activeconnectionis+*established+*/+BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,/* Calls BPF program when a+*passiveconnectionis+*established+*/+BPF_SOCK_OPS_NEEDS_ECN,/* If connection's congestion control+*needsECN+*/+};++#define TCP_BPF_IW 1001 /* Set TCP initial congestion window */+#define TCP_BPF_SNDCWND_CLAMP 1002 /* Set sndcwnd_clamp */+#endif /* _UAPI__LINUX_BPF_H__ */
+#ifdef CONFIG_BPF+static inline int tcp_call_bpf(struct sock *sk, bool is_req_sock, int op)+{+ struct bpf_sock_ops_kern sock_ops;+ int ret;++ if (!is_req_sock)+ sock_owned_by_me(sk);++ memset(&sock_ops, 0, sizeof(sock_ops));+ sock_ops.sk = sk;+ sock_ops.is_req_sock = is_req_sock;+ sock_ops.op = op;++ ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);+ if (ret == 0)+ ret = sock_ops.reply;+ else+ ret = -1;+ return ret;+}
the switch to cgroup attached only made it really nice and clean.
No global state to worry about.
I haven't looked through the minor patch details, but overall
it all looks good to me. I don't have any architectural concerns.
Acked-by: Alexei Starovoitov <ast@kernel.org>
From: Daniel Borkmann <daniel@iogearbox.net> Date: 2017-06-29 09:46:13
On 06/28/2017 07:31 PM, Lawrence Brakmo wrote:
Created a new BPF program type, BPF_PROG_TYPE_SOCK_OPS, and a corresponding
struct that allows BPF programs of this type to access some of the
socket's fields (such as IP addresses, ports, etc.). It uses the
existing bpf cgroups infrastructure so the programs can be attached per
cgroup with full inheritance support. The program will be called at
appropriate times to set relevant connections parameters such as buffer
sizes, SYN and SYN-ACK RTOs, etc., based on connection information such
as IP addresses, port numbers, etc.
[...]
Currently there are two types of ops. The first type expects the BPF
program to return a value which is then used by the caller (or a
negative value to indicate the operation is not supported). The second
type expects state changes to be done by the BPF program, for example
through a setsockopt BPF helper function, and they ignore the return
value.
The reply fields of the bpf_sockt_ops struct are there in case a bpf
program needs to return a value larger than an integer.
Signed-off-by: Lawrence Brakmo <redacted>
For BPF bits:
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
That one is indeed in host endianness. Makes sense to have remote_port
and local_port in a consistent representation.
I was wondering though whether we should do all the conversion of
BPF_ENDIAN(BPF_FROM_BE, ...) or just leave it to the user whether
he needs the BPF_ENDIAN(BPF_FROM_BE, ...) or process it in network
byte order as-is. In case the user needs to go and undo again via
BPF_ENDIAN(BPF_TO_BE, ...), e.g., to reconstruct a full v6 addr,
then we have two unneeded insns for each of the remote_ip6[X] /
local_ip6[X]. So, not providing it in host byte order, the user can
still always chose to do a BPF_ENDIAN(BPF_FROM_BE, ...) by himself,
if this representation is preferred. Wdyt?
From: Daniel Borkmann <daniel@iogearbox.net> Date: 2017-06-29 10:08:49
On 06/28/2017 07:31 PM, Lawrence Brakmo wrote:
Added support for calling a subset of socket setsockopts from
BPF_PROG_TYPE_SOCK_OPS programs. The code was duplicated rather
than making the changes to call the socket setsockopt function because
the changes required would have been larger.
The ops supported are:
SO_RCVBUF
SO_SNDBUF
SO_MAX_PACING_RATE
SO_PRIORITY
SO_RCVLOWAT
SO_MARK
Signed-off-by: Lawrence Brakmo <redacted>
Nit: I would rather make optlen a u32. But more below.
quoted hunk
+{+ struct sock *sk = bpf_sock->sk;+ int ret = 0;+ int val;++ if (bpf_sock->is_req_sock)+ return -EINVAL;++ if (level == SOL_SOCKET) {
if (optlen != sizeof(int))
return -EINVAL;
quoted hunk
+ /* Only some socketops are supported */+ val = *((int *)optval);++ switch (optname) {+ case SO_RCVBUF:+ sk->sk_userlocks |= SOCK_RCVBUF_LOCK;+ sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);+ break;+ case SO_SNDBUF:+ sk->sk_userlocks |= SOCK_SNDBUF_LOCK;+ sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);+ break;+ case SO_MAX_PACING_RATE:+ sk->sk_max_pacing_rate = val;+ sk->sk_pacing_rate = min(sk->sk_pacing_rate,+ sk->sk_max_pacing_rate);+ break;+ case SO_PRIORITY:+ sk->sk_priority = val;+ break;+ case SO_RCVLOWAT:+ if (val < 0)+ val = INT_MAX;+ sk->sk_rcvlowat = val ? : 1;+ break;+ case SO_MARK:+ sk->sk_mark = val;+ break;+ default:+ ret = -EINVAL;+ }+ } else if (level == SOL_TCP &&+ sk->sk_prot->setsockopt == tcp_setsockopt) {+ /* Place holder */+ ret = -EINVAL;+ } else {+ ret = -EINVAL;+ }+ return ret;+}++static const struct bpf_func_proto bpf_setsockopt_proto = {+ .func = bpf_setsockopt,+ .gpl_only = true,+ .ret_type = RET_INTEGER,+ .arg1_type = ARG_PTR_TO_CTX,+ .arg2_type = ARG_ANYTHING,+ .arg3_type = ARG_ANYTHING,+ .arg4_type = ARG_PTR_TO_MEM,+ .arg5_type = ARG_CONST_SIZE_OR_ZERO,
Any reason you went with the ARG_CONST_SIZE_OR_ZERO type? Semantics
of this are that allowed [arg4, arg5] pair can be i) [NULL, 0] or
ii) [non-NULL, non-zero], where in case ii) verifier checks that the
area is initialized when coming from BPF stack.
So above 'val = *((int *)optval);' would give a NULL pointer deref
with NULL passed as arg or in case optlen was < sizeof(int) we access
stack out of bounds potentially. If the [NULL, 0] pair is not required,
I would just make that a ARG_CONST_SIZE and then check for size before
accessing optval.
From: kbuild test robot <hidden> Date: 2017-06-29 16:22:28
Hi Lawrence,
[auto build test WARNING on net-next/master]
url: https://github.com/0day-ci/linux/commits/Lawrence-Brakmo/bpf-BPF-cgroup-support-for-sock_ops/20170629-203719
config: xtensa-allyesconfig (attached as .config)
compiler: xtensa-linux-gcc (GCC) 4.9.0
reproduce:
wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=xtensa
All warnings (new ones prefixed by >>):
In file included from include/linux/cgroup-defs.h:20:0,
from include/linux/cgroup.h:26,
from include/net/netprio_cgroup.h:17,
from include/linux/netdevice.h:47,
from include/net/sock.h:51,
from include/linux/tcp.h:23,
from include/net/tcp.h:24,
from net//ipv6/netfilter/nf_socket_ipv6.c:13:
include/net/tcp.h: In function 'tcp_call_bpf':
quoted
include/linux/bpf-cgroup.h:86:25: warning: the address of 'sock_ops' will always evaluate as 'true' [-Waddress]
if (cgroup_bpf_enabled && (sock_ops) && (sock_ops)->sk) { \
^
quoted
include/net/tcp.h:2047:8: note: in expansion of macro 'BPF_CGROUP_RUN_PROG_SOCK_OPS'
ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
^
--
In file included from include/linux/cgroup-defs.h:20:0,
from include/linux/cgroup.h:26,
from include/net/netprio_cgroup.h:17,
from include/linux/netdevice.h:47,
from include/net/sock.h:51,
from include/linux/tcp.h:23,
from net//netfilter/ipvs/ip_vs_core.c:33:
include/net/tcp.h: In function 'tcp_call_bpf':
quoted
include/linux/bpf-cgroup.h:86:25: warning: the address of 'sock_ops' will always evaluate as 'true' [-Waddress]
if (cgroup_bpf_enabled && (sock_ops) && (sock_ops)->sk) { \
^
quoted
include/net/tcp.h:2047:8: note: in expansion of macro 'BPF_CGROUP_RUN_PROG_SOCK_OPS'
ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
^
net//netfilter/ipvs/ip_vs_core.c: In function 'ip_vs_sched_persist':
net//netfilter/ipvs/ip_vs_core.c:399:1: warning: the frame size of 1072 bytes is larger than 1024 bytes [-Wframe-larger-than=]
}
^
net//netfilter/ipvs/ip_vs_core.c: In function 'ip_vs_new_conn_out':
net//netfilter/ipvs/ip_vs_core.c:1199:1: warning: the frame size of 1056 bytes is larger than 1024 bytes [-Wframe-larger-than=]
}
^
--
In file included from include/linux/cgroup-defs.h:20:0,
from include/linux/cgroup.h:26,
from include/net/netprio_cgroup.h:17,
from include/linux/netdevice.h:47,
from include/net/sock.h:51,
from include/linux/tcp.h:23,
from net/netfilter/ipvs/ip_vs_core.c:33:
include/net/tcp.h: In function 'tcp_call_bpf':
quoted
include/linux/bpf-cgroup.h:86:25: warning: the address of 'sock_ops' will always evaluate as 'true' [-Waddress]
if (cgroup_bpf_enabled && (sock_ops) && (sock_ops)->sk) { \
^
quoted
include/net/tcp.h:2047:8: note: in expansion of macro 'BPF_CGROUP_RUN_PROG_SOCK_OPS'
ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
^
net/netfilter/ipvs/ip_vs_core.c: In function 'ip_vs_sched_persist':
net/netfilter/ipvs/ip_vs_core.c:399:1: warning: the frame size of 1072 bytes is larger than 1024 bytes [-Wframe-larger-than=]
}
^
net/netfilter/ipvs/ip_vs_core.c: In function 'ip_vs_new_conn_out':
net/netfilter/ipvs/ip_vs_core.c:1199:1: warning: the frame size of 1056 bytes is larger than 1024 bytes [-Wframe-larger-than=]
}
^
vim +86 include/linux/bpf-cgroup.h
70 __ret; \
71 })
72
73 #define BPF_CGROUP_RUN_PROG_INET_SOCK(sk) \
74 ({ \
75 int __ret = 0; \
76 if (cgroup_bpf_enabled && sk) { \
77 __ret = __cgroup_bpf_run_filter_sk(sk, \
78 BPF_CGROUP_INET_SOCK_CREATE); \
79 } \
80 __ret; \
81 })
82
83 #define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops) \
84 ({ \
85 int __ret = 0; \
> 86 if (cgroup_bpf_enabled && (sock_ops) && (sock_ops)->sk) { \
87 typeof(sk) __sk = sk_to_full_sk((sock_ops)->sk); \
88 if (sk_fullsock(__sk)) \
89 __ret = __cgroup_bpf_run_filter_sock_ops(__sk, \
90 sock_ops, \
91 BPF_CGROUP_SOCK_OPS); \
92 } \
93 __ret; \
94 })
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
Missing end ")".
I really like this short comment of what the program does, as it helps
people browsing these sample programs.
Can you also mention in the comment (of all these) bpf programs that
people load this bpf object file via the program 'load_sock_ops'?
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer
From: Lawrence Brakmo <hidden> Date: 2017-06-29 22:25:58
On 6/29/17, 12:39 PM, "netdev-owner@vger.kernel.org on behalf of Jesper Dangaard Brouer" <netdev-owner@vger.kernel.org on behalf of brouer@redhat.com> wrote:
On Wed, 28 Jun 2017 10:31:12 -0700
Lawrence Brakmo [off-list ref] wrote:
> +++ b/samples/bpf/tcp_synrto_kern.c
> @@ -0,0 +1,60 @@
> +/* Copyright (c) 2017 Facebook
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of version 2 of the GNU General Public
> + * License as published by the Free Software Foundation.
> + *
> + * BPF program to set SYN and SYN-ACK RTOs to 10ms when using IPv6 addresses
> + * and the first 5.5 bytes of the IPv6 addresses are the same (in this example
> + * that means both hosts are in the same datacenter.
Missing end ")".
I really like this short comment of what the program does, as it helps
people browsing these sample programs.
Can you also mention in the comment (of all these) bpf programs that
people load this bpf object file via the program 'load_sock_ops'?
Thank you for finding the typo and for the comment on adding how to load the sample programs. Will be done in v5 due later today.
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: https://urldefense.proofpoint.com/v2/url?u=http-3A__www.linkedin.com_in_brouer&d=DwICAg&c=5VD0RTtNlTh3ycd41b3MUw&r=pq_Mqvzfy-C8ltkgyx1u_g&m=EJ1TyanCGEOIXEPnAm8BicVjUXEJLsvUQY1vNC_4r7g&s=INcdT-mimhOZEgFLw7hqg2V6VJ70XZJoeY83vp6V8YY&e=
From: Lawrence Brakmo <hidden> Date: 2017-06-30 07:27:33
On 6/29/17, 2:46 AM, "netdev-owner@vger.kernel.org on behalf of Daniel Borkmann" <netdev-owner@vger.kernel.org on behalf of daniel@iogearbox.net> wrote:
On 06/28/2017 07:31 PM, Lawrence Brakmo wrote:
> Created a new BPF program type, BPF_PROG_TYPE_SOCK_OPS, and a corresponding
> struct that allows BPF programs of this type to access some of the
> socket's fields (such as IP addresses, ports, etc.). It uses the
> existing bpf cgroups infrastructure so the programs can be attached per
> cgroup with full inheritance support. The program will be called at
> appropriate times to set relevant connections parameters such as buffer
> sizes, SYN and SYN-ACK RTOs, etc., based on connection information such
> as IP addresses, port numbers, etc.
[...]
> Currently there are two types of ops. The first type expects the BPF
> program to return a value which is then used by the caller (or a
> negative value to indicate the operation is not supported). The second
> type expects state changes to be done by the BPF program, for example
> through a setsockopt BPF helper function, and they ignore the return
> value.
>
> The reply fields of the bpf_sockt_ops struct are there in case a bpf
> program needs to return a value larger than an integer.
>
> Signed-off-by: Lawrence Brakmo [off-list ref]
For BPF bits:
Acked-by: Daniel Borkmann [off-list ref]
> @@ -3379,6 +3409,140 @@ static u32 xdp_convert_ctx_access(enum bpf_access_type type,
> return insn - insn_buf;
> }
>
> +static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
> + const struct bpf_insn *si,
> + struct bpf_insn *insn_buf,
> + struct bpf_prog *prog)
> +{
> + struct bpf_insn *insn = insn_buf;
> + int off;
> +
> + switch (si->off) {
[...]
> + case offsetof(struct bpf_sock_ops, remote_ip4):
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_daddr) != 4);
> +
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common, skc_daddr));
> + *insn++ = BPF_ENDIAN(BPF_FROM_BE, si->dst_reg, 32);
> + break;
> +
> + case offsetof(struct bpf_sock_ops, local_ip4):
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_rcv_saddr) != 4);
> +
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common,
> + skc_rcv_saddr));
> + *insn++ = BPF_ENDIAN(BPF_FROM_BE, si->dst_reg, 32);
> + break;
> +
> + case offsetof(struct bpf_sock_ops, remote_ip6[0]) ...
> + offsetof(struct bpf_sock_ops, remote_ip6[3]):
> +#if IS_ENABLED(CONFIG_IPV6)
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common,
> + skc_v6_daddr.s6_addr32[0]) != 4);
> +
> + off = si->off;
> + off -= offsetof(struct bpf_sock_ops, remote_ip6[0]);
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common,
> + skc_v6_daddr.s6_addr32[0]) +
> + off);
> + *insn++ = BPF_ENDIAN(BPF_FROM_BE, si->dst_reg, 32);
> +#else
> + *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
> +#endif
> + break;
> +
> + case offsetof(struct bpf_sock_ops, local_ip6[0]) ...
> + offsetof(struct bpf_sock_ops, local_ip6[3]):
> +#if IS_ENABLED(CONFIG_IPV6)
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common,
> + skc_v6_rcv_saddr.s6_addr32[0]) != 4);
> +
> + off = si->off;
> + off -= offsetof(struct bpf_sock_ops, local_ip6[0]);
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common,
> + skc_v6_rcv_saddr.s6_addr32[0]) +
> + off);
> + *insn++ = BPF_ENDIAN(BPF_FROM_BE, si->dst_reg, 32);
> +#else
> + *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
> +#endif
> + break;
> +
> + case offsetof(struct bpf_sock_ops, remote_port):
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_dport) != 2);
> +
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common, skc_dport));
> + *insn++ = BPF_ENDIAN(BPF_FROM_BE, si->dst_reg, 16);
> + break;
> +
> + case offsetof(struct bpf_sock_ops, local_port):
> + BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_num) != 2);
> +
> + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
> + struct bpf_sock_ops_kern, sk),
> + si->dst_reg, si->src_reg,
> + offsetof(struct bpf_sock_ops_kern, sk));
> + *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
> + offsetof(struct sock_common, skc_num));
That one is indeed in host endianness. Makes sense to have remote_port
and local_port in a consistent representation.
I was wondering though whether we should do all the conversion of
BPF_ENDIAN(BPF_FROM_BE, ...) or just leave it to the user whether
he needs the BPF_ENDIAN(BPF_FROM_BE, ...) or process it in network
byte order as-is. In case the user needs to go and undo again via
BPF_ENDIAN(BPF_TO_BE, ...), e.g., to reconstruct a full v6 addr,
then we have two unneeded insns for each of the remote_ip6[X] /
local_ip6[X]. So, not providing it in host byte order, the user can
still always chose to do a BPF_ENDIAN(BPF_FROM_BE, ...) by himself,
if this representation is preferred. Wdyt?
Good point about endianness. What I will do is present the data
in the same endianness as it is in the kernel sock struct and document
this in the sock_ops struct.
I will submit a new patch set soon.
> + break;
> + }
> + return insn - insn_buf;
> +}
> +
> const struct bpf_verifier_ops sk_filter_prog_ops = {
> .get_func_proto = sk_filter_func_proto,
[...]
net/core/filter.c:2721: undefined reference to `tcp_set_congestion_control'
net/core/filter.c:2724: undefined reference to `tcp_reinit_congestion_control'
net/core/filter.c:2724: undefined reference to `tcp_setsockopt'
vim +2721 net/core/filter.c
2715 default:
2716 ret = -EINVAL;
2717 }
2718 } else if (level == SOL_TCP &&
2719 sk->sk_prot->setsockopt == tcp_setsockopt) {
2720 if (optname == TCP_CONGESTION) {
2721 ret = tcp_set_congestion_control(sk, optval, false);
2722 if (!ret && bpf_sock->op > BPF_SOCK_OPS_NEEDS_ECN)
2723 /* replacing an existing ca */
2724 tcp_reinit_congestion_control(sk,
2725 inet_csk(sk)->icsk_ca_ops);
2726 } else {
2727 ret = -EINVAL;
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation