blob: 2770385c52f4ef7a693a2abc19ff6e84b36c9ccc [file] [log] [blame]
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001/*
2 * Pseudo-xprt to handle any handshake except the SSL handshake
3 *
4 * Copyright 2019 HAProxy Technologies, Olivier Houchard <ohouchard@haproxy.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <proto/connection.h>
14#include <proto/stream_interface.h>
15
16struct xprt_handshake_ctx {
17 struct connection *conn;
18 struct wait_event *send_wait;
19 struct wait_event *recv_wait;
20 struct wait_event wait_event;
21 const struct xprt_ops *xprt;
22 void *xprt_ctx;
23};
24
25DECLARE_STATIC_POOL(xprt_handshake_ctx_pool, "xprt_handshake_ctx_pool", sizeof(struct xprt_handshake_ctx));
26
27/* This XPRT doesn't take care of sending or receiving data, once its handshake
28 * is done, it just removes itself
29 */
30static size_t xprt_handshake_from_buf(struct connection *conn, void *xprt_ctx, const struct buffer *buf, size_t count, int flags)
31{
32 return 0;
33}
34
35static size_t xprt_handshake_to_buf(struct connection *conn, void *xprt_ctx, struct buffer *buf, size_t count, int flags)
36{
37 return 0;
38}
39
40static struct task *xprt_handshake_io_cb(struct task *t, void *bctx, unsigned short state)
41{
42 struct xprt_handshake_ctx *ctx = bctx;
43 struct connection *conn = ctx->conn;
44
45 if (conn->flags & CO_FL_SOCKS4_SEND)
46 if (!conn_send_socks4_proxy_request(conn)) {
47 ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
48 &ctx->wait_event);
49
50 goto out;
51 }
52
53 if (conn->flags & CO_FL_SOCKS4_RECV)
54 if (!conn_recv_socks4_proxy_response(conn)) {
55 ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
56 &ctx->wait_event);
57 goto out;
58 }
59
60 if (conn->flags & CO_FL_ACCEPT_CIP)
61 if (!conn_recv_netscaler_cip(conn, CO_FL_ACCEPT_CIP)) {
62 ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
63 &ctx->wait_event);
64 goto out;
65 }
66
67 if (conn->flags & CO_FL_ACCEPT_PROXY)
68 if (!conn_recv_proxy(conn, CO_FL_ACCEPT_PROXY)) {
69 ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_RECV,
70 &ctx->wait_event);
71 goto out;
72 }
73
74 if (conn->flags & CO_FL_SEND_PROXY)
75 if (!conn_si_send_proxy(conn, CO_FL_SEND_PROXY)) {
76 ctx->xprt->subscribe(conn, ctx->xprt_ctx, SUB_RETRY_SEND,
77 &ctx->wait_event);
78 goto out;
79 }
80
81out:
82 /* Wake the stream if we're done with the handshake, or we have a
83 * connection error
84 * */
85 if ((conn->flags & CO_FL_ERROR) ||
86 !(conn->flags & CO_FL_HANDSHAKE_NOSSL)) {
87 int ret = 0;
88 int woke = 0;
89 int was_conn_ctx = 0;
90 /* On error, wake any waiter */
91 if (ctx->recv_wait) {
92 ctx->recv_wait->events &= ~SUB_RETRY_RECV;
93 tasklet_wakeup(ctx->recv_wait->task);
94 woke = 1;
95 ctx->recv_wait = NULL;
96 }
97 if (ctx->send_wait) {
98 ctx->send_wait->events &= ~SUB_RETRY_SEND;
99 tasklet_wakeup(ctx->send_wait->task);
100 woke = 1;
101 ctx->send_wait = NULL;
102 }
103 if (!(conn->flags & CO_FL_ERROR))
104 conn->flags |= CO_FL_CONNECTED;
105 /* Remove ourself from the xprt chain */
106 if (ctx->wait_event.events != 0)
107 ctx->xprt->unsubscribe(ctx->conn,
108 ctx->xprt_ctx,
109 ctx->wait_event.events,
110 &ctx->wait_event);
111 if (conn->xprt_ctx == ctx) {
112 conn->xprt_ctx = ctx->xprt_ctx;
113 conn->xprt = ctx->xprt;
114 was_conn_ctx = 1;
115 } else
116 conn->xprt->remove_xprt(conn, conn->xprt_ctx, ctx,
117 ctx->xprt, ctx->xprt_ctx);
118 /* If we're the first xprt for the connection, let the
119 * upper layers know. If xprt_done_cb() is set, call it,
120 * and if we have a mux, and it has a wake method, call it
121 * too.
122 */
123 if (was_conn_ctx) {
124 if (ctx->conn->xprt_done_cb)
125 ret = ctx->conn->xprt_done_cb(ctx->conn);
126 if (ret >= 0 && !woke && ctx->conn->mux && ctx->conn->mux->wake)
127 ret = ctx->conn->mux->wake(ctx->conn);
128 }
129 tasklet_free(ctx->wait_event.task);
130 pool_free(xprt_handshake_ctx_pool, ctx);
131 }
132 return NULL;
133}
134
135static int xprt_handshake_init(struct connection *conn, void **xprt_ctx)
136{
137 struct xprt_handshake_ctx *ctx;
138 /* already initialized */
139 if (*xprt_ctx)
140 return 0;
141 if (!conn_ctrl_ready(conn))
142 return 0;
143
144 ctx = pool_alloc(xprt_handshake_ctx_pool);
145 if (!ctx) {
146 conn->err_code = CO_ER_SSL_NO_MEM;
147 return -1;
148 }
149 ctx->conn = conn;
150 ctx->wait_event.task = tasklet_new();
151 if (!ctx->wait_event.task) {
152 conn->err_code = CO_ER_SSL_NO_MEM;
153 pool_free(xprt_handshake_ctx_pool, ctx);
154 return -1;
155 }
156 ctx->wait_event.task->process = xprt_handshake_io_cb;
157 ctx->wait_event.task->context = ctx;
158 ctx->wait_event.events = 0;
159 /* This XPRT expects the underlying XPRT to be provided later,
160 * with an add_xprt() call, so we start trying to do the handshake
161 * there, when we'll be provided an XPRT.
162 */
163 ctx->xprt = NULL;
164 ctx->xprt_ctx = NULL;
165 ctx->send_wait = ctx->recv_wait = NULL;
166 *xprt_ctx = ctx;
167
168 return 0;
169}
170
171static void xprt_handshake_close(struct connection *conn, void *xprt_ctx)
172{
173 struct xprt_handshake_ctx *ctx = xprt_ctx;
174
175 if (ctx) {
176 if (ctx->wait_event.events != 0)
177 ctx->xprt->unsubscribe(ctx->conn, ctx->xprt_ctx,
178 ctx->wait_event.events,
179 &ctx->wait_event);
180 if (ctx->send_wait) {
181 ctx->send_wait->events &= ~SUB_RETRY_SEND;
182 tasklet_wakeup(ctx->send_wait->task);
183 }
184 if (ctx->recv_wait) {
185 ctx->recv_wait->events &= ~SUB_RETRY_RECV;
186 tasklet_wakeup(ctx->recv_wait->task);
187 }
188
189 if (ctx->xprt && ctx->xprt->close)
190 ctx->xprt->close(conn, ctx->xprt_ctx);
191 /* Remove any handshake flag, and if we were the connection
192 * xprt, get back to XPRT_RAW. If we're here because we
193 * failed an outoging connection, it will be retried using
194 * the same struct connection, and as xprt_handshake is a bit
195 * magic, because it requires a call to add_xprt(), it's better
196 * to fallback to the original XPRT to re-initiate the
197 * connection.
198 */
199 conn->flags &= ~CO_FL_HANDSHAKE_NOSSL;
200 if (conn->xprt == xprt_get(XPRT_HANDSHAKE))
201 conn->xprt = xprt_get(XPRT_RAW);
202 tasklet_free(ctx->wait_event.task);
203 pool_free(xprt_handshake_ctx_pool, ctx);
204 }
205}
206
207static int xprt_handshake_subscribe(struct connection *conn, void *xprt_ctx, int event_type, void *param)
208{
209 struct wait_event *sw;
210 struct xprt_handshake_ctx *ctx = xprt_ctx;
211
212 if (event_type & SUB_RETRY_RECV) {
213 sw = param;
214 BUG_ON(ctx->recv_wait != NULL || (sw->events & SUB_RETRY_RECV));
215 sw->events |= SUB_RETRY_RECV;
216 ctx->recv_wait = sw;
217 event_type &= ~SUB_RETRY_RECV;
218 }
219 if (event_type & SUB_RETRY_SEND) {
220 sw = param;
221 BUG_ON(ctx->send_wait != NULL || (sw->events & SUB_RETRY_SEND));
222 sw->events |= SUB_RETRY_SEND;
223 ctx->send_wait = sw;
224 event_type &= ~SUB_RETRY_SEND;
225 }
226 if (event_type != 0)
227 return -1;
228 return 0;
229
230}
231
232static int xprt_handshake_unsubscribe(struct connection *conn, void *xprt_ctx, int event_type, void *param)
233{
234 struct wait_event *sw;
235 struct xprt_handshake_ctx *ctx = xprt_ctx;
236
237 if (event_type & SUB_RETRY_RECV) {
238 sw = param;
239 BUG_ON(ctx->recv_wait != sw);
240 ctx->recv_wait = NULL;
241 sw->events &= ~SUB_RETRY_RECV;
242 }
243 if (event_type & SUB_RETRY_SEND) {
244 sw = param;
245 BUG_ON(ctx->send_wait != sw);
246 ctx->send_wait = NULL;
247 sw->events &= ~SUB_RETRY_SEND;
248 }
249 return 0;
250}
251
252/* Use the provided XPRT as an underlying XPRT, and provide the old one.
253 * Returns 0 on success, and non-zero on failure.
254 */
255static int xprt_handshake_add_xprt(struct connection *conn, void *xprt_ctx, void *toadd_ctx, const struct xprt_ops *toadd_ops, void **oldxprt_ctx, const struct xprt_ops **oldxprt_ops)
256{
257 struct xprt_handshake_ctx *ctx = xprt_ctx;
258
259 if (oldxprt_ops)
260 *oldxprt_ops = ctx->xprt;
261 if (oldxprt_ctx)
262 *oldxprt_ctx = ctx->xprt_ctx;
263 ctx->xprt = toadd_ops;
264 ctx->xprt_ctx = toadd_ctx;
265 /* Ok we know have an xprt, so let's try to do the handshake */
266 tasklet_wakeup(ctx->wait_event.task);
267 return 0;
268}
269
270/* Remove the specified xprt. If if it our underlying XPRT, remove it and
271 * return 0, otherwise just call the remove_xprt method from the underlying
272 * XPRT.
273 */
274static int xprt_handshake_remove_xprt(struct connection *conn, void *xprt_ctx, void *toremove_ctx, const struct xprt_ops *newops, void *newctx)
275{
276 struct xprt_handshake_ctx *ctx = xprt_ctx;
277
278 if (ctx->xprt_ctx == toremove_ctx) {
279 ctx->xprt_ctx = newctx;
280 ctx->xprt = newops;
281 return 0;
282 }
283 return (ctx->xprt->remove_xprt(conn, ctx->xprt_ctx, toremove_ctx, newops, newctx));
284}
285
286struct xprt_ops xprt_handshake = {
287 .snd_buf = xprt_handshake_from_buf,
288 .rcv_buf = xprt_handshake_to_buf,
289 .subscribe = xprt_handshake_subscribe,
290 .unsubscribe = xprt_handshake_unsubscribe,
291 .remove_xprt = xprt_handshake_remove_xprt,
292 .add_xprt = xprt_handshake_add_xprt,
293 .init = xprt_handshake_init,
294 .close= xprt_handshake_close,
295 .rcv_pipe = NULL,
296 .snd_pipe = NULL,
297 .shutr = NULL,
298 .shutw = NULL,
299 .name = "HS",
300};
301
302__attribute__((constructor))
303static void __xprt_handshake_init(void)
304{
305 xprt_register(XPRT_HANDSHAKE, &xprt_handshake);
306}