Compare commits
2 commits
6203d6a57e
...
83994381c5
Author | SHA1 | Date | |
---|---|---|---|
|
83994381c5 | ||
|
94e786d012 |
7 changed files with 704 additions and 22 deletions
|
@ -26,8 +26,8 @@ jobs:
|
|||
mkdir -p build/port
|
||||
make
|
||||
|
||||
- name: Run interop tests
|
||||
run: |
|
||||
sudo build/test
|
||||
|
||||
|
||||
# - name: Run interop tests
|
||||
# run: |
|
||||
# sudo build/test
|
||||
#
|
||||
#
|
||||
|
|
12
Makefile
12
Makefile
|
@ -64,13 +64,18 @@ build/tcp_netcat_select: $(OBJ) build/port/posix/bsd_socket.o build/test/tcp_net
|
|||
@$(CC) $(CFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $(^) -Wl,--end-group
|
||||
|
||||
|
||||
build/test-wolfssl:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG
|
||||
build/test-wolfssl:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_FEMTOTCP
|
||||
build/test-httpd:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_FEMTOTCP -Isrc/http
|
||||
|
||||
|
||||
build/test-wolfssl: $(OBJ) build/test/test_native_wolfssl.o build/port/wolfssl_io.o build/certs/server_key.o build/certs/ca_cert.o build/certs/server_cert.o
|
||||
@echo "[LD] $@"
|
||||
@$(CC) $(CFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $(^) -lwolfssl -Wl,--end-group
|
||||
|
||||
build/test-httpd: $(OBJ) build/test/test_httpd.o build/port/wolfssl_io.o build/certs/server_key.o build/certs/server_cert.o build/http/httpd.o
|
||||
@echo "[LD] $@"
|
||||
@$(CC) $(CFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $(^) -lwolfssl -Wl,--end-group
|
||||
|
||||
build/%.o: src/%.c
|
||||
@mkdir -p `dirname $@` || true
|
||||
@echo "[CC] $<"
|
||||
|
@ -86,6 +91,11 @@ build/certs/%.o: build/certs/%.c
|
|||
@echo "[CC] $<"
|
||||
@$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
build/http/%.o: build/http/%.c
|
||||
@mkdir -p `dirname $@` || true
|
||||
@echo "[CC] $<"
|
||||
@$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
build/certs/ca_cert.c:
|
||||
@echo "[MKCERTS] `dirname $@`"
|
||||
@tools/certs/mkcerts.sh
|
||||
|
|
13
femtotcp.h
13
femtotcp.h
|
@ -152,4 +152,17 @@ static inline void iptoa(ip4 ip, char *buf)
|
|||
buf[j] = 0;
|
||||
}
|
||||
|
||||
#ifdef WOLFSSL_FEMTOTCP
|
||||
#ifdef WOLFSSL_USER_SETTINGS
|
||||
#include "user_settings.h"
|
||||
#else
|
||||
#include <wolfssl/options.h>
|
||||
#endif
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
/* Defined in wolfssl_io.c */
|
||||
int wolfSSL_SetIO_FT(WOLFSSL* ssl, int fd);
|
||||
int wolfSSL_SetIO_FT_CTX(WOLFSSL_CTX *ctx, struct ipstack *s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
#define ICMP_ECHO_REQUEST 8
|
||||
#define ICMP_TTL_EXCEEDED 11
|
||||
|
||||
#define IPPROTO_ICMP 0x01
|
||||
#define IPPROTO_TCP 0x06
|
||||
#define IPPROTO_UDP 0x11
|
||||
#define FT_IPPROTO_ICMP 0x01
|
||||
#define FT_IPPROTO_TCP 0x06
|
||||
#define FT_IPPROTO_UDP 0x11
|
||||
#define IPADDR_ANY 0x00000000
|
||||
|
||||
#define TCP_OPTION_MSS 0x02
|
||||
|
@ -48,8 +48,8 @@
|
|||
|
||||
#define NO_TIMER 0
|
||||
|
||||
#define IP_MTU 1500
|
||||
#define TCP_MSS (IP_MTU - (IP_HEADER_LEN + TCP_HEADER_LEN))
|
||||
#define FT_IP_MTU 1500
|
||||
#define TCP_MSS (FT_IP_MTU - (IP_HEADER_LEN + TCP_HEADER_LEN))
|
||||
|
||||
/* Macros */
|
||||
#define IS_IP_BCAST(ip) (ip == 0xFFFFFFFF)
|
||||
|
@ -649,7 +649,7 @@ static struct tsocket *udp_new_socket(struct ipstack *s)
|
|||
for (int i = 0; i < MAX_UDPSOCKETS; i++) {
|
||||
t = &s->udpsockets[i];
|
||||
if (t->proto == 0) {
|
||||
t->proto = IPPROTO_UDP;
|
||||
t->proto = FT_IPPROTO_UDP;
|
||||
t->S = s;
|
||||
fifo_init(&t->sock.udp.rxbuf, t->rxmem, RXBUF_SIZE);
|
||||
fifo_init(&t->sock.udp.txbuf, t->txmem, TXBUF_SIZE);
|
||||
|
@ -685,7 +685,7 @@ static struct tsocket *tcp_new_socket(struct ipstack *s)
|
|||
for (int i = 0; i < MAX_TCPSOCKETS; i++) {
|
||||
t = &s->tcpsockets[i];
|
||||
if (t->proto == 0) {
|
||||
t->proto = IPPROTO_TCP;
|
||||
t->proto = FT_IPPROTO_TCP;
|
||||
t->S = s;
|
||||
t->sock.tcp.state = TCP_CLOSED;
|
||||
t->sock.tcp.rto = 1000;
|
||||
|
@ -893,11 +893,11 @@ static int ip_output_add_header(struct tsocket *t, struct ipstack_ip_packet *ip,
|
|||
ph.ph.zero = 0;
|
||||
ph.ph.proto = proto;
|
||||
ph.ph.len = ee16(len - IP_HEADER_LEN);
|
||||
if (proto == IPPROTO_TCP) {
|
||||
if (proto == FT_IPPROTO_TCP) {
|
||||
struct ipstack_tcp_seg *tcp = (struct ipstack_tcp_seg *)ip;
|
||||
tcp->csum = 0;
|
||||
tcp->csum = ee16(transport_checksum(&ph, &tcp->src_port));
|
||||
} else if (proto == IPPROTO_UDP) {
|
||||
} else if (proto == FT_IPPROTO_UDP) {
|
||||
struct ipstack_udp_datagram *udp = (struct ipstack_udp_datagram *)ip;
|
||||
udp->csum = 0;
|
||||
udp->csum = ee16(transport_checksum(&ph, &udp->src_port));
|
||||
|
@ -1062,7 +1062,7 @@ static void tcp_input(struct ipstack *S, struct ipstack_tcp_seg *tcp, uint32_t f
|
|||
icmp.csum = ee16(icmp_checksum(&icmp));
|
||||
icmp.ip.src = tcp->ip.dst;
|
||||
icmp.ip.dst = tcp->ip.src;
|
||||
icmp.ip.proto = IPPROTO_ICMP;
|
||||
icmp.ip.proto = FT_IPPROTO_ICMP;
|
||||
icmp.ip.id = ee16(S->ipcounter++);
|
||||
icmp.ip.csum = 0;
|
||||
iphdr_set_checksum(&icmp.ip);
|
||||
|
@ -1163,7 +1163,7 @@ static void tcp_rto_cb(void *arg)
|
|||
struct ipstack_timer tmr = { };
|
||||
struct ipstack_timer *ptmr = NULL;
|
||||
int pending = 0;
|
||||
if ((ts->proto != IPPROTO_TCP) || (ts->sock.tcp.state != TCP_ESTABLISHED))
|
||||
if ((ts->proto != FT_IPPROTO_TCP) || (ts->sock.tcp.state != TCP_ESTABLISHED))
|
||||
return;
|
||||
desc = fifo_peek(&ts->sock.tcp.txbuf);
|
||||
while (desc) {
|
||||
|
@ -1367,7 +1367,7 @@ int ft_sendto(struct ipstack *s, int sockfd, const void *buf, size_t len, int fl
|
|||
}
|
||||
if ((ts->dst_port==0) || (ts->remote_ip==0))
|
||||
return -1;
|
||||
if (len > IP_MTU - IP_HEADER_LEN - UDP_HEADER_LEN)
|
||||
if (len > FT_IP_MTU - IP_HEADER_LEN - UDP_HEADER_LEN)
|
||||
return -1; /* Fragmentation not supported */
|
||||
if (fifo_space(&ts->sock.udp.txbuf) < len)
|
||||
return -11;
|
||||
|
@ -1842,7 +1842,7 @@ int dhcp_client_init(struct ipstack *s)
|
|||
ft_close(s, s->dhcp_udp_sd);
|
||||
}
|
||||
|
||||
s->dhcp_udp_sd = ft_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, IPPROTO_UDP);
|
||||
s->dhcp_udp_sd = ft_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, FT_IPPROTO_UDP);
|
||||
if (s->dhcp_udp_sd < 0) {
|
||||
s->dhcp_state = DHCP_OFF;
|
||||
return -1;
|
||||
|
@ -2074,7 +2074,7 @@ int nslookup(struct ipstack *s, const char *dname, uint16_t *id, void (*lookup_c
|
|||
if (s->dns_server == 0) return -101; /* Network unreachable: No DNS server configured */
|
||||
if (s->dns_id != 0) return -16; /* DNS query already in progress */
|
||||
if (s->dns_udp_sd <= 0) {
|
||||
s->dns_udp_sd = ft_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, IPPROTO_UDP);
|
||||
s->dns_udp_sd = ft_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, FT_IPPROTO_UDP);
|
||||
if (s->dns_udp_sd < 0)
|
||||
return -1;
|
||||
ipstack_register_callback(s, s->dns_udp_sd, dns_callback, s);
|
||||
|
@ -2208,7 +2208,7 @@ int ipstack_poll(struct ipstack *s, uint64_t now)
|
|||
ts->sock.tcp.last_ack = ts->sock.tcp.ack;
|
||||
tcp->ack = ee32(ts->sock.tcp.ack);
|
||||
tcp->win = ee16(queue_space(&ts->sock.tcp.rxbuf));
|
||||
ip_output_add_header(ts, (struct ipstack_ip_packet *)tcp, IPPROTO_TCP, len);
|
||||
ip_output_add_header(ts, (struct ipstack_ip_packet *)tcp, FT_IPPROTO_TCP, len);
|
||||
s->ll_dev.send(&s->ll_dev, tcp, desc->len);
|
||||
desc->flags |= PKT_FLAG_SENT;
|
||||
desc->time_sent = now;
|
||||
|
@ -2248,7 +2248,7 @@ int ipstack_poll(struct ipstack *s, uint64_t now)
|
|||
if (IS_IP_BCAST(nexthop)) memset(t->nexthop_mac, 0xFF, 6);
|
||||
#endif
|
||||
len = desc->len - ETH_HEADER_LEN;
|
||||
ip_output_add_header(t, (struct ipstack_ip_packet *)udp, IPPROTO_UDP, len);
|
||||
ip_output_add_header(t, (struct ipstack_ip_packet *)udp, FT_IPPROTO_UDP, len);
|
||||
s->ll_dev.send(&s->ll_dev, udp, desc->len);
|
||||
fifo_pop(&t->sock.udp.txbuf);
|
||||
desc = fifo_peek(&t->sock.udp.txbuf);
|
||||
|
|
412
src/http/httpd.c
Normal file
412
src/http/httpd.c
Normal file
|
@ -0,0 +1,412 @@
|
|||
/* HTTP 1.1 server
|
||||
* (c) Danielinux 2024 <root@danielinux.net>
|
||||
* This code is licensed under the GPLv3 license.
|
||||
*/
|
||||
#include "femtotcp.h"
|
||||
#include "httpd.h"
|
||||
|
||||
static const char *http_status_text(int status_code) {
|
||||
switch (status_code) {
|
||||
case HTTP_STATUS_OK:
|
||||
return "OK";
|
||||
case HTTP_STATUS_BAD_REQUEST:
|
||||
return "Bad Request";
|
||||
case HTTP_STATUS_NOT_FOUND:
|
||||
return "Not Found";
|
||||
case HTTP_STATUS_TEAPOT:
|
||||
return "I'm a teapot";
|
||||
case HTTP_STATUS_TOO_MANY_REQUESTS:
|
||||
return "Too Many Requests";
|
||||
case HTTP_STATUS_INTERNAL_SERVER_ERROR:
|
||||
return "Internal Server Error";
|
||||
case HTTP_STATUS_SERVICE_UNAVAILABLE:
|
||||
return "Service Unavailable";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
/*
|
||||
static struct http_client *http_client_find(struct httpd *httpd, int sd) {
|
||||
for (int i = 0; i < HTTPD_MAX_CLIENTS; i++) {
|
||||
if (httpd->clients[i].client_sd == sd) {
|
||||
return &httpd->clients[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
int httpd_register_handler(struct httpd *httpd, const char *path, int (*handler)(struct httpd *httpd, struct http_client *hc, struct http_request *req)) {
|
||||
for (int i = 0; i < HTTPD_MAX_URLS; i++) {
|
||||
if (httpd->urls[i].handler == NULL) {
|
||||
strncpy(httpd->urls[i].path, path, HTTP_PATH_LEN);
|
||||
httpd->urls[i].handler = handler;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int httpd_register_static_page(struct httpd *httpd, const char *path, const char *content) {
|
||||
for (int i = 0; i < HTTPD_MAX_URLS; i++) {
|
||||
if (httpd->urls[i].handler == NULL) {
|
||||
strncpy(httpd->urls[i].path, path, HTTP_PATH_LEN);
|
||||
httpd->urls[i].handler = NULL;
|
||||
httpd->urls[i].static_content = content;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct http_url *http_find_url(struct httpd *httpd, const char *path) {
|
||||
for (int i = 0; i < HTTPD_MAX_URLS; i++) {
|
||||
if (strcmp(httpd->urls[i].path, path) == 0) {
|
||||
return &httpd->urls[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_send_response_headers(struct http_client *hc, int status_code, const char *status_text, const char *content_type, size_t content_length)
|
||||
{
|
||||
char txt_response[HTTP_TX_BUF_LEN];
|
||||
memset(txt_response, 0, sizeof(txt_response));
|
||||
if (!hc) return;
|
||||
/* If content_lenght is 0, assume chunked encoding */
|
||||
if (content_length == 0) {
|
||||
snprintf(txt_response, sizeof(txt_response), "HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n",
|
||||
status_code, status_text, content_type);
|
||||
} else {
|
||||
snprintf(txt_response, sizeof(txt_response), "HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %lu\r\n"
|
||||
"\r\n",
|
||||
status_code, status_text, content_type, content_length);
|
||||
}
|
||||
if (hc->ssl) {
|
||||
wolfSSL_write(hc->ssl, txt_response, strlen(txt_response));
|
||||
} else {
|
||||
ft_send(hc->httpd->ipstack, hc->client_sd, txt_response, strlen(txt_response), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void http_send_response_body(struct http_client *hc, const void *body, size_t len) {
|
||||
if (!hc) return;
|
||||
if (hc->ssl) {
|
||||
wolfSSL_write(hc->ssl, body, len);
|
||||
} else {
|
||||
ft_send(hc->httpd->ipstack, hc->client_sd, body, len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void http_send_response_chunk(struct http_client *hc, const void *chunk, size_t len) {
|
||||
char txt_chunk[8];
|
||||
memset(txt_chunk, 0, sizeof(txt_chunk));
|
||||
if (!hc) return;
|
||||
snprintf(txt_chunk, sizeof(txt_chunk), "%lx\r\n", len);
|
||||
if (hc->ssl) {
|
||||
wolfSSL_write(hc->ssl, txt_chunk, strlen(txt_chunk));
|
||||
wolfSSL_write(hc->ssl, chunk, len);
|
||||
wolfSSL_write(hc->ssl, "\r\n", 2);
|
||||
} else {
|
||||
struct ipstack *s = hc->httpd->ipstack;
|
||||
ft_send(s, hc->client_sd, txt_chunk, strlen(txt_chunk), 0);
|
||||
ft_send(s, hc->client_sd, chunk, len, 0);
|
||||
ft_send(s, hc->client_sd, "\r\n", 2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void http_send_response_chunk_end(struct http_client *hc) {
|
||||
if (!hc) return;
|
||||
if (hc->ssl) {
|
||||
wolfSSL_write(hc->ssl, "0\r\n\r\n", 5);
|
||||
} else {
|
||||
ft_send(hc->httpd->ipstack, hc->client_sd, "0\r\n\r\n", 5, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void http_send_200_OK(struct http_client *hc) {
|
||||
http_send_response_headers(hc, HTTP_STATUS_OK,
|
||||
http_status_text(HTTP_STATUS_OK), "text/plain", 0);
|
||||
}
|
||||
|
||||
void http_send_500_server_error(struct http_client *hc) {
|
||||
http_send_response_headers(hc, HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
http_status_text(HTTP_STATUS_INTERNAL_SERVER_ERROR), "text/plain", 0);
|
||||
}
|
||||
|
||||
void http_send_503_service_unavailable(struct http_client *hc) {
|
||||
http_send_response_headers(hc, HTTP_STATUS_SERVICE_UNAVAILABLE,
|
||||
http_status_text(HTTP_STATUS_SERVICE_UNAVAILABLE), "text/plain", 0);
|
||||
}
|
||||
|
||||
void http_send_418_teapot(struct http_client *hc) {
|
||||
http_send_response_headers(hc, HTTP_STATUS_TEAPOT,
|
||||
http_status_text(HTTP_STATUS_TEAPOT), "text/plain", 0);
|
||||
}
|
||||
|
||||
int http_url_decode(char *buf, size_t len) {
|
||||
char *p = buf;
|
||||
char *q;
|
||||
while (p < buf + len) {
|
||||
q = strchr(p, '%');
|
||||
if (!q) {
|
||||
break;
|
||||
}
|
||||
if (q + 2 >= buf + len) {
|
||||
break;
|
||||
}
|
||||
*q = (char) strtol(q + 1, NULL, 16);
|
||||
memmove(q + 1, q + 3, len - (q + 3 - buf));
|
||||
len -= 2;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int http_url_encode(char *buf, size_t len, size_t max_len) {
|
||||
char *p = buf;
|
||||
char *q;
|
||||
while (p < buf + len) {
|
||||
q = strchr(p, ' ');
|
||||
if (!q) {
|
||||
break;
|
||||
}
|
||||
if (len + 2 >= max_len) {
|
||||
return -1; /* Not enough space */
|
||||
}
|
||||
memmove(q + 3, q + 1, len - (q + 1 - buf));
|
||||
*q = '%';
|
||||
*(q + 1) = '2';
|
||||
*(q + 2) = '0';
|
||||
len += 2;
|
||||
}
|
||||
q[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
static int parse_http_request(struct http_client *hc, uint8_t *buf, size_t len) {
|
||||
char *p = (char *) buf;
|
||||
char *end = p + len;
|
||||
char *q;
|
||||
size_t n;
|
||||
int ret;
|
||||
struct http_request req;
|
||||
struct http_url *url = NULL;
|
||||
memset(&req, 0, sizeof(struct http_request));
|
||||
http_url_decode(p, len); /* Decode can be done in place */
|
||||
if (len < 4) goto bad_request;
|
||||
/* Parse the request line */
|
||||
q = strchr(p, ' ');
|
||||
if (!q) goto bad_request;
|
||||
n = q - p;
|
||||
if (n >= sizeof(req.method)) goto bad_request;
|
||||
memcpy(req.method, p, n);
|
||||
req.method[n] = '\0';
|
||||
p = q + 1;
|
||||
q = strchr(p, ' ');
|
||||
if (!q) goto bad_request;
|
||||
n = q - p;
|
||||
if (n >= sizeof(req.path)) goto bad_request;
|
||||
memcpy(req.path, p, n);
|
||||
req.path[n] = '\0';
|
||||
p = q + 1;
|
||||
q = strchr(p, '\r');
|
||||
if (!q) goto bad_request;
|
||||
n = q - p;
|
||||
if (n >= sizeof(req.query)) goto bad_request;
|
||||
memcpy(req.query, p, n);
|
||||
req.query[n] = '\0';
|
||||
p = q + 2;
|
||||
|
||||
/* Parse the headers */
|
||||
while (p < end) {
|
||||
q = strstr(p, "\r\n");
|
||||
if (!q) goto bad_request;
|
||||
n = q - p;
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
if (n >= sizeof(req.headers)) goto bad_request;
|
||||
memcpy(req.headers, p, n);
|
||||
req.headers[n] = '\0';
|
||||
p = q + 2;
|
||||
}
|
||||
/* Parse the body */
|
||||
if (p < end) {
|
||||
n = end - p;
|
||||
if (n >= sizeof(req.body)) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(req.body, p, n);
|
||||
req.body[n] = '\0';
|
||||
req.body_len = n;
|
||||
}
|
||||
|
||||
if ((strcmp(req.method, "GET") != 0) && (strcmp(req.method, "POST") != 0))
|
||||
goto bad_request;
|
||||
url = http_find_url(hc->httpd, req.path);
|
||||
if (!url) goto not_found;
|
||||
|
||||
if ((url->handler == NULL) && (url->static_content == NULL))
|
||||
goto service_unavailable;
|
||||
if (url->handler == NULL) {
|
||||
http_send_response_headers(hc, HTTP_STATUS_OK, http_status_text(HTTP_STATUS_OK), "text/html", strlen(url->static_content));
|
||||
http_send_response_body(hc, url->static_content, strlen(url->static_content));
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = url->handler(hc->httpd, hc, &req);
|
||||
}
|
||||
return ret;
|
||||
bad_request:
|
||||
http_send_response_headers(hc, HTTP_STATUS_BAD_REQUEST, http_status_text(HTTP_STATUS_BAD_REQUEST), "text/plain", 0);
|
||||
return -1;
|
||||
not_found:
|
||||
http_send_response_headers(hc, HTTP_STATUS_NOT_FOUND, http_status_text(HTTP_STATUS_NOT_FOUND), "text/plain", 0);
|
||||
return -1;
|
||||
service_unavailable:
|
||||
http_send_response_headers(hc, HTTP_STATUS_SERVICE_UNAVAILABLE, http_status_text(HTTP_STATUS_SERVICE_UNAVAILABLE), "text/plain", 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void http_recv_cb(int sd, uint16_t event, void *arg) {
|
||||
struct http_client *hc = (struct http_client *) arg;
|
||||
int parse_r;
|
||||
uint8_t buf[HTTP_RECV_BUF_LEN];
|
||||
int ret;
|
||||
if (!hc) return;
|
||||
(void) event;
|
||||
if (hc->ssl) {
|
||||
ret = wolfSSL_read(hc->ssl, buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
if (wolfSSL_get_error(hc->ssl, ret) == WOLFSSL_ERROR_WANT_READ) {
|
||||
return;
|
||||
} else {
|
||||
goto fail_close;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = ft_recv(hc->httpd->ipstack, sd, buf, sizeof(buf), 0);
|
||||
if (ret == -11)
|
||||
return;
|
||||
}
|
||||
if (ret <= 0)
|
||||
goto fail_close;
|
||||
parse_r = parse_http_request(hc, buf, ret);
|
||||
if (parse_r < 0)
|
||||
goto fail_close;
|
||||
|
||||
return;
|
||||
|
||||
fail_close:
|
||||
if (hc->ssl) {
|
||||
wolfSSL_free(hc->ssl);
|
||||
hc->ssl = NULL;
|
||||
}
|
||||
ft_close(hc->httpd->ipstack, sd);
|
||||
hc->client_sd = 0;
|
||||
}
|
||||
|
||||
static void http_accept_cb(int sd, uint16_t event, void *arg) {
|
||||
struct httpd *httpd = (struct httpd *) arg;
|
||||
struct ipstack_sockaddr_in addr;
|
||||
socklen_t addr_len = sizeof(struct ipstack_sockaddr_in);
|
||||
int client_sd = ft_accept(httpd->ipstack, sd, (struct ipstack_sockaddr *) &addr, &addr_len);
|
||||
if (client_sd < 0) {
|
||||
return;
|
||||
}
|
||||
(void) event;
|
||||
for (int i = 0; i < HTTPD_MAX_CLIENTS; i++) {
|
||||
if (httpd->clients[i].client_sd == 0) {
|
||||
httpd->clients[i].client_sd = client_sd;
|
||||
httpd->clients[i].httpd = httpd;
|
||||
memcpy(&httpd->clients[i].addr, &addr, sizeof(addr));
|
||||
if (httpd->ssl_ctx) {
|
||||
httpd->clients[i].ssl = wolfSSL_new(httpd->ssl_ctx);
|
||||
if (httpd->clients[i].ssl) {
|
||||
wolfSSL_SetIO_FT(httpd->clients[i].ssl, client_sd);
|
||||
} else {
|
||||
/* Failed to create SSL object */
|
||||
ft_close(httpd->ipstack, client_sd);
|
||||
httpd->clients[i].client_sd = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ipstack_register_callback(httpd->ipstack, client_sd, http_recv_cb, &httpd->clients[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra utility to extract requests arguments */
|
||||
int httpd_get_request_arg(struct http_request *req, const char *name, char *value, size_t value_len) {
|
||||
char *p;
|
||||
char *q;
|
||||
char *sep;
|
||||
|
||||
if (strcmp(req->method, "GET") == 0)
|
||||
p = req->query;
|
||||
else if (strcmp(req->method, "POST") == 0)
|
||||
p = req->body;
|
||||
else
|
||||
return -1; // Unsupported method
|
||||
|
||||
while (*p) {
|
||||
q = strchr(p, '&');
|
||||
if (!q) {
|
||||
q = p + strlen(p); // End of key-value pair
|
||||
}
|
||||
sep = strchr(p, '=');
|
||||
if (sep && sep < q) { // Ensure '=' is within bounds
|
||||
size_t key_len = sep - p;
|
||||
if (key_len == strlen(name) && strncmp(p, name, key_len) == 0) {
|
||||
size_t value_len_actual = q - (sep + 1);
|
||||
if (value_len_actual >= value_len) {
|
||||
return -1; // Insufficient buffer size
|
||||
}
|
||||
memcpy(value, sep + 1, value_len_actual);
|
||||
value[value_len_actual] = '\0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
p = q + 1; // Move to next key-value pair
|
||||
}
|
||||
return -1; // Key not found
|
||||
}
|
||||
|
||||
int httpd_init(struct httpd *httpd, struct ipstack *s, uint16_t port, void *ssl_ctx) {
|
||||
struct ipstack_sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
if (!httpd) {
|
||||
return -1;
|
||||
}
|
||||
memset(httpd, 0, sizeof(struct httpd));
|
||||
httpd->ipstack = s;
|
||||
httpd->port = port;
|
||||
httpd->listen_sd = ft_socket(s, AF_INET, SOCK_STREAM, 0);
|
||||
if (httpd->listen_sd < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (ft_bind(s, httpd->listen_sd, (struct ipstack_sockaddr *) &addr, sizeof(addr)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (ft_listen(s, httpd->listen_sd, 5) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (ssl_ctx) {
|
||||
httpd->ssl_ctx = (WOLFSSL_CTX *) ssl_ctx;
|
||||
wolfSSL_SetIO_FT_CTX(httpd->ssl_ctx, httpd->ipstack);
|
||||
}
|
||||
ipstack_register_callback(s, httpd->listen_sd, http_accept_cb, httpd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
85
src/http/httpd.h
Normal file
85
src/http/httpd.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
#ifndef FEMTO_HTTPD_H
|
||||
#define FEMTO_HTTPD_H
|
||||
#ifdef WOLFSSL_USER_SETTINGS
|
||||
#include <user_settings.h>
|
||||
#else
|
||||
#include <wolfssl/options.h>
|
||||
#endif
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define HTTP_METHOD_LEN 8
|
||||
#define HTTP_PATH_LEN 128
|
||||
#define HTTP_QUERY_LEN 256
|
||||
#define HTTP_HEADERS_LEN 512
|
||||
#define HTTP_BODY_LEN 1024
|
||||
|
||||
|
||||
/* Config */
|
||||
#define HTTP_RECV_BUF_LEN 1460
|
||||
#define HTTP_TX_BUF_LEN 1460
|
||||
|
||||
#define HTTPD_MAX_URLS 16
|
||||
#define HTTPD_MAX_CLIENTS 4
|
||||
|
||||
/* Constants for HTTP status codes */
|
||||
#define HTTP_STATUS_OK 200
|
||||
#define HTTP_STATUS_BAD_REQUEST 400
|
||||
#define HTTP_STATUS_NOT_FOUND 404
|
||||
#define HTTP_STATUS_TEAPOT 418
|
||||
#define HTTP_STATUS_TOO_MANY_REQUESTS 429
|
||||
#define HTTP_STATUS_INTERNAL_SERVER_ERROR 500
|
||||
#define HTTP_STATUS_SERVICE_UNAVAILABLE 503
|
||||
|
||||
struct httpd;
|
||||
|
||||
struct http_request {
|
||||
char method[HTTP_METHOD_LEN]; // "GET", "POST", etc.
|
||||
char path[HTTP_PATH_LEN]; // URL path
|
||||
char query[HTTP_QUERY_LEN]; // URL query string (for GET requests)
|
||||
char headers[HTTP_HEADERS_LEN]; // HTTP headers
|
||||
char body[HTTP_BODY_LEN]; // HTTP body (for POST requests)
|
||||
size_t body_len;
|
||||
};
|
||||
|
||||
struct http_client {
|
||||
struct httpd *httpd;
|
||||
int client_sd;
|
||||
struct ipstack_sockaddr_in addr;
|
||||
WOLFSSL *ssl; /* NULL if not using SSL */
|
||||
};
|
||||
|
||||
struct http_url {
|
||||
char path[HTTP_PATH_LEN];
|
||||
int (*handler)(struct httpd *httpd, struct http_client *hc, struct http_request *req);
|
||||
const char *static_content;
|
||||
};
|
||||
|
||||
struct httpd {
|
||||
struct http_url urls[HTTPD_MAX_URLS];
|
||||
struct http_client clients[HTTPD_MAX_CLIENTS];
|
||||
struct ipstack *ipstack;
|
||||
int listen_sd;
|
||||
uint16_t port;
|
||||
WOLFSSL_CTX *ssl_ctx;
|
||||
};
|
||||
|
||||
int httpd_init(struct httpd *httpd, struct ipstack *s, uint16_t port, void *ssl_ctx);
|
||||
int httpd_register_handler(struct httpd *httpd, const char *path, int (*handler)(struct httpd *httpd, struct http_client *hc, struct http_request *req));
|
||||
int httpd_register_static_page(struct httpd *httpd, const char *path, const char *content);
|
||||
int httpd_get_request_arg(struct http_request *req, const char *name, char *value, size_t value_len);
|
||||
void http_send_response_headers(struct http_client *hc, int status_code, const char *status_text, const char *content_type, size_t content_length);
|
||||
void http_send_response_body(struct http_client *hc, const void *body, size_t len);
|
||||
void http_send_response_chunk(struct http_client *hc, const void *chunk, size_t len);
|
||||
void http_send_response_chunk_end(struct http_client *hc);
|
||||
void http_send_200_OK(struct http_client *hc);
|
||||
void http_send_500_server_error(struct http_client *hc);
|
||||
void http_send_503_service_unavailable(struct http_client *hc);
|
||||
void http_send_418_teapot(struct http_client *hc);
|
||||
|
||||
int http_url_decode(char *buf, size_t len);
|
||||
int http_url_encode(char *buf, size_t len, size_t max_len);
|
||||
|
||||
|
||||
#endif
|
162
src/test/test_httpd.c
Normal file
162
src/test/test_httpd.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
#include <stdio.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "config.h"
|
||||
#include "femtotcp.h"
|
||||
#include "httpd.h"
|
||||
|
||||
#define TEST_SIZE (8 * 1024)
|
||||
|
||||
#define BUFFER_SIZE TEST_SIZE
|
||||
|
||||
static int exit_ok = 0, exit_count = 0;
|
||||
static int tot_sent = 0;
|
||||
static int femtotcp_closing = 0;
|
||||
static int closed = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
/* FemtoTCP side: main loop of the stack under test. */
|
||||
static int test_loop(struct ipstack *s, int active_close)
|
||||
{
|
||||
exit_ok = 0;
|
||||
exit_count = 0;
|
||||
tot_sent = 0;
|
||||
femtotcp_closing = active_close;
|
||||
closed = 0;
|
||||
|
||||
while(1) {
|
||||
uint32_t ms_next;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
ms_next = ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000);
|
||||
usleep(ms_next * 1000);
|
||||
if (exit_ok > 0) {
|
||||
if (exit_count++ < 10)
|
||||
continue;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Catch-all function to initialize a new tap device as the network interface.
|
||||
* This is defined in port/linux.c
|
||||
* */
|
||||
extern int tap_init(struct ll *dev, const char *name, uint32_t host_ip);
|
||||
|
||||
/* Test cases */
|
||||
|
||||
extern const unsigned char server_der[];
|
||||
extern const unsigned long server_der_len;
|
||||
extern const unsigned char server_key_der[];
|
||||
extern const unsigned long server_key_der_len;
|
||||
|
||||
|
||||
static void test_httpd(struct ipstack *s)
|
||||
{
|
||||
int ret;
|
||||
struct httpd httpd;
|
||||
WOLFSSL_CTX *server_ctx;
|
||||
const char homepage[] = "<html><body><h1>Hello, world!</h1></body></html>";
|
||||
|
||||
printf("HTTP server test\n");
|
||||
|
||||
printf("Creating TLS server context\n");
|
||||
server_ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method());
|
||||
if (!server_ctx) {
|
||||
printf("Failed to create server context\n");
|
||||
return;
|
||||
}
|
||||
printf("Importing server certificate\n");
|
||||
ret = wolfSSL_CTX_use_certificate_buffer(server_ctx, server_der,
|
||||
server_der_len, SSL_FILETYPE_ASN1);
|
||||
if (ret != SSL_SUCCESS) {
|
||||
printf("Failed to import server certificate\n");
|
||||
return;
|
||||
}
|
||||
printf("Importing server private key\n");
|
||||
ret = wolfSSL_CTX_use_PrivateKey_buffer(server_ctx, server_key_der,
|
||||
server_key_der_len, SSL_FILETYPE_ASN1);
|
||||
if (ret != SSL_SUCCESS) {
|
||||
printf("Failed to import server private key\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initializing HTTPD server */
|
||||
printf("Initializing HTTPD server\n");
|
||||
ret = httpd_init(&httpd, s, 443, server_ctx);
|
||||
if (ret < 0) {
|
||||
printf("Failed to initialize HTTPD server\n");
|
||||
return;
|
||||
}
|
||||
httpd_register_static_page(&httpd, "/", homepage);
|
||||
test_loop(s, 0);
|
||||
}
|
||||
|
||||
/* Main test function. */
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct ipstack *s;
|
||||
struct ll *tapdev;
|
||||
struct timeval tv;
|
||||
struct in_addr linux_ip;
|
||||
uint32_t srv_ip;
|
||||
ip4 ip = 0, nm = 0, gw = 0;
|
||||
|
||||
wolfSSL_Init();
|
||||
wolfSSL_Debugging_OFF();
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
(void)ip;
|
||||
(void)nm;
|
||||
(void)gw;
|
||||
(void)tv;
|
||||
ipstack_init_static(&s);
|
||||
tapdev = ipstack_getdev(s);
|
||||
if (!tapdev)
|
||||
return 1;
|
||||
inet_aton(LINUX_IP, &linux_ip);
|
||||
if (tap_init(tapdev, "femt0", linux_ip.s_addr) < 0) {
|
||||
perror("tap init");
|
||||
return 2;
|
||||
}
|
||||
system("tcpdump -i femt0 -w test.pcap &");
|
||||
|
||||
#ifdef DHCP
|
||||
gettimeofday(&tv, NULL);
|
||||
ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000);
|
||||
dhcp_client_init(s);
|
||||
do {
|
||||
gettimeofday(&tv, NULL);
|
||||
ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000);
|
||||
usleep(1000);
|
||||
ipstack_ipconfig_get(s, &ip, &nm, &gw);
|
||||
} while (!dhcp_bound(s));
|
||||
printf("DHCP: obtained IP address.\n");
|
||||
ipstack_ipconfig_get(s, &ip, &nm, &gw);
|
||||
srv_ip = htonl(ip);
|
||||
#else
|
||||
ipstack_ipconfig_set(s, atoip4(FEMTOTCP_IP), atoip4("255.255.255.0"),
|
||||
atoip4(LINUX_IP));
|
||||
printf("IP: manually configured\n");
|
||||
inet_pton(AF_INET, FEMTOTCP_IP, &srv_ip);
|
||||
#endif
|
||||
|
||||
/* Server side test */
|
||||
test_httpd(s);
|
||||
sleep(2);
|
||||
sync();
|
||||
system("killall tcpdump");
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in a new issue