浏览代码

nghttp2: new module for supporting direct http2 connections

- initial import version
Daniel-Constantin Mierla 1 年之前
父节点
当前提交
5ba76f65f5

+ 44 - 0
src/modules/nghttp2/Makefile

@@ -0,0 +1,44 @@
+#
+# WARNING: do not run this directly, it should be run by the main Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=nghttp2.so
+
+ifeq ($(CROSS_COMPILE),)
+   LNGHTTP2_BUILDER=$(shell \
+       if pkg-config --exists libnghttp2; then \
+           echo 'pkg-config libnghttp2'; \
+       else \
+           which curl-config; \
+       fi)
+endif
+
+ifneq ($(LNGHTTP2_BUILDER),)
+   LNGHTTP2DEFS = $(shell $(LNGHTTP2_BUILDER) --cflags)
+   LNGHTTP2LIBS = $(shell $(LNGHTTP2_BUILDER) --libs)
+else
+   LNGHTTP2DEFS = -I$(LOCALBASE)/include -I$(SYSBASE)/include
+   LNGHTTP2LIBS = -L$(LOCALBASE)/lib -L$(SYSBASE)/lib -lnghttp2 -levent
+endif
+
+
+ifeq ($(CROSS_COMPILE),)
+LEVENT_BUILDER = $(shell \
+	if pkg-config --exists libevent; then \
+		echo 'pkg-config libevent'; \
+	fi)
+endif
+
+ifeq ($(LEVENT_BUILDER),)
+	LEVENTDEFS=-I$(LOCALBASE)/include -I$(SYSBASE)/include
+	LEVENTLIBS=-L$(LOCALBASE)/lib -levent
+else
+	LEVENTDEFS = $(shell $(LEVENT_BUILDER) --cflags)
+	LEVENTLIBS = $(shell $(LEVENT_BUILDER) --libs)
+endif
+
+DEFS+=$(LNGHTTP2DEFS) $(LEVENTDEFS)
+LIBS=$(LNGHTTP2LIBS) $(LEVENTLIBS)
+
+include ../../Makefile.modules

+ 180 - 0
src/modules/nghttp2/README

@@ -0,0 +1,180 @@
+NGHTTP2 Module
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2024 asipto.com
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. listen_addr (str)
+              3.2. listen_port (str)
+              3.3. event_callback (str)
+
+        4. Functions
+
+              4.1. nghttp2_reply(code, reason, ctype, body)
+
+        5. Event Routes
+
+              5.1. nghttp2:request
+
+   List of Examples
+
+   1.1. Set listen_addr parameter
+   1.2. Set listen_port parameter
+   1.3. Set event_callback parameter
+   1.4. nghttp2_reply usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. listen_addr (str)
+        3.2. listen_port (str)
+        3.3. event_callback (str)
+
+   4. Functions
+
+        4.1. nghttp2_reply(code, reason, ctype, body)
+
+   5. Event Routes
+
+        5.1. nghttp2:request
+
+1. Overview
+
+   This module implements an embedded HTTP/2 server using nghttpd2
+   library.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * none.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * libnghttp2 - libnghttpd library (v1.61.0+)
+
+3. Parameters
+
+   3.1. listen_addr (str)
+   3.2. listen_port (str)
+   3.3. event_callback (str)
+
+3.1. listen_addr (str)
+
+   IPv4 address to listen for HTTP2 connection. If not set, then it
+   listens on all local addresses (port has to be specified by listen_port
+   parameter).
+
+   Default value is "" (empty - not set).
+
+   Example 1.1. Set listen_addr parameter
+...
+modparam("nghttp2", "listen_addr", "127.0.0.1")
+...
+
+3.2. listen_port (str)
+
+   Port or service name to listen for HTTP2 connection.
+
+   Default value is "8282".
+
+   Example 1.2. Set listen_port parameter
+...
+modparam("nghttp2", "listen_port", "8284")
+...
+
+3.3. event_callback (str)
+
+   The name of the function in the kemi configuration file (embedded
+   scripting language such as Lua, Python, ...) to be executed instead of
+   event_route[nghttp2:request] block.
+
+   The function has one string parameter with the value "nghttp2:request".
+
+   Default value is 'empty' (no function is executed for events).
+
+   Example 1.3. Set event_callback parameter
+...
+modparam("nghttp2", "event_callback", "ksr_nghttp2_event")
+...
+-- event callback function implemented in Lua
+function ksr_nghttp2_event(evname)
+        KSR.info("===== nghttp2 module triggered event: " .. evname .. "\n");
+        return 1;
+end
+...
+
+4. Functions
+
+   4.1. nghttp2_reply(code, reason, ctype, body)
+
+4.1.  nghttp2_reply(code, reason, ctype, body)
+
+   Send back a reply with content-type and body.
+
+   Example 1.4. nghttp2_reply usage
+...
+event_route[nghttp2:request] {
+    nghttp2_reply("200", "OK", "text/html",
+        "<html><body>OK</body></html>");
+}
+...
+
+5. Event Routes
+
+   5.1. nghttp2:request
+
+5.1.  nghttp2:request
+
+   The event route is executed when a new HTTP request is received.
+
+   Inside it, the $nghttp2(...) group of variables is available, giving
+   access to several attributes of the HTTP request, such as method, URL,
+   data (body) or headers.
+...
+...
+loadmodule "nghttp2.so
+...
+event_route[nghttp2:request] {
+    xinfo("request: $nghttp2(method) - url: $nghttp2(url) - data: [$nghttp2(data
+)]\n");
+    nghttp2_reply("200", "OK", "text/html",
+        "<html><body>OK</body></html>");
+}
+...

+ 4 - 0
src/modules/nghttp2/doc/Makefile

@@ -0,0 +1,4 @@
+docs = nghttp2.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 36 - 0
src/modules/nghttp2/doc/nghttp2.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>NGHTTP2 Module</title>
+	<productname class="trade">kamailio.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2024</year>
+	    <holder>asipto.com</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+    <xi:include href="nghttp2_admin.xml"/>
+
+</book>

+ 180 - 0
src/modules/nghttp2/doc/nghttp2_admin.xml

@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		This module implements an embedded HTTP/2 server using nghttpd2 library.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>none</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>libnghttp2</emphasis> - libnghttpd library (v1.61.0+)
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+
+	<section>
+	<title>Parameters</title>
+	<section id="nghttp2.p.listen_addr">
+		<title><varname>listen_addr</varname> (str)</title>
+		<para>
+			IPv4 address to listen for HTTP2 connection. If not set, then it
+			listens on all local addresses (port has to be specified by
+			listen_port parameter).
+		</para>
+		<para>
+		<emphasis>
+			Default value is "" (empty - not set).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>listen_addr</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("nghttp2", "listen_addr", "127.0.0.1")
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="nghttp2.p.listen_port">
+		<title><varname>listen_port</varname> (str)</title>
+		<para>
+			Port or service name to listen for HTTP2 connection.
+		</para>
+		<para>
+		<emphasis>
+			Default value is "8282".
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>listen_port</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("nghttp2", "listen_port", "8284")
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="nghttp2.p.event_callback">
+		<title><varname>event_callback</varname> (str)</title>
+		<para>
+			The name of the function in the kemi configuration file (embedded
+			scripting language such as Lua, Python, ...) to be executed instead
+			of event_route[nghttp2:request] block.
+		</para>
+		<para>
+			The function has one string parameter with the value "nghttp2:request".
+		</para>
+		<para>
+		<emphasis>
+			Default value is 'empty' (no function is executed for events).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>event_callback</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("nghttp2", "event_callback", "ksr_nghttp2_event")
+...
+-- event callback function implemented in Lua
+function ksr_nghttp2_event(evname)
+	KSR.info("===== nghttp2 module triggered event: " .. evname .. "\n");
+	return 1;
+end
+...
+</programlisting>
+	    </example>
+	</section>
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section id="nghttp2.f.nghttp2_reply">
+	    <title>
+		<function moreinfo="none">nghttp2_reply(code, reason, ctype, body)</function>
+	    </title>
+	    <para>
+		Send back a reply with content-type and body.
+	    </para>
+		<example>
+		<title><function>nghttp2_reply</function> usage</title>
+		<programlisting format="linespecific">
+...
+event_route[nghttp2:request] {
+    nghttp2_reply("200", "OK", "text/html",
+        "&lt;html&gt;&lt;body&gt;OK&lt;/body&gt;&lt;/html&gt;");
+}
+...
+</programlisting>
+	    </example>
+	</section>
+	</section>
+
+   <section>
+    <title>Event Routes</title>
+    <section id="nghttp2.evr.request">
+        <title>
+        <function moreinfo="none">nghttp2:request</function>
+        </title>
+        <para>
+			The event route is executed when a new HTTP request is received.
+        </para>
+        <para>
+			Inside it, the $nghttp2(...) group of variables is available, giving
+			access to several attributes of the HTTP request, such as method,
+			URL, data (body) or headers.
+        </para>
+        <programlisting  format="linespecific">
+...
+...
+loadmodule "nghttp2.so
+...
+event_route[nghttp2:request] {
+    xinfo("request: $nghttp2(method) - url: $nghttp2(url) - data: [$nghttp2(data)]\n");
+    nghttp2_reply("200", "OK", "text/html",
+        "&lt;html&gt;&lt;body&gt;OK&lt;/body&gt;&lt;/html&gt;");
+}
+...
+        </programlisting>
+	</section>
+	</section>
+
+</chapter>

+ 563 - 0
src/modules/nghttp2/nghttp2_mod.c

@@ -0,0 +1,563 @@
+/**
+ * Copyright (C) 2024 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../../core/sr_module.h"
+#include "../../core/dprint.h"
+#include "../../core/ut.h"
+#include "../../core/mod_fix.h"
+#include "../../core/pvar.h"
+#include "../../core/kemi.h"
+#include "../../core/fmsg.h"
+#include "../../core/cfg/cfg_struct.h"
+
+#include "nghttp2_server.h"
+
+MODULE_VERSION
+
+str _nghttp2_listen_port = str_init("8282");
+str _nghttp2_listen_addr = str_init("");
+str _nghttp2_tls_public_key = str_init("");
+str _nghttp2_tls_private_key = str_init("");
+int _nghttp2_server_pid = -1;
+
+static int nghttp2_route_no = -1;
+static str nghttp2_event_callback = STR_NULL;
+
+static int w_nghttp2_send_reply(
+		sip_msg_t *msg, char *pcode, char *preason, char *pctype, char *pbody);
+
+static int fixup_nghttp2_send_reply(void **param, int param_no);
+
+
+static int mod_init(void);
+static int child_init(int);
+static void mod_destroy(void);
+
+int pv_get_nghttp2(sip_msg_t *msg, pv_param_t *param, pv_value_t *res);
+int pv_parse_nghttp2_name(pv_spec_p sp, str *in);
+
+/* clang-format off */
+static pv_export_t mod_pvs[] = {
+	{{"nghttp2", (sizeof("nghttp2") - 1)}, PVT_OTHER, pv_get_nghttp2, 0,
+			pv_parse_nghttp2_name, 0, 0, 0},
+
+	{{0, 0}, 0, 0, 0, 0, 0, 0, 0}
+};
+
+static cmd_export_t cmds[] = {
+	{"nghttp2_reply",    (cmd_function)w_nghttp2_send_reply,
+		4, fixup_nghttp2_send_reply,  0, REQUEST_ROUTE|EVENT_ROUTE},
+
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+	{"listen_port",     PARAM_STR,    &_nghttp2_listen_port},
+	{"listen_addr",     PARAM_STR,    &_nghttp2_listen_addr},
+	{"tls_public_key",	PARAM_STR,    &_nghttp2_tls_public_key},
+	{"tls_private_key",	PARAM_STR,    &_nghttp2_tls_private_key},
+	{"event_callback",  PARAM_STR,    &nghttp2_event_callback},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	"nghttp2",		 /* module name */
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,			 /* exported functions */
+	params,			 /* exported parameters */
+	0,				 /* exported rpc functions */
+	mod_pvs,		 /* exported pseudo-variables */
+	0,				 /* response handling function */
+	mod_init,		 /* module init function */
+	child_init,		 /* per child init function */
+	mod_destroy		 /* destroy function */
+};
+/* clang-format on */
+
+
+/**
+ * init module function
+ */
+static int mod_init(void)
+{
+	sr_kemi_eng_t *keng = NULL;
+	int route_no = -1;
+
+	if(nghttp2_event_callback.s != NULL && nghttp2_event_callback.len > 0) {
+		keng = sr_kemi_eng_get();
+		if(keng == NULL) {
+			LM_ERR("failed to find kemi engine\n");
+			return -1;
+		}
+		nghttp2_route_no = -1;
+	} else {
+		route_no = route_lookup(&event_rt, "nghttp2:request");
+		if(route_no == -1) {
+			LM_ERR("failed to find event_route[nghttp2:request]\n");
+			return -1;
+		}
+		if(event_rt.rlist[route_no] == 0) {
+			LM_WARN("event_route[nghttp2:request] is empty\n");
+		}
+		nghttp2_route_no = route_no;
+	}
+
+	/* add space for one extra process */
+	register_procs(1);
+
+	/* add child to update local config framework structures */
+	cfg_register_child(1);
+
+	return 0;
+}
+
+/**
+ * @brief Initialize async module children
+ */
+static int child_init(int rank)
+{
+	int pid;
+
+	if(rank != PROC_MAIN)
+		return 0;
+
+	pid = fork_process(PROC_NOCHLDINIT, "NGHTTP2 Server Process", 1);
+	if(pid < 0)
+		return -1; /* error */
+	if(pid == 0) {
+		/* child */
+		_nghttp2_server_pid = getpid();
+
+		/* do child init to allow execution of rpc like functions */
+		if(init_child(PROC_RPC) < 0) {
+			LM_DBG("failed to do RPC child init for dispatcher\n");
+			return -1;
+		}
+		/* initialize the config framework */
+		if(cfg_child_init())
+			return -1;
+		if(nghttp2_server_run() < 0) {
+			LM_ERR("failed to initialize nghttp2 server process\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * destroy module function
+ */
+static void mod_destroy(void)
+{
+}
+
+static ksr_nghttp2_ctx_t _ksr_nghttp2_ctx = {0};
+
+/**
+ * parse the name of the $nghttp2(name)
+ */
+int pv_parse_nghttp2_name(pv_spec_p sp, str *in)
+{
+	if(sp == NULL || in == NULL || in->len <= 0)
+		return -1;
+	switch(in->len) {
+		case 3:
+			if(strncasecmp(in->s, "url", 3) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 0;
+			} else {
+				goto error;
+			}
+			break;
+		case 4:
+			if(strncasecmp(in->s, "data", 4) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 1;
+			} else if(strncasecmp(in->s, "size", 4) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 2;
+			} else {
+				goto error;
+			}
+			break;
+		case 5:
+			if(strncasecmp(in->s, "srcip", 5) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 5;
+			} else {
+				goto error;
+			}
+			break;
+		case 6:
+			if(strncasecmp(in->s, "method", 6) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 3;
+			} else {
+				goto error;
+			}
+			break;
+		case 7:
+			if(strncasecmp(in->s, "version", 7) == 0) {
+				sp->pvp.pvn.u.isname.name.n = 4;
+			} else {
+				goto error;
+			}
+			break;
+		default:
+			if(in->len > 2 && in->s[1] == ':'
+					&& (in->s[0] == 'h' || in->s[0] == 'H')) {
+				sp->pvp.pvn.type = PV_NAME_INTSTR;
+				sp->pvp.pvn.u.isname.type = PVT_HDR;
+				sp->pvp.pvn.u.isname.name.s = *in;
+				return 0;
+			}
+			goto error;
+	}
+	sp->pvp.pvn.type = PV_NAME_INTSTR;
+	sp->pvp.pvn.u.isname.type = 0;
+
+	return 0;
+
+error:
+	LM_ERR("invalid variable name [%.*s]\n", in->len, in->s);
+	return -1;
+}
+
+/**
+ * return the value of $nghttp2(name)
+ */
+int pv_get_nghttp2(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
+{
+	struct sockaddr *srcaddr = NULL;
+	const char *hdrval = NULL;
+
+	if(param == NULL) {
+		return -1;
+	}
+	if(_ksr_nghttp2_ctx.connection == NULL) {
+		return pv_get_null(msg, param, res);
+	}
+	if(param->pvn.u.isname.type == PVT_HDR) {
+		//hdrval = MHD_lookup_connection_value(_ksr_mhttpd_ctx.connection,
+		//		MHD_HEADER_KIND, param->pvn.u.isname.name.s.s + 2);
+		if(hdrval == NULL) {
+			return pv_get_null(msg, param, res);
+		}
+		return pv_get_strzval(msg, param, res, (char *)hdrval);
+	}
+
+	switch(param->pvn.u.isname.name.n) {
+		case 0: /* url */
+			return pv_get_strval(msg, param, res, &_ksr_nghttp2_ctx.url);
+		case 1: /* data */
+			return pv_get_strval(msg, param, res, &_ksr_nghttp2_ctx.data);
+		case 2: /* size */
+			return pv_get_sintval(msg, param, res, _ksr_nghttp2_ctx.data.len);
+		case 3: /* method */
+			return pv_get_strval(msg, param, res, &_ksr_nghttp2_ctx.method);
+		case 4: /* version */
+			return pv_get_strval(
+					msg, param, res, &_ksr_nghttp2_ctx.httpversion);
+		case 5: /* srcip */
+			if(_ksr_nghttp2_ctx.srcip.len > 0) {
+				return pv_get_strval(msg, param, res, &_ksr_nghttp2_ctx.srcip);
+			}
+			srcaddr = NULL;
+			//		(_ksr_nghttp2_ctx.cinfo ? _ksr_nghttp2_ctx.cinfo->client_addr
+			//							   : NULL);
+			if(srcaddr == NULL) {
+				return pv_get_null(msg, param, res);
+			}
+			switch(srcaddr->sa_family) {
+				case AF_INET:
+					if(!inet_ntop(AF_INET,
+							   &(((struct sockaddr_in *)srcaddr)->sin_addr),
+							   _ksr_nghttp2_ctx.srcipbuf,
+							   IP_ADDR_MAX_STR_SIZE)) {
+						return pv_get_null(msg, param, res);
+					}
+					break;
+				case AF_INET6:
+					if(!inet_ntop(AF_INET6,
+							   &(((struct sockaddr_in6 *)srcaddr)->sin6_addr),
+							   _ksr_nghttp2_ctx.srcipbuf,
+							   IP_ADDR_MAX_STR_SIZE)) {
+						return pv_get_null(msg, param, res);
+					}
+					break;
+				default:
+					return pv_get_null(msg, param, res);
+			}
+			_ksr_nghttp2_ctx.srcip.s = _ksr_nghttp2_ctx.srcipbuf;
+			_ksr_nghttp2_ctx.srcip.len = strlen(_ksr_nghttp2_ctx.srcipbuf);
+			return pv_get_strval(msg, param, res, &_ksr_nghttp2_ctx.srcip);
+		default:
+			return pv_get_null(msg, param, res);
+	}
+}
+
+/**
+ *
+ */
+static int ksr_nghttp2_send_reply(
+		sip_msg_t *msg, int rcode, str *sreason, str *sctype, str *sbody)
+{
+	//struct MHD_Response *response;
+	// int ret;
+
+	if(_ksr_nghttp2_ctx.connection == NULL) {
+		LM_ERR("no connection available\n");
+		return -1;
+	}
+
+	if(rcode < 100 || rcode >= 700) {
+		LM_ERR("invalid code parameter\n");
+		return -1;
+	}
+	if(sreason->s == NULL || sreason->len == 0) {
+		LM_ERR("invalid reason parameter\n");
+		return -1;
+	}
+	if(sctype->s == NULL) {
+		LM_ERR("invalid content-type parameter\n");
+		return -1;
+	}
+	if(sbody->s == NULL) {
+		LM_ERR("invalid body parameter\n");
+		return -1;
+	}
+
+#if 0
+	response = MHD_create_response_from_buffer(
+			sbody->len, sbody->s, MHD_RESPMEM_PERSISTENT);
+	if(response == NULL) {
+		LM_ERR("failed to create the response\n");
+		return -1;
+	}
+	if(sctype->len > 0) {
+		MHD_add_response_header(response, "Content-Type", sctype->s);
+	}
+	ret = MHD_queue_response(
+			_ksr_mhttpd_ctx.connection, (unsigned int)rcode, response);
+	MHD_destroy_response(response);
+
+	return (ret == MHD_YES) ? 1 : -1;
+#endif
+	return -1;
+}
+
+/**
+ *
+ */
+static int w_nghttp2_send_reply(
+		sip_msg_t *msg, char *pcode, char *preason, char *pctype, char *pbody)
+{
+	str body = str_init("");
+	str reason = str_init("OK");
+	str ctype = str_init("text/plain");
+	int code = 200;
+
+	if(_ksr_nghttp2_ctx.connection == NULL) {
+		LM_ERR("no connection available\n");
+		return -1;
+	}
+
+	if(pcode == 0 || preason == 0 || pctype == 0 || pbody == 0) {
+		LM_ERR("invalid parameters\n");
+		return -1;
+	}
+
+	if(fixup_get_ivalue(msg, (gparam_p)pcode, &code) != 0) {
+		LM_ERR("no reply code value\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_p)preason, &reason) != 0) {
+		LM_ERR("unable to get reason\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_p)pctype, &ctype) != 0) {
+		LM_ERR("unable to get content type\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_p)pbody, &body) != 0) {
+		LM_ERR("unable to get body\n");
+		return -1;
+	}
+
+	return ksr_nghttp2_send_reply(msg, code, &reason, &ctype, &body);
+}
+
+static int fixup_nghttp2_send_reply(void **param, int param_no)
+{
+	if(param_no == 1) {
+		return fixup_igp_null(param, 1);
+	} else if(param_no == 2) {
+		return fixup_spve_null(param, 1);
+	} else if(param_no == 3) {
+		return fixup_spve_null(param, 1);
+	} else if(param_no == 4) {
+		return fixup_spve_null(param, 1);
+	}
+	return 0;
+}
+
+#if 0
+static enum MHD_Result ksr_microhttpd_request(void *cls,
+		struct MHD_Connection *connection, const char *url, const char *method,
+		const char *version, const char *upload_data, size_t *upload_data_size,
+		void **ptr)
+{
+	static int _first_callback;
+	sr_kemi_eng_t *keng = NULL;
+	str evname = str_init("microhttpd:request");
+	sip_msg_t *fmsg = NULL;
+	run_act_ctx_t ctx;
+	int rtb;
+
+	if(&_first_callback != *ptr) {
+		/* the first time only the headers are valid,
+		   do not respond in the first round... */
+		*ptr = &_first_callback;
+		return MHD_YES;
+	}
+	*ptr = NULL; /* clear context pointer */
+
+	_ksr_mhttpd_ctx.connection = connection;
+	_ksr_mhttpd_ctx.method.s = (char *)method;
+	_ksr_mhttpd_ctx.method.len = strlen(_ksr_mhttpd_ctx.method.s);
+	_ksr_mhttpd_ctx.url.s = (char *)url;
+	_ksr_mhttpd_ctx.url.len = strlen(_ksr_mhttpd_ctx.url.s);
+	_ksr_mhttpd_ctx.httpversion.s = (char *)version;
+	_ksr_mhttpd_ctx.httpversion.len = strlen(_ksr_mhttpd_ctx.httpversion.s);
+	if(*upload_data_size > 0) {
+		_ksr_mhttpd_ctx.data.s = (char *)upload_data;
+		_ksr_mhttpd_ctx.data.len = (int)(*upload_data_size);
+	} else {
+		_ksr_mhttpd_ctx.data.s = NULL;
+		_ksr_mhttpd_ctx.data.len = 0;
+	}
+	_ksr_mhttpd_ctx.cinfo = MHD_get_connection_info(
+			connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
+	_ksr_mhttpd_ctx.srcip.s = NULL;
+	_ksr_mhttpd_ctx.srcip.len = 0;
+
+	LM_DBG("executing event_route[%s] (%d)\n", evname.s, microhttpd_route_no);
+	if(faked_msg_init() < 0) {
+		return MHD_NO;
+	}
+	fmsg = faked_msg_next();
+	rtb = get_route_type();
+	set_route_type(REQUEST_ROUTE);
+	init_run_actions_ctx(&ctx);
+	if(microhttpd_route_no >= 0) {
+		run_top_route(event_rt.rlist[microhttpd_route_no], fmsg, &ctx);
+	} else {
+		keng = sr_kemi_eng_get();
+		if(keng != NULL) {
+			if(sr_kemi_ctx_route(keng, &ctx, fmsg, EVENT_ROUTE,
+					   &microhttpd_event_callback, &evname)
+					< 0) {
+				LM_ERR("error running event route kemi callback\n");
+				return MHD_NO;
+			}
+		}
+	}
+	set_route_type(rtb);
+	if(ctx.run_flags & DROP_R_F) {
+		LM_ERR("exit due to 'drop' in event route\n");
+		return MHD_NO;
+	}
+
+	return MHD_YES;
+}
+
+#define KSR_MICROHTTPD_PAGE               \
+	"<html><head><title>Kamailio</title>" \
+	"</head><body>Thanks for flying Kamailio!</body></html>"
+/**
+ *
+ */
+static int microhttpd_server_run(void)
+{
+
+	struct MHD_Daemon *d;
+	struct sockaddr_in address;
+
+	if(_microhttpd_listen_addr.len > 0) {
+		address.sin_family = AF_INET;
+		address.sin_port = htons(_microhttpd_listen_port);
+		if(inet_pton(AF_INET, _microhttpd_listen_addr.s, &address.sin_addr)
+				<= 0) {
+			LM_ERR("failed to convert listen address\n");
+			return -1;
+		}
+		LM_DBG("preparing to listen on %s :%d\n", _microhttpd_listen_addr.s,
+				_microhttpd_listen_port);
+		d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, _microhttpd_listen_port,
+				NULL, NULL, &ksr_microhttpd_request, KSR_MICROHTTPD_PAGE,
+				MHD_OPTION_SOCK_ADDR, &address, MHD_OPTION_END);
+	} else {
+		LM_DBG("preparing to listen on port: %d\n", _microhttpd_listen_port);
+		d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, _microhttpd_listen_port,
+				NULL, NULL, &ksr_microhttpd_request, KSR_MICROHTTPD_PAGE,
+				MHD_OPTION_END);
+	}
+
+	if(d == NULL) {
+		return -1;
+	}
+	while(1) {
+		sleep(10);
+	}
+	return 0;
+}
+#endif
+
+/**
+ *
+ */
+/* clang-format off */
+static sr_kemi_t sr_kemi_nghttp2_exports[] = {
+	{ str_init("nghttp2"), str_init("nghttp2_reply"),
+		SR_KEMIP_INT, ksr_nghttp2_send_reply,
+		{ SR_KEMIP_INT, SR_KEMIP_STR, SR_KEMIP_STR,
+			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+
+	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
+};
+/* clang-format on */
+
+
+/**
+ *
+ */
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
+{
+	sr_kemi_modules_add(sr_kemi_nghttp2_exports);
+	return 0;
+}

+ 760 - 0
src/modules/nghttp2/nghttp2_server.c

@@ -0,0 +1,760 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16)
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+#define MAKE_NV(NAME, VALUE)                                 \
+	{                                                        \
+		(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, \
+				sizeof(VALUE) - 1, NGHTTP2_NV_FLAG_NONE      \
+	}
+
+#include "nghttp2_server.h"
+
+struct app_context;
+typedef struct app_context app_context;
+
+typedef struct http2_stream_data
+{
+	struct http2_stream_data *prev, *next;
+	char *request_path;
+	int32_t stream_id;
+	int fd;
+} http2_stream_data;
+
+typedef struct http2_session_data
+{
+	struct http2_stream_data root;
+	struct bufferevent *bev;
+	app_context *app_ctx;
+	nghttp2_session *session;
+	char *client_addr;
+} http2_session_data;
+
+struct app_context
+{
+	SSL_CTX *ssl_ctx;
+	struct event_base *evbase;
+};
+
+static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+		unsigned char *outlen, const unsigned char *in, unsigned int inlen,
+		void *arg)
+{
+	int rv;
+	(void)ssl;
+	(void)arg;
+
+	rv = nghttp2_select_alpn(out, outlen, in, inlen);
+
+	if(rv != 1) {
+		return SSL_TLSEXT_ERR_NOACK;
+	}
+
+	return SSL_TLSEXT_ERR_OK;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file)
+{
+	SSL_CTX *ssl_ctx;
+
+	ssl_ctx = SSL_CTX_new(TLS_server_method());
+	if(!ssl_ctx) {
+		LM_ERR("Could not create SSL/TLS context: %s",
+				ERR_error_string(ERR_get_error(), NULL));
+	}
+	SSL_CTX_set_options(
+			ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3
+							 | SSL_OP_NO_COMPRESSION
+							 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	if(SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
+		LM_ERR("SSL_CTX_set1_curves_list failed: %s",
+				ERR_error_string(ERR_get_error(), NULL));
+	}
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+	{
+		EC_KEY *ecdh;
+		ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+		if(!ecdh) {
+			LM_ERR("EC_KEY_new_by_curv_name failed: %s",
+					ERR_error_string(ERR_get_error(), NULL));
+		}
+		SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+		EC_KEY_free(ecdh);
+	}
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+
+	if(SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) {
+		LM_ERR("Could not read private key file %s", key_file);
+	}
+	if(SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+		LM_ERR("Could not read certificate file %s", cert_file);
+	}
+
+	SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);
+
+	return ssl_ctx;
+}
+
+/* Create SSL object */
+static SSL *create_ssl(SSL_CTX *ssl_ctx)
+{
+	SSL *ssl;
+	ssl = SSL_new(ssl_ctx);
+	if(!ssl) {
+		LM_ERR("Could not create SSL/TLS session object: %s",
+				ERR_error_string(ERR_get_error(), NULL));
+	}
+	return ssl;
+}
+
+static void add_stream(
+		http2_session_data *session_data, http2_stream_data *stream_data)
+{
+	stream_data->next = session_data->root.next;
+	session_data->root.next = stream_data;
+	stream_data->prev = &session_data->root;
+	if(stream_data->next) {
+		stream_data->next->prev = stream_data;
+	}
+}
+
+static void remove_stream(
+		http2_session_data *session_data, http2_stream_data *stream_data)
+{
+	(void)session_data;
+
+	stream_data->prev->next = stream_data->next;
+	if(stream_data->next) {
+		stream_data->next->prev = stream_data->prev;
+	}
+}
+
+static http2_stream_data *create_http2_stream_data(
+		http2_session_data *session_data, int32_t stream_id)
+{
+	http2_stream_data *stream_data;
+	stream_data = malloc(sizeof(http2_stream_data));
+	memset(stream_data, 0, sizeof(http2_stream_data));
+	stream_data->stream_id = stream_id;
+	stream_data->fd = -1;
+
+	add_stream(session_data, stream_data);
+	return stream_data;
+}
+
+static void delete_http2_stream_data(http2_stream_data *stream_data)
+{
+	if(stream_data->fd != -1) {
+		close(stream_data->fd);
+	}
+	free(stream_data->request_path);
+	free(stream_data);
+}
+
+static http2_session_data *create_http2_session_data(
+		app_context *app_ctx, int fd, struct sockaddr *addr, int addrlen)
+{
+	int rv;
+	http2_session_data *session_data;
+	SSL *ssl;
+	char host[NI_MAXHOST];
+	int val = 1;
+
+	ssl = create_ssl(app_ctx->ssl_ctx);
+	session_data = malloc(sizeof(http2_session_data));
+	memset(session_data, 0, sizeof(http2_session_data));
+	session_data->app_ctx = app_ctx;
+	setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+	session_data->bev = bufferevent_openssl_socket_new(app_ctx->evbase, fd, ssl,
+			BUFFEREVENT_SSL_ACCEPTING,
+			BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
+	bufferevent_enable(session_data->bev, EV_READ | EV_WRITE);
+	rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0,
+			NI_NUMERICHOST);
+	if(rv != 0) {
+		session_data->client_addr = strdup("(unknown)");
+	} else {
+		session_data->client_addr = strdup(host);
+	}
+
+	return session_data;
+}
+
+static void delete_http2_session_data(http2_session_data *session_data)
+{
+	http2_stream_data *stream_data;
+	SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
+	fprintf(stderr, "%s disconnected\n", session_data->client_addr);
+	if(ssl) {
+		SSL_shutdown(ssl);
+	}
+	bufferevent_free(session_data->bev);
+	nghttp2_session_del(session_data->session);
+	for(stream_data = session_data->root.next; stream_data;) {
+		http2_stream_data *next = stream_data->next;
+		delete_http2_stream_data(stream_data);
+		stream_data = next;
+	}
+	free(session_data->client_addr);
+	free(session_data);
+}
+
+/* Serialize the frame and send (or buffer) the data to
+   bufferevent. */
+static int session_send(http2_session_data *session_data)
+{
+	int rv;
+	rv = nghttp2_session_send(session_data->session);
+	if(rv != 0) {
+		LM_ERR("Fatal error: %s", nghttp2_strerror(rv));
+		return -1;
+	}
+	return 0;
+}
+
+/* Read the data in the bufferevent and feed them into nghttp2 library
+   function. Invocation of nghttp2_session_mem_recv2() may make
+   additional pending frames, so call session_send() at the end of the
+   function. */
+static int session_recv(http2_session_data *session_data)
+{
+	nghttp2_ssize readlen;
+	struct evbuffer *input = bufferevent_get_input(session_data->bev);
+	size_t datalen = evbuffer_get_length(input);
+	unsigned char *data = evbuffer_pullup(input, -1);
+
+	readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen);
+	if(readlen < 0) {
+		LM_ERR("Fatal error: %s", nghttp2_strerror((int)readlen));
+		return -1;
+	}
+	if(evbuffer_drain(input, (size_t)readlen) != 0) {
+		LM_ERR("Fatal error: evbuffer_drain failed");
+		return -1;
+	}
+	if(session_send(session_data) != 0) {
+		return -1;
+	}
+	return 0;
+}
+
+static nghttp2_ssize send_callback(nghttp2_session *session,
+		const uint8_t *data, size_t length, int flags, void *user_data)
+{
+	http2_session_data *session_data = (http2_session_data *)user_data;
+	struct bufferevent *bev = session_data->bev;
+	(void)session;
+	(void)flags;
+
+	/* Avoid excessive buffering in server side. */
+	if(evbuffer_get_length(bufferevent_get_output(session_data->bev))
+			>= OUTPUT_WOULDBLOCK_THRESHOLD) {
+		return NGHTTP2_ERR_WOULDBLOCK;
+	}
+	bufferevent_write(bev, data, length);
+	return (nghttp2_ssize)length;
+}
+
+/* Returns nonzero if the string |s| ends with the substring |sub| */
+static int ends_with(const char *s, const char *sub)
+{
+	size_t slen = strlen(s);
+	size_t sublen = strlen(sub);
+	if(slen < sublen) {
+		return 0;
+	}
+	return memcmp(s + slen - sublen, sub, sublen) == 0;
+}
+
+/* Returns int value of hex string character |c| */
+static uint8_t hex_to_uint(uint8_t c)
+{
+	if('0' <= c && c <= '9') {
+		return (uint8_t)(c - '0');
+	}
+	if('A' <= c && c <= 'F') {
+		return (uint8_t)(c - 'A' + 10);
+	}
+	if('a' <= c && c <= 'f') {
+		return (uint8_t)(c - 'a' + 10);
+	}
+	return 0;
+}
+
+/* Decodes percent-encoded byte string |value| with length |valuelen|
+   and returns the decoded byte string in allocated buffer. The return
+   value is NULL terminated. The caller must free the returned
+   string. */
+static char *percent_decode(const uint8_t *value, size_t valuelen)
+{
+	char *res;
+
+	res = malloc(valuelen + 1);
+	if(valuelen > 3) {
+		size_t i, j;
+		for(i = 0, j = 0; i < valuelen - 2;) {
+			if(value[i] != '%' || !isxdigit(value[i + 1])
+					|| !isxdigit(value[i + 2])) {
+				res[j++] = (char)value[i++];
+				continue;
+			}
+			res[j++] = (char)((hex_to_uint(value[i + 1]) << 4)
+							  + hex_to_uint(value[i + 2]));
+			i += 3;
+		}
+		memcpy(&res[j], &value[i], 2);
+		res[j + 2] = '\0';
+	} else {
+		memcpy(res, value, valuelen);
+		res[valuelen] = '\0';
+	}
+	return res;
+}
+
+static nghttp2_ssize file_read_callback(nghttp2_session *session,
+		int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags,
+		nghttp2_data_source *source, void *user_data)
+{
+	int fd = source->fd;
+	ssize_t r;
+	(void)session;
+	(void)stream_id;
+	(void)user_data;
+
+	while((r = read(fd, buf, length)) == -1 && errno == EINTR)
+		;
+	if(r == -1) {
+		return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+	}
+	if(r == 0) {
+		*data_flags |= NGHTTP2_DATA_FLAG_EOF;
+	}
+	return (nghttp2_ssize)r;
+}
+
+static int send_response(nghttp2_session *session, int32_t stream_id,
+		nghttp2_nv *nva, size_t nvlen, int fd)
+{
+	int rv;
+	nghttp2_data_provider2 data_prd;
+	data_prd.source.fd = fd;
+	data_prd.read_callback = file_read_callback;
+
+	rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, &data_prd);
+	if(rv != 0) {
+		LM_ERR("Fatal error: %s", nghttp2_strerror(rv));
+		return -1;
+	}
+	return 0;
+}
+
+static const char ERROR_HTML[] = "<html><head><title>404</title></head>"
+								 "<body><h1>404 Not Found</h1></body></html>";
+
+static int error_reply(nghttp2_session *session, http2_stream_data *stream_data)
+{
+	int rv;
+	ssize_t writelen;
+	int pipefd[2];
+	nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")};
+
+	rv = pipe(pipefd);
+	if(rv != 0) {
+		LM_ERR("Could not create pipe");
+		rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+				stream_data->stream_id, NGHTTP2_INTERNAL_ERROR);
+		if(rv != 0) {
+			LM_ERR("Fatal error: %s", nghttp2_strerror(rv));
+			return -1;
+		}
+		return 0;
+	}
+
+	writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1);
+	close(pipefd[1]);
+
+	if(writelen != sizeof(ERROR_HTML) - 1) {
+		close(pipefd[0]);
+		return -1;
+	}
+
+	stream_data->fd = pipefd[0];
+
+	if(send_response(
+			   session, stream_data->stream_id, hdrs, ARRLEN(hdrs), pipefd[0])
+			!= 0) {
+		close(pipefd[0]);
+		return -1;
+	}
+	return 0;
+}
+
+/* nghttp2_on_header_callback: Called when nghttp2 library emits
+   single header name/value pair. */
+static int on_header_callback(nghttp2_session *session,
+		const nghttp2_frame *frame, const uint8_t *name, size_t namelen,
+		const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data)
+{
+	http2_stream_data *stream_data;
+	const char PATH[] = ":path";
+	(void)flags;
+	(void)user_data;
+
+	switch(frame->hd.type) {
+		case NGHTTP2_HEADERS:
+			if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+				break;
+			}
+			stream_data = nghttp2_session_get_stream_user_data(
+					session, frame->hd.stream_id);
+			if(!stream_data || stream_data->request_path) {
+				break;
+			}
+			if(namelen == sizeof(PATH) - 1
+					&& memcmp(PATH, name, namelen) == 0) {
+				size_t j;
+				for(j = 0; j < valuelen && value[j] != '?'; ++j)
+					;
+				stream_data->request_path = percent_decode(value, j);
+			}
+			break;
+	}
+	return 0;
+}
+
+static int on_begin_headers_callback(
+		nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+{
+	http2_session_data *session_data = (http2_session_data *)user_data;
+	http2_stream_data *stream_data;
+
+	if(frame->hd.type != NGHTTP2_HEADERS
+			|| frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+		return 0;
+	}
+	stream_data = create_http2_stream_data(session_data, frame->hd.stream_id);
+	nghttp2_session_set_stream_user_data(
+			session, frame->hd.stream_id, stream_data);
+	return 0;
+}
+
+/* Minimum check for directory traversal. Returns nonzero if it is
+   safe. */
+static int check_path(const char *path)
+{
+	/* We don't like '\' in url. */
+	return path[0] && path[0] == '/' && strchr(path, '\\') == NULL
+		   && strstr(path, "/../") == NULL && strstr(path, "/./") == NULL
+		   && !ends_with(path, "/..") && !ends_with(path, "/.");
+}
+
+static int on_request_recv(nghttp2_session *session,
+		http2_session_data *session_data, http2_stream_data *stream_data)
+{
+	int fd;
+	nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")};
+	char *rel_path;
+
+	if(!stream_data->request_path) {
+		if(error_reply(session, stream_data) != 0) {
+			return NGHTTP2_ERR_CALLBACK_FAILURE;
+		}
+		return 0;
+	}
+	fprintf(stderr, "%s GET %s\n", session_data->client_addr,
+			stream_data->request_path);
+	if(!check_path(stream_data->request_path)) {
+		if(error_reply(session, stream_data) != 0) {
+			return NGHTTP2_ERR_CALLBACK_FAILURE;
+		}
+		return 0;
+	}
+	for(rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path)
+		;
+	fd = open(rel_path, O_RDONLY);
+	if(fd == -1) {
+		if(error_reply(session, stream_data) != 0) {
+			return NGHTTP2_ERR_CALLBACK_FAILURE;
+		}
+		return 0;
+	}
+	stream_data->fd = fd;
+
+	if(send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd)
+			!= 0) {
+		close(fd);
+		return NGHTTP2_ERR_CALLBACK_FAILURE;
+	}
+	return 0;
+}
+
+static int on_frame_recv_callback(
+		nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+{
+	http2_session_data *session_data = (http2_session_data *)user_data;
+	http2_stream_data *stream_data;
+	switch(frame->hd.type) {
+		case NGHTTP2_DATA:
+		case NGHTTP2_HEADERS:
+			/* Check that the client request has finished */
+			if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+				stream_data = nghttp2_session_get_stream_user_data(
+						session, frame->hd.stream_id);
+				/* For DATA and HEADERS frame, this callback may be called after
+				   on_stream_close_callback. Check that stream still alive. */
+				if(!stream_data) {
+					return 0;
+				}
+				return on_request_recv(session, session_data, stream_data);
+			}
+			break;
+		default:
+			break;
+	}
+	return 0;
+}
+
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+		uint32_t error_code, void *user_data)
+{
+	http2_session_data *session_data = (http2_session_data *)user_data;
+	http2_stream_data *stream_data;
+	(void)error_code;
+
+	stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
+	if(!stream_data) {
+		return 0;
+	}
+	remove_stream(session_data, stream_data);
+	delete_http2_stream_data(stream_data);
+	return 0;
+}
+
+static void initialize_nghttp2_session(http2_session_data *session_data)
+{
+	nghttp2_session_callbacks *callbacks;
+
+	nghttp2_session_callbacks_new(&callbacks);
+
+	nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback);
+
+	nghttp2_session_callbacks_set_on_frame_recv_callback(
+			callbacks, on_frame_recv_callback);
+
+	nghttp2_session_callbacks_set_on_stream_close_callback(
+			callbacks, on_stream_close_callback);
+
+	nghttp2_session_callbacks_set_on_header_callback(
+			callbacks, on_header_callback);
+
+	nghttp2_session_callbacks_set_on_begin_headers_callback(
+			callbacks, on_begin_headers_callback);
+
+	nghttp2_session_server_new(&session_data->session, callbacks, session_data);
+
+	nghttp2_session_callbacks_del(callbacks);
+}
+
+/* Send HTTP/2 client connection header, which includes 24 bytes
+   magic octets and SETTINGS frame */
+static int send_server_connection_header(http2_session_data *session_data)
+{
+	nghttp2_settings_entry iv[1] = {
+			{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+	int rv;
+
+	rv = nghttp2_submit_settings(
+			session_data->session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv));
+	if(rv != 0) {
+		LM_ERR("Fatal error: %s", nghttp2_strerror(rv));
+		return -1;
+	}
+	return 0;
+}
+
+/* readcb for bufferevent after client connection header was
+   checked. */
+static void readcb(struct bufferevent *bev, void *ptr)
+{
+	http2_session_data *session_data = (http2_session_data *)ptr;
+	(void)bev;
+
+	if(session_recv(session_data) != 0) {
+		delete_http2_session_data(session_data);
+		return;
+	}
+}
+
+/* writecb for bufferevent. To greaceful shutdown after sending or
+   receiving GOAWAY, we check the some conditions on the nghttp2
+   library and output buffer of bufferevent. If it indicates we have
+   no business to this session, tear down the connection. If the
+   connection is not going to shutdown, we call session_send() to
+   process pending data in the output buffer. This is necessary
+   because we have a threshold on the buffer size to avoid too much
+   buffering. See send_callback(). */
+static void writecb(struct bufferevent *bev, void *ptr)
+{
+	http2_session_data *session_data = (http2_session_data *)ptr;
+	if(evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
+		return;
+	}
+	if(nghttp2_session_want_read(session_data->session) == 0
+			&& nghttp2_session_want_write(session_data->session) == 0) {
+		delete_http2_session_data(session_data);
+		return;
+	}
+	if(session_send(session_data) != 0) {
+		delete_http2_session_data(session_data);
+		return;
+	}
+}
+
+/* eventcb for bufferevent */
+static void eventcb(struct bufferevent *bev, short events, void *ptr)
+{
+	http2_session_data *session_data = (http2_session_data *)ptr;
+	if(events & BEV_EVENT_CONNECTED) {
+		const unsigned char *alpn = NULL;
+		unsigned int alpnlen = 0;
+		SSL *ssl;
+		(void)bev;
+
+		LM_ERR("%s connected\n", session_data->client_addr);
+
+		ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+		SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+
+		if(alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
+			LM_ERR("%s h2 is not negotiated\n", session_data->client_addr);
+			delete_http2_session_data(session_data);
+			return;
+		}
+
+		initialize_nghttp2_session(session_data);
+
+		if(send_server_connection_header(session_data) != 0
+				|| session_send(session_data) != 0) {
+			delete_http2_session_data(session_data);
+			return;
+		}
+
+		return;
+	}
+	if(events & BEV_EVENT_EOF) {
+		LM_ERR("%s EOF\n", session_data->client_addr);
+	} else if(events & BEV_EVENT_ERROR) {
+		LM_ERR("%s network error\n", session_data->client_addr);
+	} else if(events & BEV_EVENT_TIMEOUT) {
+		LM_ERR("%s timeout\n", session_data->client_addr);
+	}
+	delete_http2_session_data(session_data);
+}
+
+/* callback for evconnlistener */
+static void acceptcb(struct evconnlistener *listener, int fd,
+		struct sockaddr *addr, int addrlen, void *arg)
+{
+	app_context *app_ctx = (app_context *)arg;
+	http2_session_data *session_data;
+	(void)listener;
+
+	session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
+
+	bufferevent_setcb(
+			session_data->bev, readcb, writecb, eventcb, session_data);
+}
+
+static void start_listen(struct event_base *evbase, app_context *app_ctx)
+{
+	int rv;
+	struct addrinfo hints;
+	struct addrinfo *res, *rp;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+	hints.ai_flags |= AI_ADDRCONFIG;
+#endif /* AI_ADDRCONFIG */
+
+	rv = getaddrinfo(
+			_nghttp2_listen_addr.s, _nghttp2_listen_port.s, &hints, &res);
+	if(rv != 0) {
+		LM_ERR("Could not resolve server address");
+	}
+	for(rp = res; rp; rp = rp->ai_next) {
+		struct evconnlistener *listener;
+		listener = evconnlistener_new_bind(evbase, acceptcb, app_ctx,
+				LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 16, rp->ai_addr,
+				(int)rp->ai_addrlen);
+		if(listener) {
+			freeaddrinfo(res);
+
+			return;
+		}
+	}
+	LM_ERR("Could not start listener");
+}
+
+static void initialize_app_context(
+		app_context *app_ctx, SSL_CTX *ssl_ctx, struct event_base *evbase)
+{
+	memset(app_ctx, 0, sizeof(app_context));
+	app_ctx->ssl_ctx = ssl_ctx;
+	app_ctx->evbase = evbase;
+}
+
+int nghttp2_server_run(void)
+{
+	SSL_CTX *ssl_ctx;
+	app_context app_ctx;
+	struct event_base *evbase;
+	struct sigaction act;
+
+	memset(&act, 0, sizeof(struct sigaction));
+	act.sa_handler = SIG_IGN;
+	sigaction(SIGPIPE, &act, NULL);
+
+	ssl_ctx = create_ssl_ctx(
+			_nghttp2_tls_private_key.s, _nghttp2_tls_public_key.s);
+	evbase = event_base_new();
+	initialize_app_context(&app_ctx, ssl_ctx, evbase);
+	start_listen(evbase, &app_ctx);
+
+	event_base_loop(evbase, 0);
+
+	event_base_free(evbase);
+	SSL_CTX_free(ssl_ctx);
+
+	return 0;
+}

+ 89 - 0
src/modules/nghttp2/nghttp2_server.h

@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2024 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef NGHTTP2_SERVER_H_
+#define NGHTTP2_SERVER_H_
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <netinet/in.h>
+
+#include <string.h>
+#include <errno.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include <event.h>
+#include <event2/event.h>
+#include <event2/bufferevent_ssl.h>
+#include <event2/listener.h>
+
+#define NGHTTP2_NO_SSIZE_T
+#include <nghttp2/nghttp2.h>
+
+#include "../../core/str.h"
+#include "../../core/ip_addr.h"
+
+typedef ptrdiff_t nghttp2_ssize;
+
+typedef struct ksr_nghttp2_ctx
+{
+	//struct MHD_Connection *connection;
+	void *connection;
+	str method;
+	str url;
+	str httpversion;
+	str data;
+	//const union MHD_ConnectionInfo *cinfo;
+	void *cinfo;
+	char srcipbuf[IP_ADDR_MAX_STR_SIZE];
+	str srcip;
+} ksr_nghttp2_ctx_t;
+
+extern str _nghttp2_listen_port;
+extern str _nghttp2_listen_addr;
+extern str _nghttp2_tls_public_key;
+extern str _nghttp2_tls_private_key;
+extern int _nghttp2_server_pid;
+
+int nghttp2_server_run(void);
+
+#endif