[LEDE-DEV] [RFC] uhttpd: [PATCH 1/2] modules: Add proxy module
lede at daniel.thecshore.com
lede at daniel.thecshore.com
Wed May 18 03:50:00 PDT 2016
From: Daniel Dickinson <openwrt at 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 at 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
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 at subsignal.org>
+ * Copyright (C) 2013 Felix Fietkau <nbd at openwrt.org>
+ * Copyright (C) 2015 Daniel Dickinson <openwrt at 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
--
1.9.1
More information about the Lede-dev
mailing list