aboutsummaryrefslogtreecommitdiff
path: root/proxy-proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'proxy-proto.c')
-rw-r--r--proxy-proto.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/proxy-proto.c b/proxy-proto.c
new file mode 100644
index 0000000..e90c851
--- /dev/null
+++ b/proxy-proto.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2024 github.com/Sir-Photch <sir-photch@posteo.me>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "gmid.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#define MIN(a, b) (a) < (b) ? (a) : (b)
+
+static int
+check_prefix_v1(char **buf)
+{
+ static const char PROXY[6] = "PROXY ";
+
+ if (0 != strncmp(*buf, PROXY, sizeof(PROXY)))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *buf += sizeof(PROXY);
+
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+static int
+check_proto_v1(char **buf)
+{
+ static const char TCP[3] = "TCP";
+
+ if (0 != strncmp(*buf, TCP, sizeof(TCP)))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *buf += sizeof(TCP);
+
+ int type;
+ switch ((*buf)[0]) {
+ case '4': type = 4; break;
+ case '6': type = 6; break;
+ default: return PROXY_PROTO_PARSE_FAIL;
+ }
+
+ // '4' / '6' + ' '
+ *buf += 2;
+
+ return type;
+}
+
+static int
+check_unknown_v1(char **buf)
+{
+ static const char UNKNOWN[7] = "UNKNOWN";
+
+ if (0 != strncmp(*buf, UNKNOWN, sizeof(UNKNOWN)))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *buf += sizeof(UNKNOWN);
+
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+static int
+check_crlf_v1(char *const *buf, size_t buflen)
+{
+ static const char CRLF[2] = "\r\n";
+
+ if (buflen < sizeof(CRLF))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ if (!memmem(*buf, buflen, CRLF, sizeof(CRLF)))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+static int
+check_ip_v1(int af, void *addr, char **buf)
+{
+ char *spc;
+
+ if (NULL == (spc = strchr(*buf, ' ')))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *spc++ = '\0';
+
+ if (1 != inet_pton(af, *buf, addr))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *buf = spc;
+
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+static int
+check_port_v1(uint16_t *port, char **buf, size_t *buflen)
+{
+ size_t wspc_idx = strcspn(*buf, " \r");
+ char *wspc = *buf + wspc_idx;
+
+ if (!(' ' == *wspc || '\r' == *wspc))
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *wspc++ = '\0';
+
+ const char *errstr;
+ long long num = strtonum(*buf, 0, UINT16_MAX, &errstr);
+ if (errstr)
+ return PROXY_PROTO_PARSE_FAIL;
+
+ *buf = wspc;
+ *port = num;
+
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+#define EXPECT_SUCCESS(call) do { \
+ ret = (call); \
+ if (PROXY_PROTO_PARSE_SUCCESS != ret) \
+ return ret; \
+} while (0)
+
+int proxy_proto_v1_parse(struct proxy_protocol_v1 *s, char *buf, size_t buflen, size_t *consumed)
+{
+ const char *begin = buf;
+ int ret;
+
+ EXPECT_SUCCESS(check_crlf_v1(&buf, buflen));
+ EXPECT_SUCCESS(check_prefix_v1(&buf));
+
+ ret = check_proto_v1(&buf);
+ switch (ret)
+ {
+ case 4: s->proto = PROTO_V4; break;
+ case 6: s->proto = PROTO_V6; break;
+ case PROXY_PROTO_PARSE_FAIL: {
+ EXPECT_SUCCESS(check_unknown_v1(&buf));
+
+ s->proto = PROTO_UNKNOWN;
+ return PROXY_PROTO_PARSE_SUCCESS;
+ } break;
+ default: return ret;
+ }
+
+ switch (s->proto)
+ {
+ case PROTO_V4: {
+ EXPECT_SUCCESS(check_ip_v1(AF_INET, &s->srcaddr.v4, &buf));
+ EXPECT_SUCCESS(check_ip_v1(AF_INET, &s->dstaddr.v4, &buf));
+ } break;
+
+ case PROTO_V6: {
+ EXPECT_SUCCESS(check_ip_v1(AF_INET6, &s->srcaddr.v6, &buf));
+ EXPECT_SUCCESS(check_ip_v1(AF_INET6, &s->dstaddr.v6, &buf));
+ } break;
+
+ default: ASSERT_MSG(0, "unimplemented");
+ }
+
+ EXPECT_SUCCESS(check_port_v1(&s->srcport, &buf, &buflen));
+ EXPECT_SUCCESS(check_port_v1(&s->dstport, &buf, &buflen));
+
+ assert('\n' == *buf);
+ buf += 1;
+
+ *consumed = buf - begin;
+ return PROXY_PROTO_PARSE_SUCCESS;
+}
+
+int
+proxy_proto_v1_string(const struct proxy_protocol_v1 *s, char* buf, size_t buflen)
+{
+ // "0000:0000:0000:0000:0000:0000:0000:0000\0"
+ char srcaddrbuf[40], dstaddrbuf[40];
+ int ret;
+ switch (s->proto)
+ {
+ case PROTO_UNKNOWN: ret = snprintf(buf, buflen, "unknown"); goto fin;
+ case PROTO_V4: {
+ inet_ntop(AF_INET, &s->srcaddr.v4, srcaddrbuf, 39);
+ inet_ntop(AF_INET, &s->dstaddr.v4, dstaddrbuf, 39);
+ } break;
+ case PROTO_V6: {
+ inet_ntop(AF_INET6, &s->srcaddr.v6, srcaddrbuf, 39);
+ inet_ntop(AF_INET6, &s->dstaddr.v6, dstaddrbuf, 39);
+ } break;
+ }
+
+ srcaddrbuf[39] = dstaddrbuf[39] = '\0';
+
+ ret = snprintf(
+ buf,
+ buflen,
+ "from %s port %u via %s port %u",
+ srcaddrbuf,
+ s->srcport,
+ dstaddrbuf,
+ s->dstport
+ );
+
+fin:
+ return ret;
+}