Skip to content
Snippets Groups Projects

daemon/http: HTTP response codes

Merged Oto Šťáva requested to merge doh2-status-codes into master
All threads resolved!
Files
2
+ 80
27
@@ -8,6 +8,7 @@
*/
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -18,16 +19,26 @@
#include "daemon/session.h"
#include "lib/layer/iterate.h" /* kr_response_classify */
#include "lib/cache/util.h"
#include "lib/generic/array.h"
#include "contrib/cleanup.h"
#include "contrib/base64url.h"
/** Makes a `nghttp2_nv`. `K` is the key, `KS` is the key length,
* `V` is the value, `VS` is the value length. */
#define MAKE_NV(K, KS, V, VS) \
{ (uint8_t *)(K), (uint8_t *)(V), (KS), (VS), NGHTTP2_NV_FLAG_NONE }
(nghttp2_nv) { (uint8_t *)(K), (uint8_t *)(V), (KS), (VS), NGHTTP2_NV_FLAG_NONE }
/** Makes a `nghttp2_nv` with static data. `K` is the key,
* `V` is the value. Both `K` and `V` MUST be string literals. */
#define MAKE_STATIC_NV(K, V) \
MAKE_NV((K), sizeof(K) - 1, (V), sizeof(V) - 1)
/** Makes a `nghttp2_nv` with a static key. `K` is the key,
* `V` is the value, `VS` is the value length. `K` MUST be a string literal. */
#define MAKE_STATIC_KEY_NV(K, V, VS) \
MAKE_NV((K), sizeof(K) - 1, (V), (VS))
/* Use same maximum as for tcp_pipeline_max. */
#define HTTP_MAX_CONCURRENT_STREAMS UINT16_MAX
@@ -38,6 +49,17 @@
#define MAX_DECIMAL_LENGTH(VT) ((CHAR_BIT * sizeof(VT) / 3) + 3)
/** HTTP status codes returned by kresd.
* This is obviously non-exhaustive of all HTTP status codes, feel free to add
* more if needed. */
enum http_status {
HTTP_STATUS_OK = 200,
HTTP_STATUS_BAD_REQUEST = 400,
HTTP_STATUS_NOT_FOUND = 404,
HTTP_STATUS_PAYLOAD_TOO_LARGE = 413,
HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
};
struct http_data {
uint8_t *buf;
size_t len;
@@ -47,6 +69,11 @@ struct http_data {
uv_write_t *req;
};
typedef array_t(nghttp2_nv) nghttp2_array_t;
static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
nghttp2_data_provider *prov, enum http_status status);
/*
* Write HTTP/2 protocol data to underlying transport layer.
*/
@@ -340,7 +367,8 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
kr_log_debug(DOH,
"[%p] stream %d: header too large (%zu B), refused\n",
(void *)h2, stream_id, valuelen);
refuse_stream(h2, stream_id);
http_send_response(ctx, stream_id, NULL,
HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE);
return 0;
}
@@ -360,7 +388,7 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
if (!strcasecmp(":path", (const char *)name)) {
if (check_uri((const char *)value) < 0) {
refuse_stream(h2, stream_id);
http_send_response(ctx, stream_id, NULL, HTTP_STATUS_NOT_FOUND);
return 0;
}
@@ -442,7 +470,7 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t
return 0;
}
static int submit_to_wirebuffer(struct http_ctx *ctx)
static int submit_to_wirebuffer(struct http_ctx *ctx, int32_t stream_id)
{
int ret = -1;
ssize_t len;
@@ -463,6 +491,7 @@ static int submit_to_wirebuffer(struct http_ctx *ctx)
len = ctx->buf_pos - sizeof(uint16_t);
if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) {
kr_log_debug(DOH, "[%p] invalid dnsmsg size: %zd B\n", (void *)ctx->h2, len);
http_send_response(ctx, stream_id, NULL, HTTP_STATUS_PAYLOAD_TOO_LARGE);
ret = -1;
goto cleanup;
}
@@ -496,12 +525,12 @@ static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *fram
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) {
if (ctx->current_method == HTTP_METHOD_GET) {
if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) {
refuse_stream(h2, stream_id);
http_send_response(ctx, stream_id, NULL, HTTP_STATUS_BAD_REQUEST);
return 0; /* End processing - don't submit to wirebuffer. */
}
}
if (submit_to_wirebuffer(ctx) < 0)
if (submit_to_wirebuffer(ctx, stream_id) < 0)
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
@@ -670,36 +699,60 @@ static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *bu
return send;
}
/** Convenience function for pushing `nghttp2_nv` made with MAKE_*_NV into
* arrays. */
static inline void push_nv(nghttp2_array_t *arr, nghttp2_nv nv)
{
array_push(*arr, nv);
}
/*
* Send dns response provided by the HTTP/2 data provider.
*
* Data isn't guaranteed to be sent immediately due to underlying HTTP/2 flow control.
*/
static int http_send_response(struct http_ctx *ctx, int32_t stream_id,
nghttp2_data_provider *prov)
nghttp2_data_provider *prov, enum http_status status)
{
nghttp2_session *h2 = ctx->h2;
struct http_data *data = (struct http_data*)prov->source.ptr;
int ret;
const char *directive_max_age = "max-age=";
char size[MAX_DECIMAL_LENGTH(data->len)] = { 0 };
int max_age_len = MAX_DECIMAL_LENGTH(data->ttl) + strlen(directive_max_age);
char max_age[max_age_len];
int size_len;
memset(max_age, 0, max_age_len * sizeof(*max_age));
size_len = snprintf(size, MAX_DECIMAL_LENGTH(data->len), "%zu", data->len);
max_age_len = snprintf(max_age, max_age_len, "%s%u", directive_max_age, data->ttl);
nghttp2_nv hdrs[] = {
MAKE_STATIC_NV(":status", "200"),
MAKE_STATIC_NV("content-type", "application/dns-message"),
MAKE_STATIC_NV("access-control-allow-origin", "*"),
MAKE_NV("content-length", 14, size, size_len),
MAKE_NV("cache-control", 13, max_age, max_age_len),
};
ret = nghttp2_submit_response(h2, stream_id, hdrs, sizeof(hdrs)/sizeof(*hdrs), prov);
nghttp2_array_t hdrs;
array_init(hdrs);
array_reserve(hdrs, 5);
auto_free char *status_str = NULL;
if (likely(status == HTTP_STATUS_OK)) {
push_nv(&hdrs, MAKE_STATIC_NV(":status", "200"));
} else {
int status_len = asprintf(&status_str, "%" PRIu16, status);
kr_require(status_len >= 0);
push_nv(&hdrs, MAKE_STATIC_KEY_NV(":status", status_str, status_len));
}
push_nv(&hdrs, MAKE_STATIC_NV("access-control-allow-origin", "*"));
struct http_data *data = NULL;
auto_free char *size = NULL;
auto_free char *max_age = NULL;
if (prov) {
data = (struct http_data*)prov->source.ptr;
const char *directive_max_age = "max-age=";
int max_age_len;
int size_len;
size_len = asprintf(&size, "%zu", data->len);
kr_require(size_len >= 0);
max_age_len = asprintf(&max_age, "%s%" PRIu32, directive_max_age, data->ttl);
kr_require(max_age_len >= 0);
push_nv(&hdrs, MAKE_STATIC_NV("content-type", "application/dns-message"));
push_nv(&hdrs, MAKE_STATIC_KEY_NV("content-length", size, size_len));
push_nv(&hdrs, MAKE_STATIC_KEY_NV("cache-control", max_age, max_age_len));
}
ret = nghttp2_submit_response(h2, stream_id, hdrs.at, hdrs.len, prov);
array_clear(hdrs);
if (ret != 0) {
kr_log_debug(DOH, "[%p] nghttp2_submit_response failed: %s\n", (void *)h2, nghttp2_strerror(ret));
free(data);
@@ -761,7 +814,7 @@ static int http_write_pkt(struct http_ctx *ctx, knot_pkt_t *pkt, int32_t stream_
prov.source.ptr = data;
prov.read_callback = read_callback;
return http_send_response(ctx, stream_id, &prov);
return http_send_response(ctx, stream_id, &prov, HTTP_STATUS_OK);
}
/*
Loading