diff options
author | Dave Watson <davejwatson@fb.com> | 2017-06-14 14:37:14 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-06-15 12:12:40 -0400 |
commit | 734942cc4ea6478eed125af258da1bdbb4afe578 (patch) | |
tree | a6b0b27cb551055a7c5d5756c40d737543323df1 /net/ipv4/tcp_ulp.c | |
parent | 206f60e1451b4b90cb7f3a803d1c440602a458e0 (diff) |
tcp: ULP infrastructure
Add the infrustructure for attaching Upper Layer Protocols (ULPs) over TCP
sockets. Based on a similar infrastructure in tcp_cong. The idea is that any
ULP can add its own logic by changing the TCP proto_ops structure to its own
methods.
Example usage:
setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
modules will call:
tcp_register_ulp(&tcp_tls_ulp_ops);
to register/unregister their ulp, with an init function and name.
A list of registered ulps will be returned by tcp_get_available_ulp, which is
hooked up to /proc. Example:
$ cat /proc/sys/net/ipv4/tcp_available_ulp
tls
There is currently no functionality to remove or chain ULPs, but
it should be possible to add these in the future if needed.
Signed-off-by: Boris Pismenny <borisp@mellanox.com>
Signed-off-by: Dave Watson <davejwatson@fb.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv4/tcp_ulp.c')
-rw-r--r-- | net/ipv4/tcp_ulp.c | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/net/ipv4/tcp_ulp.c b/net/ipv4/tcp_ulp.c new file mode 100644 index 000000000000..e855ea70819b --- /dev/null +++ b/net/ipv4/tcp_ulp.c | |||
@@ -0,0 +1,134 @@ | |||
1 | /* | ||
2 | * Pluggable TCP upper layer protocol support. | ||
3 | * | ||
4 | * Copyright (c) 2016-2017, Mellanox Technologies. All rights reserved. | ||
5 | * Copyright (c) 2016-2017, Dave Watson <davejwatson@fb.com>. All rights reserved. | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #include<linux/module.h> | ||
10 | #include <linux/mm.h> | ||
11 | #include <linux/types.h> | ||
12 | #include <linux/list.h> | ||
13 | #include <linux/gfp.h> | ||
14 | #include <net/tcp.h> | ||
15 | |||
16 | static DEFINE_SPINLOCK(tcp_ulp_list_lock); | ||
17 | static LIST_HEAD(tcp_ulp_list); | ||
18 | |||
19 | /* Simple linear search, don't expect many entries! */ | ||
20 | static struct tcp_ulp_ops *tcp_ulp_find(const char *name) | ||
21 | { | ||
22 | struct tcp_ulp_ops *e; | ||
23 | |||
24 | list_for_each_entry_rcu(e, &tcp_ulp_list, list) { | ||
25 | if (strcmp(e->name, name) == 0) | ||
26 | return e; | ||
27 | } | ||
28 | |||
29 | return NULL; | ||
30 | } | ||
31 | |||
32 | static const struct tcp_ulp_ops *__tcp_ulp_find_autoload(const char *name) | ||
33 | { | ||
34 | const struct tcp_ulp_ops *ulp = NULL; | ||
35 | |||
36 | rcu_read_lock(); | ||
37 | ulp = tcp_ulp_find(name); | ||
38 | |||
39 | #ifdef CONFIG_MODULES | ||
40 | if (!ulp && capable(CAP_NET_ADMIN)) { | ||
41 | rcu_read_unlock(); | ||
42 | request_module("%s", name); | ||
43 | rcu_read_lock(); | ||
44 | ulp = tcp_ulp_find(name); | ||
45 | } | ||
46 | #endif | ||
47 | if (!ulp || !try_module_get(ulp->owner)) | ||
48 | ulp = NULL; | ||
49 | |||
50 | rcu_read_unlock(); | ||
51 | return ulp; | ||
52 | } | ||
53 | |||
54 | /* Attach new upper layer protocol to the list | ||
55 | * of available protocols. | ||
56 | */ | ||
57 | int tcp_register_ulp(struct tcp_ulp_ops *ulp) | ||
58 | { | ||
59 | int ret = 0; | ||
60 | |||
61 | spin_lock(&tcp_ulp_list_lock); | ||
62 | if (tcp_ulp_find(ulp->name)) { | ||
63 | pr_notice("%s already registered or non-unique name\n", | ||
64 | ulp->name); | ||
65 | ret = -EEXIST; | ||
66 | } else { | ||
67 | list_add_tail_rcu(&ulp->list, &tcp_ulp_list); | ||
68 | } | ||
69 | spin_unlock(&tcp_ulp_list_lock); | ||
70 | |||
71 | return ret; | ||
72 | } | ||
73 | EXPORT_SYMBOL_GPL(tcp_register_ulp); | ||
74 | |||
75 | void tcp_unregister_ulp(struct tcp_ulp_ops *ulp) | ||
76 | { | ||
77 | spin_lock(&tcp_ulp_list_lock); | ||
78 | list_del_rcu(&ulp->list); | ||
79 | spin_unlock(&tcp_ulp_list_lock); | ||
80 | |||
81 | synchronize_rcu(); | ||
82 | } | ||
83 | EXPORT_SYMBOL_GPL(tcp_unregister_ulp); | ||
84 | |||
85 | /* Build string with list of available upper layer protocl values */ | ||
86 | void tcp_get_available_ulp(char *buf, size_t maxlen) | ||
87 | { | ||
88 | struct tcp_ulp_ops *ulp_ops; | ||
89 | size_t offs = 0; | ||
90 | |||
91 | rcu_read_lock(); | ||
92 | list_for_each_entry_rcu(ulp_ops, &tcp_ulp_list, list) { | ||
93 | offs += snprintf(buf + offs, maxlen - offs, | ||
94 | "%s%s", | ||
95 | offs == 0 ? "" : " ", ulp_ops->name); | ||
96 | } | ||
97 | rcu_read_unlock(); | ||
98 | } | ||
99 | |||
100 | void tcp_cleanup_ulp(struct sock *sk) | ||
101 | { | ||
102 | struct inet_connection_sock *icsk = inet_csk(sk); | ||
103 | |||
104 | if (!icsk->icsk_ulp_ops) | ||
105 | return; | ||
106 | |||
107 | if (icsk->icsk_ulp_ops->release) | ||
108 | icsk->icsk_ulp_ops->release(sk); | ||
109 | module_put(icsk->icsk_ulp_ops->owner); | ||
110 | } | ||
111 | |||
112 | /* Change upper layer protocol for socket */ | ||
113 | int tcp_set_ulp(struct sock *sk, const char *name) | ||
114 | { | ||
115 | struct inet_connection_sock *icsk = inet_csk(sk); | ||
116 | const struct tcp_ulp_ops *ulp_ops; | ||
117 | int err = 0; | ||
118 | |||
119 | if (icsk->icsk_ulp_ops) | ||
120 | return -EEXIST; | ||
121 | |||
122 | ulp_ops = __tcp_ulp_find_autoload(name); | ||
123 | if (!ulp_ops) | ||
124 | err = -ENOENT; | ||
125 | else | ||
126 | err = ulp_ops->init(sk); | ||
127 | |||
128 | if (err) | ||
129 | goto out; | ||
130 | |||
131 | icsk->icsk_ulp_ops = ulp_ops; | ||
132 | out: | ||
133 | return err; | ||
134 | } | ||