Compare commits

..

2 commits

Author SHA1 Message Date
Daniele Lacamera
83994381c5 Added httpd library + https test server
Some checks failed
/ unit_test (push) Has been cancelled
2024-12-22 15:38:54 +01:00
Daniele Lacamera
94e786d012 Revert linux automated test in CI 2024-12-20 23:35:16 +01:00
7 changed files with 704 additions and 22 deletions

View file

@ -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
#
#

View file

@ -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

View file

@ -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

View file

@ -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
View 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
View 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
View 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;
}