diff --git a/Makefile b/Makefile index 175e274..19dc991 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,11 @@ override LDFLAGS += uSockets/*.o -lz DESTDIR ?= prefix ?= /usr/local +# WITH_PROXY enables PROXY Protocol v2 support +ifeq ($(WITH_PROXY),1) + override CXXFLAGS += -DWITH_PROXY +endif + # WITH_OPENSSL=1 enables OpenSSL 1.1+ support ifeq ($(WITH_OPENSSL),1) # With problems on macOS, make sure to pass needed LDFLAGS required to find these diff --git a/src/HttpContext.h b/src/HttpContext.h index 6e82591..89c7b7d 100644 --- a/src/HttpContext.h +++ b/src/HttpContext.h @@ -1,5 +1,5 @@ /* - * Authored by Alex Hultman, 2018-2019. + * Authored by Alex Hultman, 2018-2020. * Intellectual property of third-party. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -126,8 +126,13 @@ private: // clients need to know the cursor after http parse, not servers! // how far did we read then? we need to know to continue with websocket parsing data? or? + void *proxyParser = nullptr; +#ifdef WITH_PROXY + proxyParser = &httpResponseData->proxyParser; +#endif + /* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */ - void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * { + void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, proxyParser, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * { /* For every request we reset the timeout and hang until user makes action */ /* Warning: if we are in shutdown state, resetting the timer is a security issue! */ us_socket_timeout(SSL, (us_socket_t *) s, 0); diff --git a/src/HttpParser.h b/src/HttpParser.h index 97fbc6c..557f292 100644 --- a/src/HttpParser.h +++ b/src/HttpParser.h @@ -1,5 +1,5 @@ /* - * Authored by Alex Hultman, 2018-2019. + * Authored by Alex Hultman, 2018-2020. * Intellectual property of third-party. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,8 +28,13 @@ #include "f2/function2.hpp" #include "BloomFilter.h" + + +// if using proxy parser, depend on the layout of HttpResponesData #include "ProxyParser.h" + + namespace uWS { /* We require at least this much post padding */ @@ -178,8 +183,28 @@ private: // the only caller of getHeaders template - std::pair fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, fu2::unique_function &requestHandler, fu2::unique_function &dataHandler) { + std::pair fenceAndConsumePostPadded(char *data, int length, void *user, void *reserved, HttpRequest *req, fu2::unique_function &requestHandler, fu2::unique_function &dataHandler) { + + /* How much data we CONSUMED (to throw away) */ int consumedTotal = 0; + +#ifdef WITH_PROXY + /* ProxyParser is passed as reserved parameter */ + ProxyParser *pp = (ProxyParser *) reserved; + + /* Parse PROXY protocol */ + auto [done, offset] = pp->parse({data, length}); + if (!done) { + return {0, user}; + } else { + /* We have consumed this data so skip it */ + data += offset; + length -= offset; + consumedTotal += offset; + } +#endif + + /* Fence one byte past end of our buffer (buffer has post padded margins) */ data[length] = '\r'; for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers, &req->bf)); ) { @@ -236,13 +261,7 @@ private: } public: - - /* We do this to prolong the validity of parsed headers by keeping only the fallback buffer alive */ - /*std::string &&salvageFallbackBuffer() { - return std::move(fallback); - }*/ - - void *consumePostPadded(char *data, int length, void *user, fu2::unique_function &&requestHandler, fu2::unique_function &&dataHandler, fu2::unique_function &&errorHandler) { + void *consumePostPadded(char *data, int length, void *user, void *reserved, fu2::unique_function &&requestHandler, fu2::unique_function &&dataHandler, fu2::unique_function &&errorHandler) { HttpRequest req; @@ -276,11 +295,8 @@ public: fallback.reserve(fallback.length() + maxCopyDistance + std::max(MINIMUM_HTTP_POST_PADDING, sizeof(std::string))); fallback.append(data, maxCopyDistance); - // parse proxy here - - // break here on break - std::pair consumed = fenceAndConsumePostPadded(fallback.data(), (int) fallback.length(), user, &req, requestHandler, dataHandler); + std::pair consumed = fenceAndConsumePostPadded(fallback.data(), (int) fallback.length(), user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { return consumed.second; } @@ -322,18 +338,7 @@ public: } } - // parse proxy here - /* Parse proxy header */ - ProxyParser pp; - auto [done, offset] = pp.parse({data, length}); - - if (!done) { - - } else { - printf("Proxy parser is done\n"); - } - - std::pair consumed = fenceAndConsumePostPadded(data, length, user, &req, requestHandler, dataHandler); + std::pair consumed = fenceAndConsumePostPadded(data, length, user, reserved, &req, requestHandler, dataHandler); if (consumed.second != user) { return consumed.second; } diff --git a/src/HttpResponse.h b/src/HttpResponse.h index 98bba55..b50d900 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -55,7 +55,7 @@ private: /* If we have proxy support */ #ifdef WITH_PROXY void getProxiedRemoteAddress() { - + getHttpResponseData()->proxyParser.getSourceIp(); } #endif diff --git a/src/HttpResponseData.h b/src/HttpResponseData.h index 13540f7..c81d475 100644 --- a/src/HttpResponseData.h +++ b/src/HttpResponseData.h @@ -1,5 +1,5 @@ /* - * Authored by Alex Hultman, 2018-2019. + * Authored by Alex Hultman, 2018-2020. * Intellectual property of third-party. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,8 @@ #include "f2/function2.hpp" +#include "ProxyParser.h" + namespace uWS { template @@ -50,6 +52,11 @@ private: /* Current state (content-length sent, status sent, write called, etc */ int state = 0; + +#ifdef WITH_PROXY + // proxy protocol + ProxyParser proxyParser; +#endif }; } diff --git a/src/ProxyParser.h b/src/ProxyParser.h index 38f0ba8..a6d0b79 100644 --- a/src/ProxyParser.h +++ b/src/ProxyParser.h @@ -2,43 +2,95 @@ // implements PROXY v2 protocol +#ifndef PROXY_PARSER +#define PROXY_PARSER +#ifdef WITH_PROXY + +struct proxy_hdr_v2 { + uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + uint8_t ver_cmd; /* protocol version and command */ + uint8_t fam; /* protocol family and address */ + uint16_t len; /* number of following bytes part of the header */ +}; + +/* Byte swap for little-endian systems */ +template +T _cond_byte_swap(T value) { + uint32_t endian_test = 1; + if (*((char *)&endian_test)) { + union { + T i; + uint8_t b[sizeof(T)]; + } src = { value }, dst; + + for (unsigned int i = 0; i < sizeof(value); i++) { + dst.b[i] = src.b[sizeof(value) - 1 - i]; + } + + return dst.i; + } + return value; +} struct ProxyParser { - int done = false; +private: + unsigned char sourceIp[16]; + unsigned char destIp[16]; + uint16_t sourcePort, destPort; - // 16 byte IP, 2 byte port - // 16 byte our IP, 2 byte our port +public: + /* Returns 4 or 16 bytes */ + std::string_view getSourceIP() { + return {"hello", 5}; + } - // return true when done, always return next offset for http parsing + /* Returns [done, consumed] where done = false on failure */ std::pair parse(std::string_view data) { - /* If already parsed, we're done */ - if (done) { + /* We require at least one byte to be done */ + if (!data.length()) { + return {false, 0}; + } + + /* HTTP does not start with \r, but PROXY always does */ + if (data[0] != '\r') { + //printf("This is HTTP\n"); + /* This is HTTP, so be done */ return {true, 0}; } - // we require 4 bytes to determine if this is http or not - if (data.length() < 4) { - // we are not done, buffer everything + /* We assume we are parsing PROXY V2 here */ + printf("This is PROXY v2\n"); + + /* We require 16 bytes here */ + if (data.length() < 16) { return {false, 0}; - } else { - - - // is this proxy protocol? - if (memcmp("\r\n\r\n", data.data(), 4) == 0) { - - - - } else { - // it cannot be proxy protocol here, so we are done now - done = true; - return {true, 0}; - } - - } + struct proxy_hdr_v2 header; + memcpy(&header, data.data(), 16); + + if (memcmp(header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12)) { + /* This is not PROXY protocol at all */ + return {false, 0}; + } + + printf("Version: %d\n", (header.ver_cmd & 0xf0) >> 4); + printf("Command: %d\n", (header.ver_cmd & 0x0f)); + + uint16_t hostLength = _cond_byte_swap(header.len); + + printf("Length: %d\n", hostLength); + + printf("Family: %d\n", (header.fam & 0xf0) >> 4); + printf("Transport: %d\n", (header.fam & 0x0f)); + + return {true, 16 + hostLength}; } -}; \ No newline at end of file +}; + +#endif + +#endif \ No newline at end of file