From: Remi Gacogne Date: Fri, 9 Jul 2021 14:52:52 +0000 (+0200) Subject: dnsdist: Proof of concept of DNS over HTTP/2 client X-Git-Tag: dnsdist-1.7.0-alpha1~23^2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d523caf39eb4a747ff52bd305367cc8d21a25e8e;p=thirdparty%2Fpdns.git dnsdist: Proof of concept of DNS over HTTP/2 client --- diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 7fc5aaf8f8..de23e7b933 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -2171,6 +2171,8 @@ static void sighandler(int sig) } #endif +#include "dnsdist-nghttp2.hh" + int main(int argc, char** argv) { try { @@ -2638,6 +2640,8 @@ int main(int argc, char** argv) secpollthread.detach(); } + sendHTTP2Query(); + if(g_cmdLine.beSupervised) { #ifdef HAVE_SYSTEMD sd_notify(0, "READY=1"); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index bc18aaf62c..5928ca9e8b 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -158,6 +158,7 @@ dnsdist_SOURCES = \ dnsdist-lua-vars.cc \ dnsdist-lua-web.cc \ dnsdist-lua.cc dnsdist-lua.hh \ + dnsdist-nghttp2.cc dnsdist-nghttp2.hh \ dnsdist-prometheus.hh \ dnsdist-protobuf.cc dnsdist-protobuf.hh \ dnsdist-protocols.cc dnsdist-protocols.hh \ @@ -301,6 +302,7 @@ testrunner_SOURCES = \ dnsdist_LDFLAGS = \ $(AM_LDFLAGS) \ $(PROGRAM_LDFLAGS) \ + -lnghttp2 \ -pthread dnsdist_LDADD = \ diff --git a/pdns/dnsdistdist/dnsdist-nghttp2.cc b/pdns/dnsdistdist/dnsdist-nghttp2.cc new file mode 100644 index 0000000000..9e95676b62 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-nghttp2.cc @@ -0,0 +1,282 @@ + +#include + +#include "iputils.hh" +#include "libssl.hh" +#include "noinitvector.hh" +#include "tcpiohandler.hh" +#include "sstuff.hh" + +#warning remove me +#include "dnswriter.hh" + +struct MyUserData +{ + std::unique_ptr session{nullptr, nghttp2_session_del}; + std::unique_ptr handler; + PacketBuffer out; + PacketBuffer in; + size_t outPos{0}; + size_t inPos{0}; +}; + +static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) { + cerr<<"in "<<__PRETTY_FUNCTION__<(user_data); + userData->out.insert(userData->out.end(), data, data + length); + userData->handler->write(userData->out.data() + userData->outPos, userData->out.size() - userData->outPos, timeval{2, 0}); + userData->out.clear(); + return length; +} + +static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) { + cerr<<"in "<<__PRETTY_FUNCTION__<(user_data); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { + cerr<<"All headers received"<settings.niv<settings.niv; idx++) { + cerr<<"- "<settings.iv[idx].settings_id<<" "<settings.iv[idx].value<(user_data); + cerr<<"Got data of size "<(data), len)<(user_data); + + cerr<<"Stream "<(user_data); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { + /* Print response headers for the initiated request. */ + cerr<<"got header for "<hd.stream_id<<":"<(name), namelen)<(value), valuelen)<(user_data); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { + cerr<<"Response headers for stream ID="<hd.stream_id<read(userData.in.data(), userData.in.size(), timeval{2, 0}, timeval{2, 0}, true); + // userData.handler->tryRead(userData.in, pos, userData.in.size()); + cerr<<"got "< 0) { + auto readlen = nghttp2_session_mem_recv(userData.session.get(), userData.in.data(), pos); + cerr<<"nghttp2_session_mem_recv returned "< tlsCtx = getTLSContext(tlsParams); + + Socket sock(remote.sin4.sin_family, SOCK_STREAM); + // FIXME + auto fd = sock.getHandle(); + setTCPNoDelay(fd); + MyUserData userData; + userData.handler = std::make_unique(host, sock.releaseHandle(), timeval{2, 0}, tlsCtx, time(nullptr)); + userData.handler->connect(true, remote, timeval{2, 0}); + + /* check ALPN: +SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "h2 is not negotiated\n"); + delete_http2_session_data(session_data); + return; + } + */ + + nghttp2_session_callbacks* cbs = nullptr; + if (nghttp2_session_callbacks_new(&cbs) != 0) { + cerr<<"unable to create a callback object for a new HTTP/2 session"< callbacks(cbs, nghttp2_session_callbacks_del); + cbs = nullptr; + + nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks.get(), on_begin_headers_callback); + + nghttp2_session* sess = nullptr; + if (nghttp2_session_client_new(&sess, callbacks.get(), &userData) != 0) { + cerr<<"Coult not allocate a new HTTP/2 session"<(sess, nghttp2_session_del); + sess = nullptr; + + callbacks.reset(); + +#warning we should make the 100 configurable here, as we might want a lower number before receiving the one actually supported by the server +#warning we should also make the window size configurable, but 16M is a nice default + nghttp2_settings_entry iv[] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, + {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}, + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16*1024*1024} + }; + /* client 24 bytes magic string will be sent by nghttp2 library */ + int rv = nghttp2_submit_settings(userData.session.get(), NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(*iv)); + if (rv != 0) { + cerr<<"Could not submit SETTINGS: "< pw(userData.in, DNSName("doh.dnsdist.org."), QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.commit(); + + /* we could use nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME and nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE + to avoid a copy and lowercasing as long as we take care of making sure that the data will outlive the request + and that it is already lowercased. */ + auto payloadSize = std::to_string(userData.in.size()); + const nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "POST"), + MAKE_NV2(":scheme", "https"), + MAKE_NV(":authority", host.c_str(), host.size()), + MAKE_NV(":path", path.c_str(), path.size()), + MAKE_NV2("accept", "application/dns-message"), + MAKE_NV2("content-type", "application/dns-message"), + MAKE_NV("content-length", payloadSize.c_str(), payloadSize.size()), + MAKE_NV2("user-agent", "nghttp2-" NGHTTP2_VERSION "/dnsdist") + }; + + /* f data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key in nva (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set + */ + cerr<<"Remote size window is "< ssize_t + { + cerr<<"in data provider"<(user_data); + if (userData->inPos >= userData->in.size()) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + cerr<<"EOF"<in.size()- userData->inPos; + size_t toCopy = length > remaining ? remaining : length; + memcpy(buf, &userData->in.at(userData->inPos), toCopy); + userData->inPos += toCopy; + cerr<