diff mbox

[LEDE-DEV,RFC] uhttpd: [PATCH 1/2] modules: Add proxy module

Message ID 1463568601-24541-1-git-send-email-lede@daniel.thecshore.com
State Changes Requested
Headers show

Commit Message

Daniel Dickinson May 18, 2016, 10:50 a.m. UTC
From: Daniel Dickinson <openwrt@daniel.thecshore.com>

A first attempt at simple HTTP proxy support (to allow
URL beginning with /prefix to really be access to some
other server; this is potenitally useful with some
web applications, or if you want to have a single
entry point to multiple servers (and aren't serving
huge traffic).

Signed-off-by: Daniel Dickinson <lede@daniel.thecshore.com>
---

 NOTE: This is basically untested and is provided to get comments
 before proceeding with something which may be of no interest.

 CMakeLists.txt |  11 +-
 client.c       |  90 +++++++++++--
 listen.c       |   6 +-
 main.c         |  41 +++++-
 proc.c         |  38 ++++++
 proxy.c        | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 uhttpd.h       |  53 ++++++++
 utils.c        |  52 ++++++--
 8 files changed, 667 insertions(+), 30 deletions(-)
 create mode 100644 proxy.c

Comments

Jo-Philipp Wich May 23, 2016, 3:12 p.m. UTC | #1
Hi Daniel,

from a cursory look the code looks okay to me so far however I'd really
love to see this getting added as loadable plugin.

Do you think it is feasible to do? We might need to extend "struct
uhttpd_ops" for that but that should be no problem as long as new
members are getting added at the end.

~ Jo
Felix Fietkau May 23, 2016, 5:01 p.m. UTC | #2
On 2016-05-23 17:12, Jo-Philipp Wich wrote:
> Hi Daniel,
> 
> from a cursory look the code looks okay to me so far however I'd really
> love to see this getting added as loadable plugin.
> 
> Do you think it is feasible to do? We might need to extend "struct
> uhttpd_ops" for that but that should be no problem as long as new
> members are getting added at the end.
I think the json_script support might be a suitable way to hook into url
handling and configure proxy support. I think that's way more flexible
than adding even more tricks to the already overused prefix matching code.

- Felix
diff mbox

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8514351..3bf39db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,6 +10,7 @@  ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations
 OPTION(TLS_SUPPORT "TLS support" ON)
 OPTION(LUA_SUPPORT "Lua support" ON)
 OPTION(UBUS_SUPPORT "ubus support" ON)
+OPTION(PROXY_SUPPORT "proxy support" ON)
 
 IF(APPLE)
   INCLUDE_DIRECTORIES(/opt/local/include)
@@ -32,9 +33,7 @@  IF(HAVE_SHADOW)
     ADD_DEFINITIONS(-DHAVE_SHADOW)
 ENDIF()
 
-ADD_EXECUTABLE(uhttpd ${SOURCES})
 FIND_LIBRARY(libjson NAMES json-c json)
-TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
 
 SET(PLUGINS "")
 IF(LUA_SUPPORT)
@@ -73,6 +72,14 @@  IF(UBUS_SUPPORT)
 	TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json ${libjson})
 ENDIF()
 
+IF(PROXY_SUPPORT)
+	SET(SOURCES ${SOURCES} proxy.c)
+	ADD_DEFINITIONS(-DHAVE_PROXY)
+ENDIF()
+
+ADD_EXECUTABLE(uhttpd ${SOURCES})
+TARGET_LINK_LIBRARIES(uhttpd ubox dl json_script blobmsg_json ${libjson} ${LIBS})
+
 IF(PLUGINS)
 	SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES
 		PREFIX ""
diff --git a/client.c b/client.c
index 73e0e49..4643a56 100644
--- a/client.c
+++ b/client.c
@@ -24,9 +24,11 @@ 
 #include "tls.h"
 
 static LIST_HEAD(clients);
-static bool client_done = false;
+bool client_done = false;
 
 int n_clients = 0;
+int n_connections = 0;
+
 struct config conf = {};
 
 const char * const http_versions[] = {
@@ -40,6 +42,9 @@  const char * const http_methods[] = {
 	[UH_HTTP_MSG_POST] = "POST",
 	[UH_HTTP_MSG_HEAD] = "HEAD",
 	[UH_HTTP_MSG_OPTIONS] = "OPTIONS",
+	[UH_HTTP_MSG_DELETE] = "DELETE",
+	[UH_HTTP_MSG_PUT] = "PUT",
+	[UH_HTTP_MSG_CONNECT] = "CONNECT",
 };
 
 void uh_http_header(struct client *cl, int code, const char *summary)
@@ -73,7 +78,7 @@  static void uh_connection_close(struct client *cl)
 	ustream_state_change(cl->us);
 }
 
-static void uh_dispatch_done(struct client *cl)
+void uh_dispatch_done(struct client *cl)
 {
 	if (cl->dispatch.free)
 		cl->dispatch.free(cl);
@@ -104,7 +109,7 @@  static void uh_keepalive_poll_cb(struct uloop_timeout *timeout)
 	cl->us->notify_read(cl->us, 0);
 }
 
-static void uh_poll_connection(struct client *cl)
+void uh_poll_connection(struct client *cl)
 {
 	cl->timeout.cb = uh_keepalive_poll_cb;
 	uloop_timeout_set(&cl->timeout, 1);
@@ -160,6 +165,49 @@  static int find_idx(const char * const *list, int max, const char *str)
 	return -1;
 }
 
+#ifdef HAVE_PROXY
+static int client_parse_response(struct client *cl, char *data)
+{
+	struct client *proxycl = cl->proxycl;
+
+	if (!proxycl)
+		return CLIENT_STATE_DONE;
+
+	struct http_request *req = &cl->request;
+	char *code, *msg, *version;
+	int h_version;
+
+	version = strtok(data, " ");
+	code = strtok(NULL, " ");
+	msg = strtok(NULL, " ");
+	if (!code || !msg || !version)
+		goto error;
+
+	memset(&cl->request, 0, sizeof(cl->request));
+	h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version);
+	if (h_version < 0) {
+		req->version = UH_HTTP_VER_1_0;
+		return CLIENT_STATE_DONE;
+	}
+
+	req->method = proxycl->request.method;
+	req->version = h_version;
+	if (req->version < UH_HTTP_VER_1_1 || req->method == UH_HTTP_MSG_POST ||
+	    !conf.http_keepalive)
+		req->connection_close = true;
+	req->code = atoi(code);
+	req->msg = strdup(msg);
+	if (req->code <= 0)
+		goto error;
+
+	return CLIENT_STATE_HEADER;
+
+	error:
+		uh_client_error(cl->proxycl, 502, "Bad Gateway", "Invalid response from target\n");
+		return CLIENT_STATE_DONE;
+}
+#endif
+
 static int client_parse_request(struct client *cl, char *data)
 {
 	struct http_request *req = &cl->request;
@@ -206,10 +254,30 @@  static bool client_init_cb(struct client *cl, char *buf, int len)
 
 	*newline = 0;
 	blob_buf_init(&cl->hdr, 0);
+#ifdef HAVE_PROXY
+	if (cl->response) {
+		cl->state = client_parse_response(cl, buf);
+	} else {
+#endif
 	cl->state = client_parse_request(cl, buf);
+#ifdef HAVE_PROXY
+	}
+#endif
 	ustream_consume(cl->us, newline + 2 - buf);
-	if (cl->state == CLIENT_STATE_DONE)
-		uh_header_error(cl, 400, "Bad Request");
+
+	if (cl->state == CLIENT_STATE_DONE) {
+#ifdef HAVE_PROXY
+		if (!cl->response) {
+#endif
+			uh_header_error(cl, 400, "Bad Request");
+#ifdef HAVE_PROXY
+		} else {
+			uh_client_error(cl->proxycl, 502, "Bad Gateway", "Invalid response from target\n");
+#endif
+#ifdef HAVE_PROXY
+		}
+#endif
+	}
 
 	return true;
 }
@@ -303,7 +371,7 @@  static void client_header_complete(struct client *cl)
 	uh_handle_request(cl);
 }
 
-static void client_parse_header(struct client *cl, char *data)
+void uh_client_parse_header(struct client *cl, char *data)
 {
 	struct http_request *r = &cl->request;
 	char *err;
@@ -473,7 +541,7 @@  static bool client_header_cb(struct client *cl, char *buf, int len)
 		return false;
 
 	*newline = 0;
-	client_parse_header(cl, buf);
+	uh_client_parse_header(cl, buf);
 	line_len = newline + 2 - buf;
 	ustream_consume(cl->us, line_len);
 	if (cl->state == CLIENT_STATE_DATA)
@@ -522,6 +590,7 @@  static void client_close(struct client *cl)
 
 	client_done = true;
 	n_clients--;
+	n_connections--;
 	uh_dispatch_done(cl);
 	uloop_timeout_cancel(&cl->timeout);
 	if (cl->tls)
@@ -572,7 +641,7 @@  static void client_notify_state(struct ustream *s)
 	uh_client_notify_state(cl);
 }
 
-static void set_addr(struct uh_addr *addr, void *src)
+void uh_set_addr(struct uh_addr *addr, void *src)
 {
 	struct sockaddr_in *sin = src;
 	struct sockaddr_in6 *sin6 = src;
@@ -606,10 +675,10 @@  bool uh_accept_client(int fd, bool tls)
 	if (sfd < 0)
 		return false;
 
-	set_addr(&cl->peer_addr, &addr);
+	uh_set_addr(&cl->peer_addr, &addr);
 	sl = sizeof(addr);
 	getsockname(sfd, (struct sockaddr *) &addr, &sl);
-	set_addr(&cl->srv_addr, &addr);
+	uh_set_addr(&cl->srv_addr, &addr);
 
 	cl->us = &cl->sfd.stream;
 	if (tls) {
@@ -628,6 +697,7 @@  bool uh_accept_client(int fd, bool tls)
 
 	next_client = NULL;
 	n_clients++;
+	n_connections++;
 	cl->id = client_id++;
 	cl->tls = tls;
 
diff --git a/listen.c b/listen.c
index 92ca680..63d6da5 100644
--- a/listen.c
+++ b/listen.c
@@ -57,7 +57,7 @@  static void uh_poll_listeners(struct uloop_timeout *timeout)
 	struct listener *l;
 
 	if ((!n_blocked && conf.max_connections) ||
-	    n_clients >= conf.max_connections)
+	    n_connections >= conf.max_connections)
 		return;
 
 	list_for_each_entry(l, &listeners, list) {
@@ -65,7 +65,7 @@  static void uh_poll_listeners(struct uloop_timeout *timeout)
 			continue;
 
 		l->fd.cb(&l->fd, ULOOP_READ);
-	    if (n_clients >= conf.max_connections)
+	    if (n_connections >= conf.max_connections)
 			break;
 
 		n_blocked--;
@@ -92,7 +92,7 @@  static void listener_cb(struct uloop_fd *fd, unsigned int events)
 			break;
 	}
 
-	if (conf.max_connections && n_clients >= conf.max_connections)
+	if (conf.max_connections && n_connections >= conf.max_connections)
 		uh_block_listener(l);
 }
 
diff --git a/main.c b/main.c
index fb27665..2c6eb17 100644
--- a/main.c
+++ b/main.c
@@ -154,6 +154,10 @@  static int usage(const char *name)
 		"	-a              Do not authenticate JSON-RPC requests against UBUS session api\n"
 		"	-X		Enable CORS HTTP headers on JSON-RPC api\n"
 #endif
+#ifdef HAVE_PROXY
+		"	-P prefix=uri   Redirect prefix to alternate URI (proxy)\n"
+		"       -M count        Maximum number of proxied requests\n"
+#endif
 		"	-x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
 		"	-y alias[=path]	URL alias handle\n"
 		"	-i .ext=path    Use interpreter at path for files with the given extension\n"
@@ -174,6 +178,10 @@  static void init_defaults_pre(void)
 	conf.network_timeout = 30;
 	conf.http_keepalive = 20;
 	conf.max_script_requests = 3;
+#ifdef HAVE_PROXY
+	conf.max_proxy_requests = 10;
+	INIT_LIST_HEAD(&conf.proxies);
+#endif
 	conf.max_connections = 100;
 	conf.realm = "Protected Area";
 	conf.cgi_prefix = "/cgi-bin";
@@ -229,10 +237,13 @@  int main(int argc, char **argv)
 	BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
 
 	uh_dispatch_add(&cgi_dispatch);
+#ifdef HAVE_PROXY
+	uh_dispatch_add(&proxy_dispatch);
+#endif
 	init_defaults_pre();
 	signal(SIGPIPE, SIG_IGN);
 
-	while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
+	while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:P:M:")) != -1) {
 		switch(ch) {
 #ifdef HAVE_TLS
 		case 'C':
@@ -447,6 +458,34 @@  int main(int argc, char **argv)
 			                "ignoring -%c\n", ch);
 			break;
 #endif
+#ifdef HAVE_PROXY
+		case 'P':
+			optarg = strdup(optarg);
+			port = strchr(optarg, '=');
+			if (!port) {
+				fprintf(stderr, "Error: Invalid proxy destination: %s\n",
+					optarg);
+			}
+			*port++ = 0;
+			if ((strcmp(&optarg[0], "http://") == 0) && port) {
+				uh_proxy_add(optarg, &port[7]);
+			} else {
+				fprintf(stderr, "Error: Invalid proxy destination or protocol: %s\n",
+					optarg);
+				exit(1);
+			}
+
+			break;
+
+		case 'M':
+			conf.max_proxy_requests = atoi(optarg);
+			break;
+#else
+		case 'P':
+		case 'M':
+			fprintf(stderr, "uhttpd: Proxy support not compiled, "
+			                "ignoring -%c\n", ch);
+#endif
 		default:
 			return usage(argv[0]);
 		}
diff --git a/proc.c b/proc.c
index 4819e08..66288fa 100644
--- a/proc.c
+++ b/proc.c
@@ -121,6 +121,44 @@  static struct env_var extra_vars[] = {
 	[VAR_REMOTE_PORT] = { "REMOTE_PORT", remote_port },
 };
 
+char *uh_get_local_addr(struct client *cl)
+{
+	static char local_addr[INET6_ADDRSTRLEN];
+	inet_ntop(cl->srv_addr.family, &cl->srv_addr.in, local_addr, sizeof(local_addr));
+	return local_addr;
+}
+
+char *uh_get_local_port(struct client *cl)
+{
+	static char local_port[6];
+	snprintf(local_port, sizeof(local_port), "%d", cl->srv_addr.port);
+	return local_port;
+}
+
+char *uh_get_remote_addr(struct client *cl)
+{
+	static char remote_addr[INET6_ADDRSTRLEN];
+	inet_ntop(cl->peer_addr.family, &cl->peer_addr.in, remote_addr, sizeof(remote_addr));
+	return remote_addr;
+}
+
+char *uh_get_remote_port(struct client *cl)
+{
+	static char remote_port[6];
+	snprintf(remote_port, sizeof(remote_port), "%d", cl->peer_addr.port);
+	return remote_port;
+}
+
+char *uh_get_redirect_status(struct client *cl)
+{
+	static char redirect_status[4];
+	struct http_request *req = &cl->request;
+	snprintf(redirect_status, sizeof(redirect_status),
+		 "%d", req->redirect_status);
+
+	return redirect_status;
+}
+
 struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi)
 {
 	struct http_request *req = &cl->request;
diff --git a/proxy.c b/proxy.c
new file mode 100644
index 0000000..27c856b
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,406 @@ 
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
+ *   Copyright (C) 2015 Daniel Dickinson <openwrt@daniel.thecshore.com>
+ *
+ * Permission to use, copy, modify, and/or 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 <ctype.h>
+#include <string.h>
+#include "uhttpd.h"
+#include "plugin.h"
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <limits.h>
+
+bool proxy_done = false;
+static int n_proxies = 0;
+
+void uh_proxy_add(const char *prefix, const char *uri)
+{
+	struct proxy_uri *pu;
+	char * new_prefix;
+	char * new_port;
+	char * new_hostname;
+	char * new_path;
+
+	char *path = strchr(uri, '/');
+	*path++ = 0;
+
+	char *port = strchr(uri, ':');
+	if (!port)
+		port = "80";
+
+	pu = calloc_a(sizeof(*pu),
+		&new_prefix, strlen(prefix) + 1,
+		&new_hostname, strlen(uri) + 1,
+		&new_port, strlen(port) + 1,
+		&new_path, strlen(path) + 1);
+
+	pu->prefix = strcpy(new_prefix, prefix);
+	pu->hostname = strcpy(new_hostname, uri);
+	pu->port = strcpy(new_port, port);
+	pu->path = strcpy(new_path, path);
+
+	list_add_tail(&pu->list, &conf.proxies);
+}
+
+static int proxy_sock_init(struct proxy_uri *target, struct sockaddr_in6 *addr, unsigned int *sl)
+{
+	int sock = -1;
+	int yes = 1;
+	int status;
+
+	struct addrinfo *addrs = NULL, *p = NULL;
+	static struct addrinfo hints = {
+		.ai_family = AF_UNSPEC,
+		.ai_socktype = SOCK_STREAM,
+		.ai_flags = AI_PASSIVE,
+	};
+
+	if ((status = getaddrinfo(target->hostname, target->port, &hints, &addrs)) != 0) {
+		fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
+		return 0;
+	}
+
+	for (p = addrs; p; p = p->ai_next) {
+		/* get the socket */
+		sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+		if (sock < 0) {
+			perror("socket()");
+			goto error;
+		}
+
+		/* required to get parallel v4 + v6 working */
+		if (p->ai_family == AF_INET6 &&
+		    setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
+			perror("setsockopt()");
+			goto error;
+		}
+
+		if (connect(sock, p->ai_addr, p->ai_addrlen) < 0) {
+			continue;
+		} else {
+			memcpy(addr, p->ai_addr, sizeof(*addr));
+			*sl = p->ai_addrlen;
+			fd_cloexec(sock);
+			break;
+		}
+	}
+	freeaddrinfo(addrs);
+
+	if (sock < 0) {
+		goto error;
+	}
+
+	return sock;
+
+error:
+	if (sock > -1)
+		close(sock);
+	return -1;
+}
+
+static void proxy_close(struct client *proxy)
+{
+	proxy_done = true;
+	uh_dispatch_done(proxy);
+	uloop_timeout_cancel(&proxy->timeout);
+	ustream_free(&proxy->sfd.stream);
+	close(proxy->sfd.fd.fd);
+	n_connections--;
+	n_proxies--;
+	blob_buf_free(&proxy->hdr);
+	if (proxy->request.msg)
+		free(proxy->request.msg);
+	if (proxy->request.url)
+		free(proxy->request.url);
+	free(proxy);
+}
+
+static void proxy_client_notify_state(struct client *proxy)
+{
+	struct ustream *s = proxy->us;
+
+	if (!s->write_error && proxy->state != CLIENT_STATE_CLEANUP) {
+		if (proxy->state == CLIENT_STATE_DATA)
+			return;
+
+		if (!s->eof || s->w.data_bytes)
+			return;
+	}
+
+	return proxy_close(proxy);
+}
+
+static void proxy_ustream_notify_state(struct ustream *s)
+{
+	struct client *cl = container_of(s, struct client, sfd.stream);
+
+	proxy_client_notify_state(cl);
+}
+
+static int proxy_send_data_cb(struct client *cl, const char *buf, int len)
+{
+	/* For proxy cl = proxy and cl->proxycl = originator
+         * For the originator the opposite is true
+         * We just shovel data from one side to the other.
+         */
+	if (!cl->proxycl)
+		return INT_MAX;
+
+	uh_ustream_chunk_write(cl, cl->proxycl->us, buf, len);
+
+	ustream_consume(cl->us, len);
+
+	return len;
+}
+
+static void proxy_ustream_read_response_cb(struct ustream *s, int bytes)
+{
+	struct client *proxy = container_of(s, struct client, sfd.stream);
+
+	proxy->response = true;
+	uh_client_read_cb(proxy);
+}
+
+static bool proxy_write_request_init_cb(struct client *cl, char *buf, int len) {
+	struct client *proxy = cl->proxycl;
+
+	if (!proxy)
+		return false;
+
+	switch (proxy->request.method) {
+	case UH_HTTP_MSG_GET:
+		uh_ustream_chunk_printf(cl, proxy->us, "GET ");
+		break;
+	case UH_HTTP_MSG_POST:
+		uh_ustream_chunk_printf(cl, proxy->us, "POST ");
+		break;
+	case UH_HTTP_MSG_HEAD:
+		uh_ustream_chunk_printf(cl, proxy->us, "HEAD ");
+		break;
+	case UH_HTTP_MSG_DELETE:
+		uh_ustream_chunk_printf(cl, proxy->us, "DELETE ");
+		break;
+	case UH_HTTP_MSG_PUT:
+		uh_ustream_chunk_printf(cl, proxy->us, "PUT ");
+		break;
+	case UH_HTTP_MSG_OPTIONS:
+		uh_ustream_chunk_printf(cl, proxy->us, "OPTIONS ");
+		break;
+	case UH_HTTP_MSG_CONNECT:
+		uh_ustream_chunk_printf(cl, proxy->us, "CONNECT ");
+		break;
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "%s", proxy->request.proxy->path);
+
+	char *match = strstr(proxy->request.url, proxy->request.proxy->path) + strlen(proxy->request.proxy->path);
+
+	if (strlen(match) > 0) {
+		uh_ustream_chunk_printf(cl, proxy->us, "/%s ", match);
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "HTTP/");
+
+	switch(cl->request.version) {
+	case UH_HTTP_VER_0_9:
+		uh_ustream_chunk_printf(cl, proxy->us, "0.9");
+		break;
+	case UH_HTTP_VER_1_0:
+		uh_ustream_chunk_printf(cl, proxy->us, "1.0");
+		break;
+	case UH_HTTP_VER_1_1:
+		uh_ustream_chunk_printf(cl, proxy->us, "1.1");
+		break;
+	}
+	uh_ustream_chunk_printf(cl, proxy->us, "\r\n");
+
+	proxy->wreqstate = CLIENT_STATE_HEADER;
+
+	return true;
+}
+
+static bool proxy_write_request_header_cb(struct client *cl, char *buf, int len) {
+	struct client *proxy = cl->proxycl;
+	int rem;
+	struct proxy_uri *pu = proxy->request.proxy;
+
+	struct blob_attr *cur;
+
+	if (!proxy)
+		return false;
+
+	blob_for_each_attr(cur, cl->hdr.head, rem) {
+		if (strcmp(blobmsg_name(cur), "Host") == 0) {
+			uh_ustream_chunk_printf(cl, proxy->us, "Host: %s:%s\r\n", pu->hostname, pu->port);
+			uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Host: %s\r\n'", blobmsg_data(cur));
+		} else {
+			uh_ustream_chunk_printf(cl, proxy->us, "%s: %s\r\n", blobmsg_name(cur), blobmsg_data(cur));
+		}
+	}
+
+	char *remote_addr = uh_get_remote_addr(cl);
+	char *remote_port = uh_get_remote_port(cl);
+
+        uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-For: %s:%s\r\n", remote_addr, remote_port);
+	if (cl->tls) {
+		uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Proto: https\r\n");
+	} else {
+		uh_ustream_chunk_printf(cl, proxy->us, "X-Forwarded-Proto: http\r\n");
+	}
+
+	uh_ustream_chunk_printf(cl, proxy->us, "\r\n");
+
+	proxy->wreqstate = CLIENT_STATE_DATA;
+
+	return true;
+}
+
+static bool proxy_write_request_data_cb(struct client *cl, char *buf, int len)
+{
+	client_poll_post_data(cl);
+	return false;
+}
+
+typedef bool (*proxy_write_request_cb_t)(struct client *cl, char *buf, int len);
+static proxy_write_request_cb_t proxy_write_request_cbs[] = {
+	[CLIENT_STATE_INIT] = proxy_write_request_init_cb,
+	[CLIENT_STATE_HEADER] = proxy_write_request_header_cb,
+	[CLIENT_STATE_DATA] = proxy_write_request_data_cb
+};
+
+static void proxy_write_request_cb(struct client *cl) {
+	struct client *proxy = cl->proxycl;
+
+	if (!proxy)
+		return;
+
+	struct ustream *us = cl->us;
+	char *str;
+	int len;
+
+	proxy_done = false;
+	while (!client_done && !proxy_done) {
+		str = ustream_get_read_buf(us, &len);
+		if (!str || !len)
+			break;
+
+		if (proxy->wreqstate >= array_size(proxy_write_request_cbs) || !proxy_write_request_cbs[proxy->wreqstate])
+			break;
+
+		if (!proxy_write_request_cbs[proxy->wreqstate](cl, str, len))
+			if (len == us->r.buffer_len &&
+			    cl->state != CLIENT_STATE_DATA) {
+				uh_client_error(cl, 502, "Bad Gateway",
+						  "Connection terminated prematurely.");
+			}
+	}
+}
+
+static void proxy_ustream_write_request_cb(struct ustream *s, int bytes)
+{
+	struct client *proxy = container_of(s, struct client, sfd.stream);
+
+	proxy_write_request_cb(proxy);
+}
+
+static struct client *proxy_init_client(struct client *cl, struct proxy_uri *target, char *url)
+{
+	static struct client *proxy;
+	unsigned int sl;
+	int sfd;
+	struct sockaddr_in6 addr;
+
+	proxy = calloc(1, sizeof(struct client));
+
+	sl = sizeof(addr);
+	sfd = proxy_sock_init(target, &addr, &sl);
+
+	if (sfd < 0)
+		return NULL;
+
+	uh_set_addr(&proxy->peer_addr, &addr);
+	sl = sizeof(addr);
+	getsockname(sfd, (struct sockaddr *) &addr, &sl);
+	uh_set_addr(&proxy->srv_addr, &addr);
+
+	proxy->sfd.fd.fd = sfd;
+	proxy->us = &proxy->sfd.stream;
+	proxy->us->notify_write = proxy_ustream_write_request_cb;
+	proxy->us->notify_read = proxy_ustream_read_response_cb;
+	proxy->us->notify_state = proxy_ustream_notify_state;
+	proxy->us->string_data = true;
+	proxy->dispatch.data_send = proxy_send_data_cb;
+	cl->dispatch.data_send = proxy_send_data_cb;
+
+	cl->proxycl = proxy;
+	proxy->proxycl = cl;
+	proxy->request.proxy = target;
+	proxy->request.url = strdup(url);
+
+	ustream_fd_init(&proxy->sfd, sfd);
+
+	uh_poll_connection(proxy);
+
+	return proxy;
+}
+
+static void proxy_init(struct client *cl, char *url, struct proxy_uri *pu) {
+	if ((conf.max_connections && (n_connections >= conf.max_connections)) || (conf.max_proxy_requests && (n_proxies >= conf.max_proxy_requests)))
+		return;
+
+	struct client *proxy = proxy_init_client(cl, pu, url);
+
+	if (!proxy) {
+		uh_client_error(cl, 502, "Bad Gateway",
+				  "Proxy %s failed to connect to any address\n", pu->prefix);
+		return;
+	}
+	n_connections++;
+	n_proxies++;
+
+        return;
+}
+
+static void proxy_handle_request(struct client *cl, char *url, struct path_info *pi)
+{
+	struct proxy_uri *p;
+
+        if (!list_empty(&conf.proxies)) list_for_each_entry(p, &conf.proxies, list)
+		if (uh_path_match(p->prefix, url))
+			return proxy_init(cl, url, p);
+
+}
+
+static bool check_proxy_url(const char *url)
+{
+        struct proxy_uri *p;
+
+        if (!list_empty(&conf.proxies)) list_for_each_entry(p, &conf.proxies, list)
+		if (uh_path_match(p->prefix, url))
+			return true;
+	return false;
+}
+
+struct dispatch_handler proxy_dispatch = {
+	.script = false,
+	.check_url = check_proxy_url,
+	.handle_request = proxy_handle_request,
+};
diff --git a/uhttpd.h b/uhttpd.h
index f9ea761..d47b7b4 100644
--- a/uhttpd.h
+++ b/uhttpd.h
@@ -71,6 +71,10 @@  struct config {
 	int tls_redirect;
 	int tcp_keepalive;
 	int max_script_requests;
+#ifdef HAVE_PROXY
+	struct list_head proxies;
+	int max_proxy_requests;
+#endif
 	int max_connections;
 	int http_keepalive;
 	int script_timeout;
@@ -92,6 +96,9 @@  enum http_method {
 	UH_HTTP_MSG_POST,
 	UH_HTTP_MSG_HEAD,
 	UH_HTTP_MSG_OPTIONS,
+	UH_HTTP_MSG_DELETE,
+	UH_HTTP_MSG_PUT,
+	UH_HTTP_MSG_CONNECT,
 };
 
 enum http_version {
@@ -123,6 +130,12 @@  struct http_request {
 	bool disable_chunked;
 	uint8_t transfer_chunked;
 	const struct auth_realm *realm;
+#ifdef HAVE_PROXY
+	int code;
+	char *msg;
+	struct proxy_uri *proxy;
+	char *url;
+#endif
 };
 
 enum client_state {
@@ -140,6 +153,16 @@  struct interpreter {
 	const char *ext;
 };
 
+#ifdef HAVE_PROXY
+struct proxy_uri {
+	struct list_head list;
+	const char *prefix;
+	const char *hostname;
+	const char *port;
+	const char *path;
+};
+#endif
+
 struct path_info {
 	const char *root;
 	const char *phys;
@@ -150,6 +173,9 @@  struct path_info {
 	bool redirected;
 	struct stat stat;
 	const struct interpreter *ip;
+#ifdef HAVE_PROXY
+	struct proxy_uri *proxy;
+#endif
 };
 
 struct env_var {
@@ -248,6 +274,13 @@  struct client {
 	struct ustream_ssl ssl;
 #endif
 	struct uloop_timeout timeout;
+
+#ifdef HAVE_PROXY
+	struct client *proxycl;
+	enum client_state wreqstate;
+	bool response;
+#endif
+
 	int requests;
 
 	enum client_state state;
@@ -263,14 +296,19 @@  struct client {
 
 extern char uh_buf[4096];
 extern int n_clients;
+extern int n_connections;
 extern struct config conf;
 extern const char * const http_versions[];
 extern const char * const http_methods[];
 extern struct dispatch_handler cgi_dispatch;
+extern struct dispatch_handler proxy_dispatch;
+extern bool client_done;
+extern bool proxy_done;
 
 void uh_index_add(const char *filename);
 
 bool uh_accept_client(int fd, bool tls);
+void uh_set_addr(struct uh_addr *addr, void *src);
 
 void uh_unblock_listeners(void);
 void uh_setup_listeners(void);
@@ -279,6 +317,9 @@  int uh_socket_bind(const char *host, const char *port, bool tls);
 int uh_first_tls_port(int family);
 
 bool uh_use_chunked(struct client *cl);
+void uh_ustream_chunk_write(struct client *cl, struct ustream *us, const void *data, int len);
+void uh_ustream_chunk_vprintf(struct client *cl, struct ustream *us, const char *format, va_list arg);
+void uh_ustream_chunk_printf(struct client *cl, struct ustream *us, const char *format, ...);
 void uh_chunk_write(struct client *cl, const void *data, int len);
 void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg);
 
@@ -296,6 +337,9 @@  void uh_handle_request(struct client *cl);
 void client_poll_post_data(struct client *cl);
 void uh_client_read_cb(struct client *cl);
 void uh_client_notify_state(struct client *cl);
+void uh_client_parse_header(struct client *cl, char *data);
+void uh_dispatch_done(struct client *cl);
+void uh_poll_connection(struct client *cl);
 
 void uh_auth_add(const char *path, const char *user, const char *pass);
 bool uh_auth_check(struct client *cl, struct path_info *pi);
@@ -304,6 +348,9 @@  void uh_close_listen_fds(void);
 void uh_close_fds(void);
 
 void uh_interpreter_add(const char *ext, const char *path);
+#ifdef HAVE_PROXY
+void uh_proxy_add(const char *prefix, const char *uri);
+#endif
 void uh_dispatch_add(struct dispatch_handler *d);
 
 void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid);
@@ -312,6 +359,12 @@  void uh_relay_free(struct relay *r);
 void uh_relay_kill(struct client *cl, struct relay *r);
 
 struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi);
+char *uh_get_local_addr(struct client *cl);
+char *uh_get_local_port(struct client *cl);
+char *uh_get_remote_addr(struct client *cl);
+char *uh_get_remote_port(struct client *cl);
+char *uh_get_redirect_status(struct client *cl);
+
 bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
 		       void (*cb)(struct client *cl, struct path_info *pi, char *url));
 
diff --git a/utils.c b/utils.c
index 29e03c0..bd4082f 100644
--- a/utils.c
+++ b/utils.c
@@ -35,7 +35,7 @@  bool uh_use_chunked(struct client *cl)
 	return !cl->request.disable_chunked;
 }
 
-void uh_chunk_write(struct client *cl, const void *data, int len)
+void uh_ustream_chunk_write(struct client *cl, struct ustream *us, const void *data, int len)
 {
 	bool chunked = uh_use_chunked(cl);
 
@@ -44,13 +44,13 @@  void uh_chunk_write(struct client *cl, const void *data, int len)
 
 	uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
 	if (chunked)
-		ustream_printf(cl->us, "%X\r\n", len);
-	ustream_write(cl->us, data, len, true);
+		ustream_printf(us, "%X\r\n", len);
+	ustream_write(us, data, len, true);
 	if (chunked)
-		ustream_printf(cl->us, "\r\n", len);
+		ustream_printf(us, "\r\n", len);
 }
 
-void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
+void uh_ustream_chunk_vprintf(struct client *cl, struct ustream *us, const char *format, va_list arg)
 {
 	char buf[256];
 	va_list arg2;
@@ -61,7 +61,7 @@  void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
 
 	uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
 	if (!uh_use_chunked(cl)) {
-		ustream_vprintf(cl->us, format, arg);
+		ustream_vprintf(us, format, arg);
 		return;
 	}
 
@@ -69,24 +69,24 @@  void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
 	len = vsnprintf(buf, sizeof(buf), format, arg2);
 	va_end(arg2);
 
-	ustream_printf(cl->us, "%X\r\n", len);
+	ustream_printf(us, "%X\r\n", len);
 	if (len < sizeof(buf))
-		ustream_write(cl->us, buf, len, true);
+		ustream_write(us, buf, len, true);
 	else
-		ustream_vprintf(cl->us, format, arg);
-	ustream_printf(cl->us, "\r\n", len);
+		ustream_vprintf(us, format, arg);
+	ustream_printf(us, "\r\n", len);
 }
 
-void uh_chunk_printf(struct client *cl, const char *format, ...)
+void uh_ustream_chunk_printf(struct client *cl, struct ustream *us, const char *format, ...)
 {
 	va_list arg;
 
 	va_start(arg, format);
-	uh_chunk_vprintf(cl, format, arg);
+	uh_ustream_chunk_vprintf(cl, us, format, arg);
 	va_end(arg);
 }
 
-void uh_chunk_eof(struct client *cl)
+static void ustream_chunk_eof(struct client *cl, struct ustream *us)
 {
 	if (!uh_use_chunked(cl))
 		return;
@@ -94,7 +94,31 @@  void uh_chunk_eof(struct client *cl)
 	if (cl->state == CLIENT_STATE_CLEANUP)
 		return;
 
-	ustream_printf(cl->us, "0\r\n\r\n");
+	ustream_printf(us, "0\r\n\r\n");
+}
+
+void uh_chunk_write(struct client *cl, const void *data, int len)
+{
+	uh_ustream_chunk_write(cl, cl->us, data, len);
+}
+
+void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg)
+{
+	uh_ustream_chunk_vprintf(cl, cl->us, format, arg);
+}
+
+void uh_chunk_printf(struct client *cl, const char *format, ...)
+{
+	va_list arg;
+
+	va_start(arg, format);
+	uh_ustream_chunk_printf(cl, cl->us, format, arg);
+	va_end(arg);
+}
+
+void uh_chunk_eof(struct client *cl)
+{
+	ustream_chunk_eof(cl, cl->us);
 }
 
 /* blen is the size of buf; slen is the length of src.  The input-string need