Skip to content

Commit bfacb77

Browse files
committed
Added X-Forwarded-{For,Proto} support.
Initially from mattsta@713edb2, updated to apply to latest bumptech/stud master. Uses joyent's http-parser, specifically commit nodejs/http-parser@1786fda. The changes are *completely* isolated, to enable it compile stud using "USE_HTTP_HEADERS=1 make". Original commit message follows: ---- This is an early implementation of adding XFF support. Notes: - You can't just append an XFF. You must detect any existing XFF headers and remove them. We can't allow people to spoof originating IPs to our backends. - The changes are *completely* isolated. - When you run `make` you get two git downloads and three binaries: - stud -- normal stud. no HTTP support is even compiled. - stud-http -- stud + http + x-forward-{for,proto} injection - stud-http-jem -- stud-http with statically linked jemalloc - There isn't a command line option to enable XFF (yet). You get XFF forced on you when you run stud-http or stud-http-jem. At this point, no performance tests have been done against stud vs. stud-http vs. stud-http-jem. This is an initial "get it out there" commit. The PROTO_HTTP changes introduce new malloc calls (hence adding jemalloc), but all allocations are free'd as verified by valgrind x86_64. No reported leaks exist. As an early implementation, things to be cleaned up include: - More stable error handling - Better code organization - More inline documentation - Performance tests and tradeoffs with prepending IP octets versus XFF - \n vs. \r vs. \r\n for re-building the HTTP headers For parsing the HTTP headers, I use ry's portable and performant http-parser. To (hopefully) improve memory usage, I pull in jemalloc. Refactoring and performance benchmarks are forthcoming. Enjoy. ----
1 parent 19a7f19 commit bfacb77

File tree

6 files changed

+425
-74
lines changed

6 files changed

+425
-74
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ core
44
core.*
55
*.o
66
stud
7+
http-parser

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,24 @@ ifneq ($(NO_CONFIG_FILE),)
3333
CFLAGS += -DNO_CONFIG_FILE
3434
endif
3535

36+
# X-Forwarded-{For,Proto} support?
37+
ifneq ($(USE_HTTP_HEADERS),)
38+
CFLAGS += -DPROTO_HTTP
39+
OBJS += proto_http.o http-parser/http_parser.o
40+
endif
41+
3642
ALL += stud
3743
realall: $(ALL)
3844

45+
$(OBJS): http-parser
46+
47+
http-parser:
48+
git clone https://github.com/joyent/http-parser.git
49+
cd http-parser && git checkout -q 1786fdae36d3d40d59463dacab1cfb4165cf9f1d
50+
51+
http-parser/http_parser.o: http-parser
52+
make -C http-parser http_parser.o
53+
3954
stud: $(OBJS)
4055
$(CC) -o $@ $^ $(LDFLAGS)
4156

proto_http.c

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#include "proto_http.h"
2+
#include "stud.h"
3+
4+
int on_body(http_parser *parser, const char *at, size_t len) {
5+
proxystate *ps = parser->data;
6+
ps->ph.body = realloc(ps->ph.body, ps->ph.body_sz + len);
7+
memcpy(ps->ph.body + ps->ph.body_sz, at, len);
8+
ps->ph.body_sz += len;
9+
return 0;
10+
}
11+
12+
int on_header_field(http_parser *parser, const char *at, size_t len)
13+
{
14+
proxystate *ps = parser->data;
15+
if (ps->ph.last_was_value) {
16+
ps->ph.nlines++;
17+
18+
if (ps->ph.nlines == MAX_HEADER_LINES) {
19+
return -1; /* max headers. error. handle better */
20+
}
21+
22+
/* strip off any inbound x-forwarded-{for,proto} headers */
23+
if (len >= 11 && strncasecmp(at, "x-forwarded", 11) == 0) {
24+
ps->ph.stripped_last_header = 1;
25+
ps->ph.nlines--; /* revert nlines increment so we can resuse the slot */
26+
return 0;
27+
}
28+
29+
CURRENT_LINE->field_len = len;
30+
CURRENT_LINE->field = malloc(len+1); /* LOSING HERE */
31+
strncpy(CURRENT_LINE->field, at, len);
32+
33+
CURRENT_LINE->value = NULL;
34+
CURRENT_LINE->value_len = 0;
35+
}
36+
else {
37+
assert(CURRENT_LINE->value == NULL);
38+
assert(CURRENT_LINE->value_len == 0);
39+
40+
CURRENT_LINE->field_len += len;
41+
CURRENT_LINE->field = realloc(CURRENT_LINE->field,
42+
CURRENT_LINE->field_len+1);
43+
strncat(CURRENT_LINE->field, at, len);
44+
45+
/* strip off any inbound x-forwarded-{for,proto} headers */
46+
if (CURRENT_LINE->field_len >= 11 &&
47+
strncasecmp(CURRENT_LINE->field, "x-forwarded", 11) == 0) {
48+
free(CURRENT_LINE->field);
49+
CURRENT_LINE->field = NULL;
50+
ps->ph.stripped_last_header = 1;
51+
ps->ph.nlines--; /* revert nlines increment so we can resuse the slot */
52+
return 0;
53+
}
54+
}
55+
56+
CURRENT_LINE->field[CURRENT_LINE->field_len] = '\0';
57+
ps->ph.last_was_value = 0;
58+
return 0;
59+
}
60+
61+
int on_message_complete(http_parser *parser) {
62+
proxystate *ps = parser->data;
63+
ps->ph.done_parsing_http = 1;
64+
return 0;
65+
}
66+
67+
int on_header_value(http_parser *parser, const char *at, size_t len) {
68+
proxystate *ps = parser->data;
69+
70+
/* The previous header was excluded, so let's exclude its value too. */
71+
if (ps->ph.stripped_last_header) {
72+
ps->ph.stripped_last_header = 0; /* reset so it continues for next field */
73+
return 0;
74+
}
75+
76+
if (!ps->ph.last_was_value) {
77+
CURRENT_LINE->value_len = len;
78+
CURRENT_LINE->value = malloc(len+1); /* LOSING HERE */
79+
strncpy(CURRENT_LINE->value, at, len);
80+
}
81+
else {
82+
CURRENT_LINE->value_len += len;
83+
CURRENT_LINE->value = realloc(CURRENT_LINE->value,
84+
CURRENT_LINE->value_len+1);
85+
strncat(CURRENT_LINE->value, at, len);
86+
}
87+
88+
CURRENT_LINE->value[CURRENT_LINE->value_len] = '\0';
89+
ps->ph.last_was_value = 1;
90+
return 0;
91+
}
92+
93+
int cb_url(http_parser *parser, const char *at, size_t length) {
94+
char *uri = malloc(length + 1);
95+
((proxystate*)parser->data)->ph.uri = uri;
96+
memcpy(uri, at, length);
97+
98+
uri[length] = '\0';
99+
100+
return 0;
101+
}
102+
103+
#define MAX_IPv6_LENGTH 40
104+
int cb_headers_complete(http_parser *parser) {
105+
proxystate *ps = parser->data;
106+
ps->ph.method = parser->method;
107+
struct sockaddr *sa = (struct sockaddr *)&ps->remote_ip;
108+
struct sockaddr *found_addr;
109+
if (sa->sa_family == AF_INET) {
110+
found_addr = (struct sockaddr *)&(((struct sockaddr_in*)sa)->sin_addr);
111+
}
112+
else {
113+
found_addr = (struct sockaddr *)&(((struct sockaddr_in6*)sa)->sin6_addr);
114+
}
115+
ps->ph.nlines++;
116+
CURRENT_LINE->field = strdup("X-Forwarded-For");
117+
CURRENT_LINE->field_len = 15; /* strlen("X-Forwarded-For"); */
118+
CURRENT_LINE->value = malloc(MAX_IPv6_LENGTH);
119+
inet_ntop(ps->remote_ip.ss_family, found_addr,
120+
CURRENT_LINE->value, MAX_IPv6_LENGTH);
121+
CURRENT_LINE->value_len = strlen(CURRENT_LINE->value);
122+
123+
ps->ph.nlines++;
124+
CURRENT_LINE->field = strdup("X-Forwarded-Proto");
125+
CURRENT_LINE->field_len = 17; /* strlen("X-Forwarded-Proto"); */
126+
CURRENT_LINE->value = strdup("https");
127+
CURRENT_LINE->value_len = 5; /* strlen("https"); */
128+
return 0;
129+
}
130+
131+
#define MAX_TOTAL_HEADER_LENGTH 1024 * 64
132+
char *assemble_headers(void *pre_ps) {
133+
proxystate *ps = pre_ps;
134+
char *final = malloc(MAX_TOTAL_HEADER_LENGTH);
135+
char *mover = final;
136+
int used_sz = 0;
137+
int i = 0;
138+
for (i = 0; i < ps->ph.nlines; i++) {
139+
/* Not sure why some fields < nlines are null.
140+
started after initing everything to NULL
141+
if (ps->ph.header[i].field == NULL) {
142+
printf("Found NULL at nlines: %d\n", i);
143+
continue;
144+
} */
145+
int remaining_sz = 1024 * 64 - used_sz;
146+
int sz = snprintf(mover, remaining_sz, "%.*s: %.*s\n",
147+
(int)ps->ph.header[i].field_len,
148+
ps->ph.header[i].field,
149+
(int)ps->ph.header[i].value_len,
150+
ps->ph.header[i].value);
151+
if (sz >= remaining_sz) {
152+
return NULL;
153+
}
154+
else {
155+
mover += sz;
156+
used_sz += sz;
157+
if (used_sz > MAX_TOTAL_HEADER_LENGTH - 3) {
158+
/* minus 3 to save room for the \r\n\0 at the end */
159+
return NULL; /* reached more header size than we can handle */
160+
}
161+
}
162+
/* int field_sz = ps->ph.header[i].field_len;
163+
int value_sz = ps->ph.header[i].value_len;
164+
used_sz += field_sz + 2 + value_sz + 1;
165+
if (used_sz > 1024 * 64) {
166+
return NULL;
167+
}
168+
memcpy(mover, ps->ph.header[i].field, field_sz);
169+
mover += field_sz;
170+
memcpy(mover + field_sz, ": ", 2);
171+
mover += 2;
172+
memcpy(mover + field_sz + 2, ps->ph.header[i].value, value_sz);
173+
mover += value_sz;
174+
memcpy(mover + field_sz + 2 + 1, "\n", 1);
175+
mover += 1; */
176+
}
177+
final[used_sz] = '\r';
178+
final[used_sz+1] = '\n';
179+
final[used_sz+2] = '\0';
180+
return final;
181+
}

proto_http.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#ifndef proto_http_h
2+
#define proto_http_h
3+
4+
#include "http-parser/http_parser.h"
5+
6+
#define CURRENT_LINE (&ps->ph.header[ps->ph.nlines-1])
7+
#define MAX_HEADER_LINES 2000
8+
9+
struct line {
10+
char *field;
11+
size_t field_len;
12+
char *value;
13+
size_t value_len;
14+
};
15+
16+
typedef struct proto_http {
17+
http_parser_settings settings;
18+
http_parser *parser;
19+
struct line header[MAX_HEADER_LINES];
20+
char last_was_value;
21+
int nlines;
22+
char stripped_last_header;
23+
enum http_method method;
24+
char *uri;
25+
char *body;
26+
int body_sz;
27+
char done_parsing_http;
28+
} proto_http;
29+
30+
31+
char *assemble_headers(void *ps);
32+
int on_body(http_parser *parser, const char *at, size_t len);
33+
int on_header_field(http_parser *parser, const char *at, size_t len);
34+
int on_header_value(http_parser *parser, const char *at, size_t len);
35+
int on_message_complete(http_parser *parser);
36+
int on_header_value(http_parser *parser, const char *at, size_t len);
37+
int cb_url(http_parser *parser, const char *at, size_t length);
38+
int cb_headers_complete(http_parser *parser);
39+
40+
#endif

0 commit comments

Comments
 (0)