1 /*
2  * This file is part of PowerDNS or dnsdist.
3  * Copyright -- PowerDNS.COM B.V. and its contributors
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * In addition, for the avoidance of any doubt, permission is granted to
10  * link this program with OpenSSL and to (re)distribute the binaries
11  * produced as the result of such linking.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 #pragma once
23 
24 #include <protozero/pbf_writer.hpp>
25 
26 #include "config.h"
27 #include "iputils.hh"
28 #include "gettime.hh"
29 #include "uuid-utils.hh"
30 
31 namespace pdns {
32   namespace ProtoZero {
33     class Message {
34     public:
35       enum class MessageType : int32_t { DNSQueryType = 1, DNSResponseType = 2, DNSOutgoingQueryType = 3, DNSIncomingResponseType = 4 };
36       enum class Field : protozero::pbf_tag_type { type = 1, messageId = 2, serverIdentity = 3, socketFamily = 4, socketProtocol = 5, from = 6, to = 7, inBytes = 8, timeSec = 9, timeUsec = 10, id = 11, question = 12, response = 13, originalRequestorSubnet = 14, requestorId = 15, initialRequestId = 16, deviceId = 17, newlyObservedDomain = 18, deviceName = 19, fromPort = 20, toPort = 21 };
37       enum class QuestionField : protozero::pbf_tag_type { qName = 1, qType = 2, qClass = 3};
38       enum class ResponseField : protozero::pbf_tag_type { rcode = 1, rrs = 2, appliedPolicy = 3, tags = 4, queryTimeSec = 5, queryTimeUsec = 6, appliedPolicyType = 7, appliedPolicyTrigger = 8, appliedPolicyHit = 9, appliedPolicyKind = 10, validationState = 11 };
39       enum class RRField : protozero::pbf_tag_type { name = 1, type = 2, class_ = 3, ttl = 4, rdata = 5, udr = 6 };
40 
Message(std::string & buffer)41       Message(std::string& buffer): d_buffer(buffer), d_message{d_buffer}
42       {
43       }
44 
45       Message(const Message&) = delete;
46       Message(Message&&) = delete;
47       Message& operator=(const Message&) = delete;
48       Message& operator=(Message&&) = delete;
49 
50       void setRequest(const boost::uuids::uuid& uniqueId, const ComboAddress& requestor, const ComboAddress& local, const DNSName& qname, uint16_t qtype, uint16_t qclass, uint16_t id, bool tcp, size_t len);
51       void setResponse(const DNSName& qname, uint16_t qtype, uint16_t qclass);
52 
setType(MessageType mtype)53       void setType(MessageType mtype)
54       {
55         add_enum(d_message, Field::type, static_cast<int32_t>(mtype));
56       }
57 
setMessageIdentity(const boost::uuids::uuid & uniqueId)58       void setMessageIdentity(const boost::uuids::uuid& uniqueId)
59       {
60         add_bytes(d_message, Field::messageId, reinterpret_cast<const char*>(uniqueId.begin()), uniqueId.size());
61       }
62 
setServerIdentity(const std::string & serverIdentity)63       void setServerIdentity(const std::string& serverIdentity)
64       {
65         add_bytes(d_message, Field::serverIdentity, serverIdentity.data(), serverIdentity.length());
66       }
67 
setSocketFamily(int family)68       void setSocketFamily(int family)
69       {
70         add_enum(d_message, Field::socketFamily, family == AF_INET ? 1 : 2);
71       }
72 
setSocketProtocol(bool tcp)73       void setSocketProtocol(bool tcp)
74       {
75         add_enum(d_message, Field::socketProtocol, tcp ? 2 : 1);
76       }
77 
setFrom(const ComboAddress & ca)78       void setFrom(const ComboAddress& ca)
79       {
80         encodeComboAddress(static_cast<protozero::pbf_tag_type>(Field::from), ca);
81       }
82 
setTo(const ComboAddress & ca)83       void setTo(const ComboAddress& ca)
84       {
85         encodeComboAddress(static_cast<protozero::pbf_tag_type>(Field::to), ca);
86       }
87 
setInBytes(uint64_t len)88       void setInBytes(uint64_t len)
89       {
90         add_uint64(d_message, Field::inBytes, len);
91       }
92 
setTime()93       void setTime()
94       {
95         struct timespec ts;
96         gettime(&ts, true);
97 
98         setTime(ts.tv_sec, ts.tv_nsec / 1000);
99       }
100 
setTime(time_t sec,uint32_t usec)101       void setTime(time_t sec, uint32_t usec)
102       {
103         add_uint32(d_message, Field::timeSec, sec);
104         add_uint32(d_message, Field::timeUsec, usec);
105       }
106 
setId(uint16_t id)107       void setId(uint16_t id)
108       {
109         add_uint32(d_message, Field::id, ntohs(id));
110       }
111 
setQuestion(const DNSName & qname,uint16_t qtype,uint16_t qclass)112       void setQuestion(const DNSName& qname, uint16_t qtype, uint16_t qclass)
113       {
114         protozero::pbf_writer pbf_question{d_message, static_cast<protozero::pbf_tag_type>(Field::question)};
115         encodeDNSName(pbf_question, d_buffer, static_cast<protozero::pbf_tag_type>(QuestionField::qName), qname);
116         pbf_question.add_uint32(static_cast<protozero::pbf_tag_type>(QuestionField::qType), qtype);
117         pbf_question.add_uint32(static_cast<protozero::pbf_tag_type>(QuestionField::qClass), qclass);
118       }
119 
setEDNSSubnet(const Netmask & nm,uint8_t mask)120       void setEDNSSubnet(const Netmask& nm, uint8_t mask)
121       {
122         encodeNetmask(static_cast<protozero::pbf_tag_type>(Field::originalRequestorSubnet), nm, mask);
123       }
124 
setRequestorId(const std::string & req)125       void setRequestorId(const std::string& req)
126       {
127         if (!req.empty()) {
128           add_string(d_message, Field::requestorId, req);
129         }
130       }
131 
setInitialRequestID(const boost::uuids::uuid & uniqueId)132       void setInitialRequestID(const boost::uuids::uuid& uniqueId)
133       {
134         add_bytes(d_message, Field::initialRequestId, reinterpret_cast<const char*>(uniqueId.begin()), uniqueId.size());
135       }
136 
setDeviceId(const std::string & id)137       void setDeviceId(const std::string& id)
138       {
139         if (!id.empty()) {
140           add_string(d_message, Field::deviceId, id);
141         }
142       }
143 
setNewlyObservedDomain(bool nod)144       void setNewlyObservedDomain(bool nod)
145       {
146         add_bool(d_message, Field::newlyObservedDomain, nod);
147       }
148 
setDeviceName(const std::string & name)149       void setDeviceName(const std::string& name)
150       {
151         if (!name.empty()) {
152           add_string(d_message, Field::deviceName, name);
153         }
154       }
155 
setFromPort(in_port_t port)156       void setFromPort(in_port_t port)
157       {
158         add_uint32(d_message, Field::fromPort, port);
159       }
160 
setToPort(in_port_t port)161       void setToPort(in_port_t port)
162       {
163         add_uint32(d_message, Field::toPort, port);
164       }
165 
startResponse()166       void startResponse()
167       {
168         d_response = protozero::pbf_writer{d_message, static_cast<protozero::pbf_tag_type>(Field::response)};
169       }
170 
commitResponse()171       void commitResponse()
172       {
173         d_response.commit();
174       }
175 
setResponseCode(uint8_t rcode)176       void setResponseCode(uint8_t rcode)
177       {
178         d_response.add_uint32(static_cast<protozero::pbf_tag_type>(ResponseField::rcode), rcode);
179       }
180 
setNetworkErrorResponseCode()181       void setNetworkErrorResponseCode()
182       {
183         /* special code meaning 'network error', like a timeout */
184         d_response.add_uint32(static_cast<protozero::pbf_tag_type>(ResponseField::rcode), 65536);
185       }
186 
setAppliedPolicy(const std::string & policy)187       void setAppliedPolicy(const std::string& policy)
188       {
189         d_response.add_string(static_cast<protozero::pbf_tag_type>(ResponseField::appliedPolicy), policy);
190       }
191 
addPolicyTags(const std::unordered_set<std::string> & tags)192       void addPolicyTags(const std::unordered_set<std::string>& tags)
193       {
194         for (const auto& tag : tags) {
195           addPolicyTag(tag);
196         }
197       }
198 
addPolicyTag(const string & tag)199       void addPolicyTag(const string& tag)
200       {
201         d_response.add_string(static_cast<protozero::pbf_tag_type>(ResponseField::tags), tag);
202       }
203 
setQueryTime(uint32_t sec,uint32_t usec)204       void setQueryTime(uint32_t sec, uint32_t usec)
205       {
206         d_response.add_uint32(static_cast<protozero::pbf_tag_type>(ResponseField::queryTimeSec), sec);
207         d_response.add_uint32(static_cast<protozero::pbf_tag_type>(ResponseField::queryTimeUsec), usec);
208       }
209 
210       void addRRsFromPacket(const char* packet, const size_t len, bool includeCNAME=false);
211       void addRR(const DNSName& name, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& blob);
212 
213     protected:
214       void encodeComboAddress(protozero::pbf_tag_type type, const ComboAddress& ca);
215       void encodeNetmask(protozero::pbf_tag_type type, const Netmask& subnet, uint8_t mask);
216       void encodeDNSName(protozero::pbf_writer& pbf, std::string& buffer, protozero::pbf_tag_type type, const DNSName& name);
217 
add_enum(protozero::pbf_writer & writer,Field type,int32_t value)218       static void add_enum(protozero::pbf_writer& writer, Field type, int32_t value)
219       {
220         writer.add_enum(static_cast<protozero::pbf_tag_type>(type), value);
221       }
222 
add_bool(protozero::pbf_writer & writer,Field type,bool value)223       static void add_bool(protozero::pbf_writer& writer, Field type, bool value)
224       {
225         writer.add_bool(static_cast<protozero::pbf_tag_type>(type), value);
226       }
227 
add_uint32(protozero::pbf_writer & writer,Field type,uint32_t value)228       static void add_uint32(protozero::pbf_writer& writer, Field type, uint32_t value)
229       {
230         writer.add_uint32(static_cast<protozero::pbf_tag_type>(type), value);
231       }
232 
add_uint64(protozero::pbf_writer & writer,Field type,uint64_t value)233       static void add_uint64(protozero::pbf_writer& writer, Field type, uint64_t value)
234       {
235         writer.add_uint64(static_cast<protozero::pbf_tag_type>(type), value);
236       }
237 
add_bytes(protozero::pbf_writer & writer,Field type,const char * data,size_t len)238       static void add_bytes(protozero::pbf_writer& writer, Field type, const char* data, size_t len)
239       {
240         writer.add_bytes(static_cast<protozero::pbf_tag_type>(type), data, len);
241       }
242 
add_string(protozero::pbf_writer & writer,Field type,const std::string & str)243       static void add_string(protozero::pbf_writer& writer, Field type, const std::string& str)
244       {
245         writer.add_string(static_cast<protozero::pbf_tag_type>(type), str);
246       }
247 
248 
249       std::string& d_buffer;
250       protozero::pbf_writer d_message;
251       protozero::pbf_writer d_response;
252     };
253   };
254 };
255