1 /** 2 * @file 3 * Application layered TCP connection API that executes a proxy-connect. 4 * 5 * This file provides a starting layer that executes a proxy-connect e.g. to 6 * set up TLS connections through a http proxy. 7 */ 8 9 /* 10 * Copyright (c) 2018 Simon Goldschmidt 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without modification, 14 * are permitted provided that the following conditions are met: 15 * 16 * 1. Redistributions of source code must retain the above copyright notice, 17 * this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright notice, 19 * this list of conditions and the following disclaimer in the documentation 20 * and/or other materials provided with the distribution. 21 * 3. The name of the author may not be used to endorse or promote products 22 * derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 25 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 26 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 27 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 29 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 32 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 33 * OF SUCH DAMAGE. 34 * 35 * This file is part of the lwIP TCP/IP stack. 36 * 37 * Author: Simon Goldschmidt <[email protected]> 38 * 39 */ 40 41 #include "lwip/apps/altcp_proxyconnect.h" 42 43 #if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */ 44 45 #include "lwip/altcp.h" 46 #include "lwip/priv/altcp_priv.h" 47 48 #include "lwip/altcp_tcp.h" 49 #include "lwip/altcp_tls.h" 50 51 #include "lwip/mem.h" 52 #include "lwip/init.h" 53 54 /** This string is passed in the HTTP header as "User-Agent: " */ 55 #ifndef ALTCP_PROXYCONNECT_CLIENT_AGENT 56 #define ALTCP_PROXYCONNECT_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)" 57 #endif 58 59 #define ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED 0x01 60 #define ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE 0x02 61 62 typedef struct altcp_proxyconnect_state_s 63 { 64 ip_addr_t outer_addr; 65 u16_t outer_port; 66 struct altcp_proxyconnect_config *conf; 67 u8_t flags; 68 } altcp_proxyconnect_state_t; 69 70 /* Variable prototype, the actual declaration is at the end of this file 71 since it contains pointers to static functions declared here */ 72 extern const struct altcp_functions altcp_proxyconnect_functions; 73 74 /* memory management functions: */ 75 76 static altcp_proxyconnect_state_t * 77 altcp_proxyconnect_state_alloc(void) 78 { 79 altcp_proxyconnect_state_t *ret = (altcp_proxyconnect_state_t *)mem_calloc(1, sizeof(altcp_proxyconnect_state_t)); 80 return ret; 81 } 82 83 static void 84 altcp_proxyconnect_state_free(altcp_proxyconnect_state_t *state) 85 { 86 LWIP_ASSERT("state != NULL", state != NULL); 87 mem_free(state); 88 } 89 90 /* helper functions */ 91 92 #define PROXY_CONNECT "CONNECT %s:%d HTTP/1.1\r\n" /* HOST, PORT */ \ 93 "User-Agent: %s\r\n" /* User-Agent */\ 94 "Proxy-Connection: keep-alive\r\n" \ 95 "Connection: keep-alive\r\n" \ 96 "\r\n" 97 #define PROXY_CONNECT_FORMAT(host, port) PROXY_CONNECT, host, port, ALTCP_PROXYCONNECT_CLIENT_AGENT 98 99 /* Format the http proxy connect request via snprintf */ 100 static int 101 altcp_proxyconnect_format_request(char *buffer, size_t bufsize, const char *host, int port) 102 { 103 return snprintf(buffer, bufsize, PROXY_CONNECT_FORMAT(host, port)); 104 } 105 106 /* Create and send the http proxy connect request */ 107 static err_t 108 altcp_proxyconnect_send_request(struct altcp_pcb *conn) 109 { 110 int len, len2; 111 mem_size_t alloc_len; 112 char *buffer, *host; 113 altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state; 114 115 if (!state) { 116 return ERR_VAL; 117 } 118 /* Use printf with zero length to get the required allocation size */ 119 len = altcp_proxyconnect_format_request(NULL, 0, "", state->outer_port); 120 if (len < 0) { 121 return ERR_VAL; 122 } 123 /* add allocation size for IP address strings */ 124 #if LWIP_IPV6 125 len += 40; /* worst-case IPv6 address length */ 126 #else 127 len += 16; /* worst-case IPv4 address length */ 128 #endif 129 alloc_len = (mem_size_t)len; 130 if ((len < 0) || (int)alloc_len != len) { 131 /* overflow */ 132 return ERR_MEM; 133 } 134 /* Allocate a bufer for the request string */ 135 buffer = (char *)mem_malloc(alloc_len); 136 if (buffer == NULL) { 137 return ERR_MEM; 138 } 139 host = ipaddr_ntoa(&state->outer_addr); 140 len2 = altcp_proxyconnect_format_request(buffer, alloc_len, host, state->outer_port); 141 if ((len2 > 0) && (len2 <= len) && (len2 <= 0xFFFF)) { 142 err_t err = altcp_write(conn->inner_conn, buffer, (u16_t)len2, TCP_WRITE_FLAG_COPY); 143 if (err != ERR_OK) { 144 /* @todo: abort? */ 145 mem_free(buffer); 146 return err; 147 } 148 } 149 mem_free(buffer); 150 return ERR_OK; 151 } 152 153 /* callback functions from inner/lower connection: */ 154 155 /** Connected callback from lower connection (i.e. TCP). 156 * Not really implemented/tested yet... 157 */ 158 static err_t 159 altcp_proxyconnect_lower_connected(void *arg, struct altcp_pcb *inner_conn, err_t err) 160 { 161 struct altcp_pcb *conn = (struct altcp_pcb *)arg; 162 if (conn && conn->state) { 163 LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn); 164 LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */ 165 /* upper connected is called when handshake is done */ 166 if (err != ERR_OK) { 167 if (conn->connected) { 168 if (conn->connected(conn->arg, conn, err) == ERR_ABRT) { 169 return ERR_ABRT; 170 } 171 return ERR_OK; 172 } 173 } 174 /* send proxy connect request here */ 175 return altcp_proxyconnect_send_request(conn); 176 } 177 return ERR_VAL; 178 } 179 180 /** Recv callback from lower connection (i.e. TCP) 181 * This one mainly differs between connection setup (wait for proxy OK string) 182 * and application phase (data is passed on to the application). 183 */ 184 static err_t 185 altcp_proxyconnect_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err) 186 { 187 altcp_proxyconnect_state_t *state; 188 struct altcp_pcb *conn = (struct altcp_pcb *)arg; 189 190 LWIP_ASSERT("no err expected", err == ERR_OK); 191 LWIP_UNUSED_ARG(err); 192 193 if (!conn) { 194 /* no connection given as arg? should not happen, but prevent pbuf/conn leaks */ 195 if (p != NULL) { 196 pbuf_free(p); 197 } 198 altcp_close(inner_conn); 199 return ERR_CLSD; 200 } 201 state = (altcp_proxyconnect_state_t *)conn->state; 202 LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn); 203 if (!state) { 204 /* already closed */ 205 if (p != NULL) { 206 pbuf_free(p); 207 } 208 altcp_close(inner_conn); 209 return ERR_CLSD; 210 } 211 if (state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE) { 212 /* application phase, just pass this through */ 213 if (conn->recv) { 214 return conn->recv(conn->arg, conn, p, err); 215 } 216 pbuf_free(p); 217 return ERR_OK; 218 } else { 219 /* setup phase */ 220 /* handle NULL pbuf (inner connection closed) */ 221 if (p == NULL) { 222 if (altcp_close(conn) != ERR_OK) { 223 altcp_abort(conn); 224 return ERR_ABRT; 225 } 226 return ERR_OK; 227 } else { 228 /* @todo: parse setup phase rx data 229 for now, we just wait for the end of the header... */ 230 u16_t idx = pbuf_memfind(p, "\r\n\r\n", 4, 0); 231 altcp_recved(inner_conn, p->tot_len); 232 pbuf_free(p); 233 if (idx != 0xFFFF) { 234 state->flags |= ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE; 235 if (conn->connected) { 236 return conn->connected(conn->arg, conn, ERR_OK); 237 } 238 } 239 return ERR_OK; 240 } 241 } 242 } 243 244 /** Sent callback from lower connection (i.e. TCP) 245 * This only informs the upper layer to try to send more, not about 246 * the number of ACKed bytes. 247 */ 248 static err_t 249 altcp_proxyconnect_lower_sent(void *arg, struct altcp_pcb *inner_conn, u16_t len) 250 { 251 struct altcp_pcb *conn = (struct altcp_pcb *)arg; 252 LWIP_UNUSED_ARG(len); 253 if (conn) { 254 altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state; 255 LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn); 256 LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */ 257 if (!state || !(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) { 258 /* @todo: do something here? */ 259 return ERR_OK; 260 } 261 /* pass this on to upper sent */ 262 if (conn->sent) { 263 return conn->sent(conn->arg, conn, len); 264 } 265 } 266 return ERR_OK; 267 } 268 269 /** Poll callback from lower connection (i.e. TCP) 270 * Just pass this on to the application. 271 * @todo: retry sending? 272 */ 273 static err_t 274 altcp_proxyconnect_lower_poll(void *arg, struct altcp_pcb *inner_conn) 275 { 276 struct altcp_pcb *conn = (struct altcp_pcb *)arg; 277 if (conn) { 278 LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn); 279 LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */ 280 if (conn->poll) { 281 return conn->poll(conn->arg, conn); 282 } 283 } 284 return ERR_OK; 285 } 286 287 static void 288 altcp_proxyconnect_lower_err(void *arg, err_t err) 289 { 290 struct altcp_pcb *conn = (struct altcp_pcb *)arg; 291 if (conn) { 292 conn->inner_conn = NULL; /* already freed */ 293 if (conn->err) { 294 conn->err(conn->arg, err); 295 } 296 altcp_free(conn); 297 } 298 } 299 300 301 /* setup functions */ 302 303 static void 304 altcp_proxyconnect_setup_callbacks(struct altcp_pcb *conn, struct altcp_pcb *inner_conn) 305 { 306 altcp_arg(inner_conn, conn); 307 altcp_recv(inner_conn, altcp_proxyconnect_lower_recv); 308 altcp_sent(inner_conn, altcp_proxyconnect_lower_sent); 309 altcp_err(inner_conn, altcp_proxyconnect_lower_err); 310 /* tcp_poll is set when interval is set by application */ 311 /* listen is set totally different :-) */ 312 } 313 314 static err_t 315 altcp_proxyconnect_setup(struct altcp_proxyconnect_config *config, struct altcp_pcb *conn, struct altcp_pcb *inner_conn) 316 { 317 altcp_proxyconnect_state_t *state; 318 if (!config) { 319 return ERR_ARG; 320 } 321 LWIP_ASSERT("invalid inner_conn", conn != inner_conn); 322 323 /* allocate proxyconnect context */ 324 state = altcp_proxyconnect_state_alloc(); 325 if (state == NULL) { 326 return ERR_MEM; 327 } 328 state->flags = 0; 329 state->conf = config; 330 altcp_proxyconnect_setup_callbacks(conn, inner_conn); 331 conn->inner_conn = inner_conn; 332 conn->fns = &altcp_proxyconnect_functions; 333 conn->state = state; 334 return ERR_OK; 335 } 336 337 /** Allocate a new altcp layer connecting through a proxy. 338 * This function gets the inner pcb passed. 339 * 340 * @param config struct altcp_proxyconnect_config that contains the proxy settings 341 * @param inner_pcb pcb that makes the connection to the proxy (i.e. tcp pcb) 342 */ 343 struct altcp_pcb * 344 altcp_proxyconnect_new(struct altcp_proxyconnect_config *config, struct altcp_pcb *inner_pcb) 345 { 346 struct altcp_pcb *ret; 347 if (inner_pcb == NULL) { 348 return NULL; 349 } 350 ret = altcp_alloc(); 351 if (ret != NULL) { 352 if (altcp_proxyconnect_setup(config, ret, inner_pcb) != ERR_OK) { 353 altcp_free(ret); 354 return NULL; 355 } 356 } 357 return ret; 358 } 359 360 /** Allocate a new altcp layer connecting through a proxy. 361 * This function allocates the inner pcb as tcp pcb, resulting in a direct tcp 362 * connection to the proxy. 363 * 364 * @param config struct altcp_proxyconnect_config that contains the proxy settings 365 * @param ip_type IP type of the connection (@ref lwip_ip_addr_type) 366 */ 367 struct altcp_pcb * 368 altcp_proxyconnect_new_tcp(struct altcp_proxyconnect_config *config, u8_t ip_type) 369 { 370 struct altcp_pcb *inner_pcb, *ret; 371 372 /* inner pcb is tcp */ 373 inner_pcb = altcp_tcp_new_ip_type(ip_type); 374 if (inner_pcb == NULL) { 375 return NULL; 376 } 377 ret = altcp_proxyconnect_new(config, inner_pcb); 378 if (ret == NULL) { 379 altcp_close(inner_pcb); 380 } 381 return ret; 382 } 383 384 /** Allocator function to allocate a proxy connect altcp pcb connecting directly 385 * via tcp to the proxy. 386 * 387 * The returned pcb is a chain: altcp_proxyconnect - altcp_tcp - tcp pcb 388 * 389 * This function is meant for use with @ref altcp_new. 390 * 391 * @param arg struct altcp_proxyconnect_config that contains the proxy settings 392 * @param ip_type IP type of the connection (@ref lwip_ip_addr_type) 393 */ 394 struct altcp_pcb * 395 altcp_proxyconnect_alloc(void *arg, u8_t ip_type) 396 { 397 return altcp_proxyconnect_new_tcp((struct altcp_proxyconnect_config *)arg, ip_type); 398 } 399 400 401 #if LWIP_ALTCP_TLS 402 403 /** Allocator function to allocate a TLS connection through a proxy. 404 * 405 * The returned pcb is a chain: altcp_tls - altcp_proxyconnect - altcp_tcp - tcp pcb 406 * 407 * This function is meant for use with @ref altcp_new. 408 * 409 * @param arg struct altcp_proxyconnect_tls_config that contains the proxy settings 410 * and tls settings 411 * @param ip_type IP type of the connection (@ref lwip_ip_addr_type) 412 */ 413 struct altcp_pcb * 414 altcp_proxyconnect_tls_alloc(void *arg, u8_t ip_type) 415 { 416 struct altcp_proxyconnect_tls_config *cfg = (struct altcp_proxyconnect_tls_config *)arg; 417 struct altcp_pcb *proxy_pcb; 418 struct altcp_pcb *tls_pcb; 419 420 proxy_pcb = altcp_proxyconnect_new_tcp(&cfg->proxy, ip_type); 421 tls_pcb = altcp_tls_wrap(cfg->tls_config, proxy_pcb); 422 423 if (tls_pcb == NULL) { 424 altcp_close(proxy_pcb); 425 } 426 return tls_pcb; 427 } 428 #endif /* LWIP_ALTCP_TLS */ 429 430 /* "virtual" functions */ 431 static void 432 altcp_proxyconnect_set_poll(struct altcp_pcb *conn, u8_t interval) 433 { 434 if (conn != NULL) { 435 altcp_poll(conn->inner_conn, altcp_proxyconnect_lower_poll, interval); 436 } 437 } 438 439 static void 440 altcp_proxyconnect_recved(struct altcp_pcb *conn, u16_t len) 441 { 442 altcp_proxyconnect_state_t *state; 443 if (conn == NULL) { 444 return; 445 } 446 state = (altcp_proxyconnect_state_t *)conn->state; 447 if (state == NULL) { 448 return; 449 } 450 if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) { 451 return; 452 } 453 altcp_recved(conn->inner_conn, len); 454 } 455 456 static err_t 457 altcp_proxyconnect_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port, altcp_connected_fn connected) 458 { 459 altcp_proxyconnect_state_t *state; 460 461 if ((conn == NULL) || (ipaddr == NULL)) { 462 return ERR_VAL; 463 } 464 state = (altcp_proxyconnect_state_t *)conn->state; 465 if (state == NULL) { 466 return ERR_VAL; 467 } 468 if (state->flags & ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED) { 469 return ERR_VAL; 470 } 471 state->flags |= ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED; 472 473 conn->connected = connected; 474 /* connect to our proxy instead, but store the requested address and port */ 475 ip_addr_copy(state->outer_addr, *ipaddr); 476 state->outer_port = port; 477 478 return altcp_connect(conn->inner_conn, &state->conf->proxy_addr, state->conf->proxy_port, altcp_proxyconnect_lower_connected); 479 } 480 481 static struct altcp_pcb * 482 altcp_proxyconnect_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err) 483 { 484 LWIP_UNUSED_ARG(conn); 485 LWIP_UNUSED_ARG(backlog); 486 LWIP_UNUSED_ARG(err); 487 /* listen not supported! */ 488 return NULL; 489 } 490 491 static void 492 altcp_proxyconnect_abort(struct altcp_pcb *conn) 493 { 494 if (conn != NULL) { 495 if (conn->inner_conn != NULL) { 496 altcp_abort(conn->inner_conn); 497 } 498 altcp_free(conn); 499 } 500 } 501 502 static err_t 503 altcp_proxyconnect_close(struct altcp_pcb *conn) 504 { 505 if (conn == NULL) { 506 return ERR_VAL; 507 } 508 if (conn->inner_conn != NULL) { 509 err_t err = altcp_close(conn->inner_conn); 510 if (err != ERR_OK) { 511 /* closing inner conn failed, return the error */ 512 return err; 513 } 514 } 515 /* no inner conn or closing it succeeded, deallocate myself */ 516 altcp_free(conn); 517 return ERR_OK; 518 } 519 520 static err_t 521 altcp_proxyconnect_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, u8_t apiflags) 522 { 523 altcp_proxyconnect_state_t *state; 524 525 LWIP_UNUSED_ARG(apiflags); 526 527 if (conn == NULL) { 528 return ERR_VAL; 529 } 530 531 state = (altcp_proxyconnect_state_t *)conn->state; 532 if (state == NULL) { 533 /* @todo: which error? */ 534 return ERR_CLSD; 535 } 536 if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) { 537 /* @todo: which error? */ 538 return ERR_VAL; 539 } 540 return altcp_write(conn->inner_conn, dataptr, len, apiflags); 541 } 542 543 static void 544 altcp_proxyconnect_dealloc(struct altcp_pcb *conn) 545 { 546 /* clean up and free tls state */ 547 if (conn) { 548 altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state; 549 if (state) { 550 altcp_proxyconnect_state_free(state); 551 conn->state = NULL; 552 } 553 } 554 } 555 const struct altcp_functions altcp_proxyconnect_functions = { 556 altcp_proxyconnect_set_poll, 557 altcp_proxyconnect_recved, 558 altcp_default_bind, 559 altcp_proxyconnect_connect, 560 altcp_proxyconnect_listen, 561 altcp_proxyconnect_abort, 562 altcp_proxyconnect_close, 563 altcp_default_shutdown, 564 altcp_proxyconnect_write, 565 altcp_default_output, 566 altcp_default_mss, 567 altcp_default_sndbuf, 568 altcp_default_sndqueuelen, 569 altcp_default_nagle_disable, 570 altcp_default_nagle_enable, 571 altcp_default_nagle_disabled, 572 altcp_default_setprio, 573 altcp_proxyconnect_dealloc, 574 altcp_default_get_tcp_addrinfo, 575 altcp_default_get_ip, 576 altcp_default_get_port 577 #ifdef LWIP_DEBUG 578 , altcp_default_dbg_get_tcp_state 579 #endif 580 }; 581 582 #endif /* LWIP_ALTCP */ 583