From: W.C.A. Wijngaards Date: Mon, 20 Jan 2020 16:23:01 +0000 (+0100) Subject: dnstap test progam can log on one line type,ip,qname,qtype,qclass X-Git-Tag: 1.11.0rc1~120^2~111 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ac362625c3886d31799ae1854ee98155e0c33e34;p=thirdparty%2Funbound.git dnstap test progam can log on one line type,ip,qname,qtype,qclass --- diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index 30412fb3b..4b8ef1dad 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -56,12 +56,19 @@ #include "util/ub_event.h" #include "util/net_help.h" #include "services/listen_dnsport.h" +#include "sldns/sbuffer.h" +#include "sldns/wire2str.h" +#include +#include "dnstap/dnstap.pb-c.h" + +#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap" /** usage information for streamtcp */ static void usage(char* argv[]) { printf("usage: %s [options]\n", argv[0]); printf(" Listen to dnstap messages\n"); + printf("stdout has dnstap log, stderr has verbose server log\n"); printf("-u use unix socket with this file name\n"); printf("-l long format for DNS printout\n"); printf("-v more verbose log output\n"); @@ -69,6 +76,9 @@ static void usage(char* argv[]) exit(1); } +/** long format option, for multiline printout per message */ +static int longformat = 0; + /** tap callback variables */ struct tap_data { /** the fd */ @@ -79,16 +89,180 @@ struct tap_data { int len_done; /** have we read the data, and how many bytes of it */ size_t data_done; - /** are we bi-directional (if false, uni-directional) */ - int is_bidirectional; /** are we reading a control frame */ int control_frame; + /** are we bi-directional (if false, uni-directional) */ + int is_bidirectional; /** data of the frame */ uint8_t* frame; /** length of this frame */ size_t len; }; +/** log control frame contents */ +static void log_control_frame(uint8_t* pkt, size_t len) +{ + uint32_t frametype = 0; + char buf[256]; + size_t at = 0, remain; + uint8_t* pos; + if(verbosity == 0) return; + if(len < 4) { + log_err("malformed control frame, too short, len=%d", (int)len); + return; + } + buf[0]=0; + frametype = sldns_read_uint32(pkt); + if(frametype == FSTRM_CONTROL_FRAME_ACCEPT) { + at+=snprintf(buf+at, sizeof(buf)-at, "accept"); + } else if(frametype == FSTRM_CONTROL_FRAME_START) { + at+=snprintf(buf+at, sizeof(buf)-at, "start"); + } else if(frametype == FSTRM_CONTROL_FRAME_STOP) { + at+=snprintf(buf+at, sizeof(buf)-at, "stop"); + } else if(frametype == FSTRM_CONTROL_FRAME_READY) { + at+=snprintf(buf+at, sizeof(buf)-at, "ready"); + } else if(frametype == FSTRM_CONTROL_FRAME_FINISH) { + at+=snprintf(buf+at, sizeof(buf)-at, "finish"); + } else { + at+=snprintf(buf+at, sizeof(buf)-at, "type%d", + (int)frametype); + } + + /* show the content type options */ + pos = pkt + 4; + remain = len - 4; + while(remain >= 8) { + uint32_t field_type = sldns_read_uint32(pos); + uint32_t field_len = sldns_read_uint32(pos+4); + if(remain < field_len) { + at+=snprintf(buf+at, sizeof(buf)-at, "malformed_field"); + break; + } + if(field_type == FSTRM_CONTROL_FIELD_TYPE_CONTENT_TYPE) { + at+=snprintf(buf+at, sizeof(buf)-at, " content-type("); + if(at+field_len < sizeof(buf)) { + memmove(buf+at, pos+8, field_len); + at += field_len; + } + at+=snprintf(buf+at, sizeof(buf)-at, ")"); + } + pos += 8 + field_len; + remain -= (8 + field_len); + } + log_info("control frame %s", buf); +} + +/** convert mtype to string */ +static const char* mtype_to_str(enum _Dnstap__Message__Type mtype) +{ + switch(mtype) { + case DNSTAP__MESSAGE__TYPE__AUTH_QUERY: + return "AUTH_QUERY"; + case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE: + return "AUTH_RESPONSE"; + case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY: + return "RESOLVER_QUERY"; + case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE: + return "RESOLVER_RESPONSE"; + case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY: + return "CLIENT_QUERY"; + case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE: + return "CLIENT_RESPONSE"; + case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY: + return "FORWARDER_QUERY"; + case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE: + return "FORWARDER_RESPONSE"; + case DNSTAP__MESSAGE__TYPE__STUB_QUERY: + return "STUB_QUERY"; + case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE: + return "STUB_RESPONSE"; + default: break; + } + return "unknown_message_type"; +} + +/** convert type address to a string ip4 or ip6, malloced or NULL on fail */ +static char* str_of_addr(ProtobufCBinaryData address) +{ + char buf[64]; + socklen_t len = sizeof(buf); + if(address.len == 4) { + if(inet_ntop(AF_INET, address.data, buf, len)!=0) + return strdup(buf); + } else if(address.len == 16) { + if(inet_ntop(AF_INET6, address.data, buf, len)!=0) + return strdup(buf); + } + return NULL; +} + +/** convert message buffer (of dns bytes) to the first qname, type, class, + * malloced or NULL on fail */ +static char* q_of_msg(ProtobufCBinaryData message) +{ + char buf[300]; + /* header, name, type, class minimum to get the query tuple */ + if(message.len < 12 + 1 + 4 + 4) return NULL; + if(sldns_wire2str_rrquestion_buf(message.data+12, message.len-12, + buf, sizeof(buf)) != 0) { + /* remove trailing newline, tabs to spaces */ + if(buf[0] != 0) buf[strlen(buf)-1]=0; + if(strrchr(buf, '\t')) *strrchr(buf, '\t')=' '; + if(strrchr(buf, '\t')) *strrchr(buf, '\t')=' '; + return strdup(buf); + } + return NULL; +} + +/** log data frame contents */ +static void log_data_frame(uint8_t* pkt, size_t len) +{ + Dnstap__Dnstap* d = dnstap__dnstap__unpack(NULL, len, pkt); + const char* mtype = NULL; + char* maddr=NULL, *qinf=NULL; + if(!d) { + log_err("could not unpack"); + return; + } + if(d->base.descriptor != &dnstap__dnstap__descriptor) { + log_err("wrong base descriptor"); + dnstap__dnstap__free_unpacked(d, NULL); + return; + } + if(d->type != DNSTAP__DNSTAP__TYPE__MESSAGE) { + log_err("dnstap type not type_message"); + dnstap__dnstap__free_unpacked(d, NULL); + return; + } + if(d->message) { + mtype = mtype_to_str(d->message->type); + if(d->message->has_query_address) + maddr = str_of_addr(d->message->query_address); + else if(d->message->has_response_address) + maddr = str_of_addr(d->message->response_address); + if(d->message->has_query_message) + qinf = q_of_msg(d->message->query_message); + else if(d->message->has_response_message) + qinf = q_of_msg(d->message->response_message); + + } else { + mtype = "nomessage"; + } + + printf("%s%s%s%s%s\n", mtype, (maddr?" ":""), (maddr?maddr:""), + (qinf?" ":""), (qinf?qinf:"")); + free(maddr); + free(qinf); + + if(longformat) { + if(d->has_identity) { + } + if(d->has_version) { + } + } + dnstap__dnstap__free_unpacked(d, NULL); +} + /** receive bytes from fd, prints errors if bad, * returns 0: closed/error, -1: continue, >0 number of bytes */ static ssize_t receive_bytes(int fd, void* buf, size_t len) @@ -131,16 +305,95 @@ void tap_data_free(struct tap_data* data) free(data); } +/** reply with ACCEPT control frame to bidirectional client, + * returns 0 on error */ +static int reply_with_accept(int fd) +{ + /* control frame on reply: + * 4 bytes 0 escape + * 4 bytes bigendian length of frame + * 4 bytes bigendian type ACCEPT + * 4 bytes bigendian frame option content type + * 4 bytes bigendian length of string + * string of content type. + */ + uint32_t* acceptframe; + /* len includes the escape and framelength */ + size_t len = 4+4+4+4+4+strlen(DNSTAP_CONTENT_TYPE); + acceptframe = calloc(1, len); + if(!acceptframe) { + log_err("out of memory"); + return 0; + } + acceptframe[0] = 0; + acceptframe[1] = htonl(4+4+4+strlen(DNSTAP_CONTENT_TYPE)); + acceptframe[2] = htonl(FSTRM_CONTROL_FRAME_ACCEPT); + acceptframe[3] = htonl(FSTRM_CONTROL_FIELD_TYPE_CONTENT_TYPE); + acceptframe[4] = htonl(strlen(DNSTAP_CONTENT_TYPE)); + memmove(&acceptframe[5], DNSTAP_CONTENT_TYPE, + strlen(DNSTAP_CONTENT_TYPE)); + + fd_set_block(fd); + if(send(fd, acceptframe, len, 0) == -1) { +#ifndef USE_WINSOCK + log_err("send failed: %s", strerror(errno)); +#else + log_err("send failed: %s", wsa_strerror(WSAGetLastError())); +#endif + fd_set_nonblock(fd); + free(acceptframe); + return 0; + } + if(verbosity) log_info("sent control frame(accept) content-type:(%s)", + DNSTAP_CONTENT_TYPE); + + fd_set_nonblock(fd); + free(acceptframe); + return 1; +} + +/** reply with FINISH control frame to bidirectional client, + * returns 0 on error */ +static int reply_with_finish(int fd) +{ + /* control frame on reply: + * 4 bytes 0 escape + * 4 bytes bigendian length of frame + * 4 bytes bigendian type FINISH + */ + uint32_t finishframe[3]; + /* len includes the escape and framelength */ + size_t len = 4+4+4; + finishframe[0] = 0; + finishframe[1] = htonl(4); + finishframe[2] = htonl(FSTRM_CONTROL_FRAME_FINISH); + + fd_set_block(fd); + if(send(fd, finishframe, len, 0) == -1) { +#ifndef USE_WINSOCK + log_err("send failed: %s", strerror(errno)); +#else + log_err("send failed: %s", wsa_strerror(WSAGetLastError())); +#endif + fd_set_nonblock(fd); + return 0; + } + if(verbosity) log_info("sent control frame(finish)"); + + fd_set_nonblock(fd); + return 1; +} + /** callback for dnstap listener */ static void tap_callback(int fd, short ATTR_UNUSED(bits), void* arg) { struct tap_data* data = (struct tap_data*)arg; - if(verbosity) log_info("tap callback"); + if(verbosity>=3) log_info("tap callback"); while(data->len_done < 4) { uint32_t l = (uint32_t)data->len; ssize_t ret = receive_bytes(fd, ((uint8_t*)&l)+data->len_done, 4-data->len_done); - log_info("s recv %d got %d %d", (int)ret, (int)l, (int)ntohl(l)); + if(verbosity>=4) log_info("s recv %d", (int)ret); if(ret == 0) { /* closed or error */ tap_data_free(data); @@ -154,7 +407,7 @@ static void tap_callback(int fd, short ATTR_UNUSED(bits), void* arg) if(data->len_done < 4) return; /* continue later */ data->len = (size_t)(ntohl(l)); - if(verbosity) log_info("length is %d", (int)data->len); + if(verbosity>=3) log_info("length is %d", (int)data->len); if(data->len == 0) { /* it is a control frame */ data->control_frame = 1; @@ -170,10 +423,12 @@ static void tap_callback(int fd, short ATTR_UNUSED(bits), void* arg) } } } + /* we want to read the full length now */ if(data->data_done < data->len) { ssize_t r = receive_bytes(fd, data->frame + data->data_done, data->len - data->data_done); + if(verbosity>=4) log_info("f recv %d", (int)r); if(r == 0) { /* closed or error */ tap_data_free(data); @@ -186,13 +441,27 @@ static void tap_callback(int fd, short ATTR_UNUSED(bits), void* arg) if(data->data_done < data->len) return; /* continue later */ } + /* we are done with a frame */ - if(verbosity) log_info("received %sframe len %d", + if(verbosity>=3) log_info("received %sframe len %d", (data->control_frame?"control ":""), (int)data->len); - if(data->len >= 4 && ntohl(*(uint32_t*)data->frame) == + if(data->control_frame) + log_control_frame(data->frame, data->len); + else log_data_frame(data->frame, data->len); + + if(data->len >= 4 && sldns_read_uint32(data->frame) == FSTRM_CONTROL_FRAME_READY) { data->is_bidirectional = 1; if(verbosity) log_info("bidirectional stream"); + if(!reply_with_accept(fd)) { + tap_data_free(data); + } + } else if(data->len >= 4 && sldns_read_uint32(data->frame) == + FSTRM_CONTROL_FRAME_STOP && data->is_bidirectional) { + if(!reply_with_finish(fd)) { + tap_data_free(data); + return; + } } /* prepare for next frame */ @@ -202,6 +471,7 @@ static void tap_callback(int fd, short ATTR_UNUSED(bits), void* arg) data->len = 0; data->len_done = 0; data->data_done = 0; + } /** callback for main listening file descriptor */ @@ -279,7 +549,7 @@ setup_fd(char* socketpath) /** setup and run the server to listen to DNSTAP messages */ static void -setup_and_run(char* socketpath, int longformat) +setup_and_run(char* socketpath) { int fd; time_t secs = 0; @@ -316,7 +586,7 @@ extern char* optarg; int main(int argc, char** argv) { int c; - int usessl = 0, longformat = 0; + int usessl = 0; char* socketpath = NULL; #ifdef USE_WINSOCK WSADATA wsa_data; @@ -378,7 +648,7 @@ int main(int argc, char** argv) (void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); #endif } - setup_and_run(socketpath, longformat); + setup_and_run(socketpath); checklock_stop(); #ifdef USE_WINSOCK WSACleanup();