if(vars.count("ipBindAddrNoPort")) {
ret->ipBindAddrNoPort=boost::get<bool>(vars["ipBindAddrNoPort"]);
+ }
+
+ if(vars.count("addXPF")) {
+ ret->addXPF=boost::get<bool>(vars["addXPF"]);
}
if(vars.count("maxCheckFailures")) {
break;
}
+ if (dq.addXPF && ds->addXPF) {
+ addXPF(dq);
+ }
+
int dsock = -1;
uint16_t downstreamFailures=0;
#ifdef MSG_FASTOPEN
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "sstuff.hh"
-#include "misc.hh"
-#include <netinet/tcp.h>
+
+#include "config.h"
+
+#include <fstream>
+#include <getopt.h>
+#include <grp.h>
#include <limits>
-#include "dolog.hh"
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <sys/resource.h>
+#include <unistd.h>
#if defined (__OpenBSD__) || defined(__NetBSD__)
#include <readline/readline.h>
#include <editline/readline.h>
#endif
-#include "dnsname.hh"
-#include "dnswriter.hh"
-#include "base64.hh"
-#include <fstream>
-#include "delaypipe.hh"
-#include <unistd.h>
-#include "sodcrypto.hh"
-#include "dnsdist-lua.hh"
-#include <grp.h>
-#include <pwd.h>
-#include "lock.hh"
-#include <getopt.h>
-#include <sys/resource.h>
-#include "dnsdist-cache.hh"
-#include "gettime.hh"
-#include "ednsoptions.hh"
-
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
+#include "dnsdist.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-lua.hh"
+
+#include "base64.hh"
+#include "delaypipe.hh"
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "sodcrypto.hh"
+#include "sstuff.hh"
+#include "xpf.hh"
+
+#ifdef HAVE_PROTOBUF
thread_local boost::uuids::random_generator t_uuidGenerator;
+#endif
/* Known sins:
return result;
}
+bool addXPF(DNSQuestion& dq)
+{
+ std::string payload = generateXPFPayload(dq.tcp, *dq.remote, *dq.local);
+ uint8_t root = '\0';
+ dnsrecordheader drh;
+ drh.d_type = htons(QType::XPF);
+ drh.d_class = htons(QClass::IN);
+ drh.d_ttl = 0;
+ drh.d_clen = htons(payload.size());
+ size_t recordHeaderLen = sizeof(root) + sizeof(drh);
+
+ size_t available = dq.size - dq.len;
+
+ if ((payload.size() + recordHeaderLen) > available) {
+ return false;
+ }
+
+ size_t pos = dq.len;
+ memcpy(reinterpret_cast<char*>(dq.dh) + pos, &root, sizeof(root));
+ pos += sizeof(root);
+ memcpy(reinterpret_cast<char*>(dq.dh) + pos, &drh, sizeof(drh));
+ pos += sizeof(drh);
+ memcpy(reinterpret_cast<char*>(dq.dh) + pos, payload.data(), payload.size());
+ pos += payload.size();
+
+ dq.len = pos;
+
+ dq.dh->arcount = htons(ntohs(dq.dh->arcount) + 1);
+
+ return true;
+}
+
static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest)
{
if (msgh->msg_flags & MSG_TRUNC) {
return;
}
+ if (dq.addXPF && ss->addXPF) {
+ addXPF(dq);
+ }
+
ss->queries++;
unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
}
#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+
// listens to incoming queries, sends out to downstream servers, noting the intended return path
static void* udpClientThread(ClientState* cs)
try
bool skipCache{false};
bool ecsOverride;
bool useECS{true};
+ bool addXPF{true};
};
struct DNSResponse : DNSQuestion
bool mustResolve{false};
bool upStatus{false};
bool useECS{false};
+ bool addXPF{false};
bool setCD{false};
std::atomic<bool> connected{false};
bool tcpFastOpen{false};
bool encryptResponse(char* response, uint16_t* responseLen, size_t responseSize, bool tcp, std::shared_ptr<DnsCryptQuery> dnsCryptQuery, dnsheader** dh, dnsheader* dhCopy);
#endif
+bool addXPF(DNSQuestion& dq);
+
#include "dnsdist-snmp.hh"
extern bool g_snmpEnabled;
sstuff.hh \
statnode.cc statnode.hh \
tcpiohandler.cc tcpiohandler.hh \
+ xpf.cc xpf.hh \
ext/luawrapper/include/LuaContext.hpp \
ext/json11/json11.cpp \
ext/json11/json11.hpp \
sholder.hh \
sodcrypto.cc \
sstuff.hh \
- testrunner.cc
+ testrunner.cc \
+ xpf.cc xpf.hh
testrunner_LDFLAGS = \
$(AM_LDFLAGS) \
newServer({
address="IP:PORT", -- IP and PORT of the backend server (mandatory)
+ addXPF=BOOL, -- Add the client's IP address and port to the query, along with the original destination address and port,
+ -- using the experimental XPF record from `draft-bellis-dnsop-xpf`. Default to false
qps=NUM, -- Limit the number of queries per second to NUM, when using the `firstAvailable` policy
order=NUM, -- The order of this server, used by the `leastOustanding` and `firstAvailable` policies
weight=NUM, -- The weight of this server, used by the `wrandom` and `whashed` policies
--- /dev/null
+../xpf.cc
\ No newline at end of file
--- /dev/null
+../xpf.hh
\ No newline at end of file
sin4.sin_family=AF_INET;
sin4.sin_addr.s_addr=0;
sin4.sin_port=0;
+ sin6.sin6_scope_id = 0;
+ sin6.sin6_flowinfo = 0;
}
ComboAddress(const struct sockaddr *sa, socklen_t salen) {
string toString() const
{
char host[1024];
- int retval;
+ int retval = 0;
if(sin4.sin_family && !(retval = getnameinfo((struct sockaddr*) this, getSocklen(), host, sizeof(host),0, 0, NI_NUMERICHOST)))
return host;
else
return address;
}
-inline ComboAddress makeComboAddressFromRaw(uint8_t version, const string &str)
+inline ComboAddress makeComboAddressFromRaw(uint8_t version, const char* raw, size_t len)
{
ComboAddress address;
- size_t len;
if (version == 4) {
- len = 4;
- address.sin4.sin_family=AF_INET;
- if(str.size() != len) throw NetmaskException("invalid raw address length");
- memcpy(&address.sin4.sin_addr, str.c_str(), len);
+ address.sin4.sin_family = AF_INET;
+ if (len != sizeof(address.sin4.sin_addr)) throw NetmaskException("invalid raw address length");
+ memcpy(&address.sin4.sin_addr, raw, sizeof(address.sin4.sin_addr));
}
else if (version == 6) {
- len = 16;
- address.sin4.sin_family=AF_INET6;
- if(str.size() != len) throw NetmaskException("invalid raw address length");
- memcpy(&address.sin6.sin6_addr, str.c_str(), len);
+ address.sin6.sin6_family = AF_INET6;
+ if (len != sizeof(address.sin6.sin6_addr)) throw NetmaskException("invalid raw address length");
+ memcpy(&address.sin6.sin6_addr, raw, sizeof(address.sin6.sin6_addr));
}
else throw NetmaskException("invalid address family");
return address;
}
+inline ComboAddress makeComboAddressFromRaw(uint8_t version, const string &str)
+{
+ return makeComboAddressFromRaw(version, str.c_str(), str.size());
+}
+
/** This class represents a netmask and can be queried to see if a certain
IP address is matched by this mask */
class Netmask
#include "namespaces.hh"
+#include "xpf.hh"
+
typedef map<ComboAddress, uint32_t, ComboAddress::addressOnlyLessThan> tcpClientCounts_t;
static thread_local std::shared_ptr<RecursorLua4> t_pdl;
static AtomicCounter counter;
static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
+static NetmaskGroup g_XPFAcl;
static size_t g_tcpMaxQueriesPerConn;
static uint64_t g_latencyStatSize;
static uint32_t g_disthashseed;
DNSComboWriter(const char* data, uint16_t len, const struct timeval& now) : d_mdp(true, data, len), d_now(now),
d_tcp(false), d_socket(-1)
{}
- MOADNSParser d_mdp;
- void setRemote(const ComboAddress* sa)
+
+ void setRemote(const ComboAddress& sa)
+ {
+ d_remote=sa;
+ }
+
+ void setSource(const ComboAddress& sa)
{
- d_remote=*sa;
+ d_source=sa;
}
void setLocal(const ComboAddress& sa)
d_local=sa;
}
+ void setDestination(const ComboAddress& sa)
+ {
+ d_destination=sa;
+ }
void setSocket(int sock)
{
string getRemote() const
{
- return d_remote.toString();
+ if (d_source == d_remote) {
+ return d_source.toStringWithPort();
+ }
+ return d_source.toStringWithPort() + " (proxied by " + d_remote.toStringWithPort() + ")";
}
+ MOADNSParser d_mdp;
struct timeval d_now;
- ComboAddress d_remote, d_local;
+ /* Remote client, might differ from d_source
+ in case of XPF, in which case d_source holds
+ the IP of the client and d_remote of the proxy
+ */
+ ComboAddress d_remote;
+ ComboAddress d_source;
+ /* Destination address, might differ from
+ d_destination in case of XPF, in which case
+ d_destination holds the IP of the proxy and
+ d_local holds our own. */
+ ComboAddress d_local;
+ ComboAddress d_destination;
#ifdef HAVE_PROTOBUF
boost::uuids::uuid d_uuid;
string d_requestorId;
static string makeLoginfo(DNSComboWriter* dc)
try
{
- return "("+dc->d_mdp.d_qname.toLogString()+"/"+DNSRecordContent::NumberToType(dc->d_mdp.d_qtype)+" from "+(dc->d_remote.toString())+")";
+ return "("+dc->d_mdp.d_qname.toLogString()+"/"+DNSRecordContent::NumberToType(dc->d_mdp.d_qtype)+" from "+(dc->getRemote())+")";
}
catch(...)
{
RecProtoBufMessage pbMessage(RecProtoBufMessage::Response);
#ifdef HAVE_PROTOBUF
if (luaconfsLocal->protobufServer) {
- Netmask requestorNM(dc->d_remote, dc->d_remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
const ComboAddress& requestor = requestorNM.getMaskedNetwork();
- pbMessage.update(dc->d_uuid, &requestor, &dc->d_local, dc->d_tcp, dc->d_mdp.d_header.id);
+ pbMessage.update(dc->d_uuid, &requestor, &dc->d_destination, dc->d_tcp, dc->d_mdp.d_header.id);
pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
pbMessage.setQuestion(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
}
int res = RCode::NoError;
DNSFilterEngine::Policy appliedPolicy;
DNSRecord spoofed;
- RecursorLua4::DNSQuestion dq(dc->d_remote, dc->d_local, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ);
+ RecursorLua4::DNSQuestion dq(dc->d_source, dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_tcp, variableAnswer, wantsRPZ);
dq.ednsFlags = &edo.d_Z;
dq.ednsOptions = &dc->d_ednsOpts;
dq.tag = dc->d_tag;
// Check if the query has a policy attached to it
if (wantsRPZ) {
- appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_remote, sr.d_discardedPolicies);
+ appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies);
}
// if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
if(!shouldNotValidate && sr.isDNSSECValidationRequested()) {
try {
if(sr.doLog()) {
- L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<endl;
+ L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<endl;
}
auto state = sr.getValidationState();
if(state == Secure) {
if(sr.doLog()) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates correctly"<<endl;
}
// Is the query source interested in the value of the ad-bit?
}
else if(state == Insecure) {
if(sr.doLog()) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Insecure"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates as Insecure"<<endl;
}
pw.getHeader()->ad=0;
}
else if(state == Bogus) {
if(g_dnssecLogBogus || sr.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
- L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates as Bogus"<<endl;
+ L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->getRemote()<<" validates as Bogus"<<endl;
}
// Does the query or validation mode sending out a SERVFAIL on validation errors?
if(ret.size()) {
orderAndShuffle(ret);
- if(auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_remote)) {
+ if(auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
stable_sort(ret.begin(), ret.end(), *sl);
variableAnswer=true;
}
}
g_rs.submitResponse(dc->d_mdp.d_qtype, packet.size(), !dc->d_tcp);
- updateResponseStats(res, dc->d_remote, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+ updateResponseStats(res, dc->d_source, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
#ifdef HAVE_PROTOBUF
if (luaconfsLocal->protobufServer && (!luaconfsLocal->protobufTaggedOnly || (appliedPolicy.d_name && !appliedPolicy.d_name->empty()) || !dc->d_policyTags.empty())) {
pbMessage.setBytes(packet.size());
addCMsgSrcAddr(&msgh, cbuf, &dc->d_local, 0);
}
if(sendmsg(dc->d_socket, &msgh, 0) < 0 && g_logCommonErrors)
- L<<Logger::Warning<<"Sending UDP reply to client "<<dc->d_remote.toStringWithPort()<<" failed with: "<<strerror(errno)<<endl;
+ L<<Logger::Warning<<"Sending UDP reply to client "<<dc->getRemote()<<" failed with: "<<strerror(errno)<<endl;
if(!SyncRes::s_nopacketcache && !variableAnswer && !sr.wasVariable() ) {
t_packetCache->insertResponsePacket(dc->d_tag, dc->d_qhash, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass,
string((const char*)&*packet.begin(), packet.size()),
}
}
-static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options)
+static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
+ bool& foundECS, EDNSSubnetOpts* ednssubnet, std::map<uint16_t, EDNSOptionView>* options,
+ bool& foundXPF, ComboAddress* xpfSource, ComboAddress* xpfDest)
{
- bool found = false;
- const struct dnsheader* dh = (struct dnsheader*)question.c_str();
+ const bool lookForXPF = xpfSource != nullptr;
+ const bool lookForECS = ednssubnet != nullptr;
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(question.c_str());
size_t questionLen = question.length();
unsigned int consumed=0;
*dnsname=DNSName(question.c_str(), questionLen, sizeof(dnsheader), false, qtype, qclass, &consumed);
size_t pos= sizeof(dnsheader)+consumed+4;
- /* at least OPT root label (1), type (2), class (2) and ttl (4) + OPT RR rdlen (2)
- = 11 */
- if(ntohs(dh->arcount) == 1 && questionLen > pos + 11) { // this code can extract one (1) EDNS Subnet option
+ const size_t headerSize = /* root */ 1 + sizeof(dnsrecordheader);
+ const uint16_t arcount = ntohs(dh->arcount);
+
+ for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && ((lookForECS && !foundECS) || (lookForXPF && !foundXPF)); arpos++) {
+ if (question.at(pos) != 0) {
+ /* not an OPT or a XPF, bye. */
+ return;
+ }
+
+ pos += 1;
+ const dnsrecordheader* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos));
+ pos += sizeof(dnsrecordheader);
+
+ if (pos >= questionLen) {
+ return;
+ }
+
/* OPT root label (1) followed by type (2) */
- if(question.at(pos)==0 && question.at(pos+1)==0 && question.at(pos+2)==QType::OPT) {
+ if(lookForECS && ntohs(drh->d_type) == QType::OPT) {
if (!options) {
char* ecsStart = nullptr;
size_t ecsLen = 0;
- int res = getEDNSOption((char*)question.c_str()+pos+9, questionLen - pos - 9, EDNSOptionCode::ECS, &ecsStart, &ecsLen);
+ /* we need to pass the record len */
+ int res = getEDNSOption(const_cast<char*>(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen)))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStart, &ecsLen);
if (res == 0 && ecsLen > 4) {
EDNSSubnetOpts eso;
if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
*ednssubnet=eso;
- found = true;
+ foundECS = true;
}
}
}
else {
- int res = getEDNSOptions((char*)question.c_str()+pos+9, questionLen - pos - 9, *options);
+ /* we need to pass the record len */
+ int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos -sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options);
if (res == 0) {
const auto& it = options->find(EDNSOptionCode::ECS);
if (it != options->end() && it->second.content != nullptr && it->second.size > 0) {
EDNSSubnetOpts eso;
if(getEDNSSubnetOptsFromString(it->second.content, it->second.size, &eso)) {
*ednssubnet=eso;
- found = true;
+ foundECS = true;
}
}
}
}
}
+ else if (lookForXPF && ntohs(drh->d_type) == QType::XPF && ntohs(drh->d_class) == QClass::IN && drh->d_ttl == 0) {
+ if ((questionLen - pos) < ntohs(drh->d_clen)) {
+ return;
+ }
+
+ foundXPF = parseXPFPayload(reinterpret_cast<const char*>(&question.at(pos)), ntohs(drh->d_clen), *xpfSource, xpfDest);
+ }
+
+ pos += ntohs(drh->d_clen);
}
- return found;
}
static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
}
if(!bytes || bytes < 0) {
if(g_logCommonErrors)
- L<<Logger::Error<<"TCP client "<< conn->d_remote.toString() <<" disconnected after first byte"<<endl;
+ L<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
t_fdm->removeReadFD(fd);
return;
}
else if(conn->state==TCPConnection::GETQUESTION) {
ssize_t bytes=recv(conn->getFD(), conn->data + conn->bytesread, conn->qlen - conn->bytesread, 0);
if(!bytes || bytes < 0 || bytes > std::numeric_limits<std::uint16_t>::max()) {
- L<<Logger::Error<<"TCP client "<< conn->d_remote.toString() <<" disconnected while reading question body"<<endl;
+ L<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
t_fdm->removeReadFD(fd);
return;
}
catch(MOADNSException &mde) {
g_stats.clientParseError++;
if(g_logCommonErrors)
- L<<Logger::Error<<"Unable to parse packet from TCP client "<< conn->d_remote.toString() <<endl;
+ L<<Logger::Error<<"Unable to parse packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
return;
}
dc->d_tcpConnection = conn; // carry the torch
dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
dc->d_tcp=true;
- dc->setRemote(&conn->d_remote);
+ dc->setRemote(conn->d_remote);
+ dc->setSource(conn->d_remote);
ComboAddress dest;
memset(&dest, 0, sizeof(dest));
dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
socklen_t len = dest.getSocklen();
getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
dc->setLocal(dest);
+ dc->setDestination(dest);
DNSName qname;
uint16_t qtype=0;
uint16_t qclass=0;
bool needECS = false;
+ bool needXPF = g_XPFAcl.match(conn->d_remote);
string requestorId;
string deviceId;
#ifdef HAVE_PROTOBUF
}
#endif
- if(needECS || (t_pdl && t_pdl->d_gettag)) {
+ if(needECS || needXPF || (t_pdl && t_pdl->d_gettag)) {
try {
std::map<uint16_t, EDNSOptionView> ednsOptions;
+ bool xpfFound = false;
dc->d_ecsParsed = true;
- dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+ dc->d_ecsFound = false;
+ getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass,
+ dc->d_ecsFound, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
+ xpfFound, needXPF ? &dc->d_source : nullptr, needXPF ? &dc->d_destination : nullptr);
if(t_pdl && t_pdl->d_gettag) {
try {
- dc->d_tag = t_pdl->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId);
+ dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId);
}
catch(std::exception& e) {
if(g_logCommonErrors)
const struct dnsheader* dh = (const struct dnsheader*) conn->data;
if (!luaconfsLocal->protobufTaggedOnly) {
- protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId);
+ protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId);
}
}
catch(std::exception& e) {
if(dc->d_mdp.d_header.qr) {
delete dc;
g_stats.ignoredCount++;
- L<<Logger::Error<<"Ignoring answer from TCP client "<< conn->d_remote.toString() <<" on server socket!"<<endl;
+ L<<Logger::Error<<"Ignoring answer from TCP client "<< dc->getRemote() <<" on server socket!"<<endl;
return;
}
if(dc->d_mdp.d_header.opcode) {
delete dc;
g_stats.ignoredCount++;
- L<<Logger::Error<<"Ignoring non-query opcode from TCP client "<< conn->d_remote.toString() <<" on server socket!"<<endl;
+ L<<Logger::Error<<"Ignoring non-query opcode from TCP client "<< dc->getRemote() <<" on server socket!"<<endl;
return;
}
else {
unsigned int ctag=0;
uint32_t qhash = 0;
bool needECS = false;
+ bool needXPF = g_XPFAcl.match(fromaddr);
std::vector<std::string> policyTags;
LuaContext::LuaObject data;
+ ComboAddress source = fromaddr;
+ ComboAddress destination = destaddr;
string requestorId;
string deviceId;
#ifdef HAVE_PROTOBUF
*/
#endif
- if(needECS || (t_pdl && t_pdl->d_gettag)) {
+ if(needECS || needXPF || (t_pdl && t_pdl->d_gettag)) {
try {
std::map<uint16_t, EDNSOptionView> ednsOptions;
- ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+ bool xpfFound = false;
+
+ ecsFound = false;
+
+ getQNameAndSubnet(question, &qname, &qtype, &qclass,
+ ecsFound, &ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr,
+ xpfFound, needXPF ? &source : nullptr, needXPF ? &destination : nullptr);
+
qnameParsed = true;
ecsParsed = true;
if(t_pdl && t_pdl->d_gettag) {
try {
- ctag=t_pdl->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId);
+ ctag=t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId);
}
catch(std::exception& e) {
if(g_logCommonErrors)
#ifdef HAVE_PROTOBUF
if(luaconfsLocal->protobufServer) {
if (!luaconfsLocal->protobufTaggedOnly || !policyTags.empty()) {
- protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId);
+ protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId);
}
}
#endif /* HAVE_PROTOBUF */
if (cacheHit) {
#ifdef HAVE_PROTOBUF
if(luaconfsLocal->protobufServer && (!luaconfsLocal->protobufTaggedOnly || !pbMessage.getAppliedPolicy().empty() || !pbMessage.getPolicyTags().empty())) {
- Netmask requestorNM(fromaddr, fromaddr.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
const ComboAddress& requestor = requestorNM.getMaskedNetwork();
- pbMessage.update(uniqueId, &requestor, &destaddr, false, dh->id);
+ pbMessage.update(uniqueId, &requestor, &destination, false, dh->id);
pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
pbMessage.setRequestorId(requestorId);
}
#endif /* HAVE_PROTOBUF */
if(!g_quiet)
- L<<Logger::Notice<<t_id<< " question answered from packet cache tag="<<ctag<<" from "<<fromaddr.toString()<<endl;
+ L<<Logger::Notice<<t_id<< " question answered from packet cache tag="<<ctag<<" from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<endl;
g_stats.packetCacheHits++;
SyncRes::s_queries++;
addCMsgSrcAddr(&msgh, cbuf, &destaddr, 0);
}
if(sendmsg(fd, &msgh, 0) < 0 && g_logCommonErrors)
- L<<Logger::Warning<<"Sending UDP reply to client "<<fromaddr.toStringWithPort()<<" failed with: "<<strerror(errno)<<endl;
+ L<<Logger::Warning<<"Sending UDP reply to client "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<" failed with: "<<strerror(errno)<<endl;
if(response.length() >= sizeof(struct dnsheader)) {
struct dnsheader tmpdh;
memcpy(&tmpdh, response.c_str(), sizeof(tmpdh));
- updateResponseStats(tmpdh.rcode, fromaddr, response.length(), 0, 0);
+ updateResponseStats(tmpdh.rcode, source, response.length(), 0, 0);
}
g_stats.avgLatencyUsec=(1-1.0/g_latencyStatSize)*g_stats.avgLatencyUsec + 0.0; // we assume 0 usec
g_stats.avgLatencyOursUsec=(1-1.0/g_latencyStatSize)*g_stats.avgLatencyOursUsec + 0.0; // we assume 0 usec
}
if(t_pdl) {
- if(t_pdl->ipfilter(fromaddr, destaddr, *dh)) {
+ if(t_pdl->ipfilter(source, destination, *dh)) {
if(!g_quiet)
- L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<" based on policy"<<endl;
+ L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<" based on policy"<<endl;
g_stats.policyDrops++;
return 0;
}
if(MT->numProcesses() > g_maxMThreads) {
if(!g_quiet)
- L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<", over capacity"<<endl;
+ L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<source.toStringWithPort()<<(source != fromaddr ? " (via "+fromaddr.toStringWithPort()+")" : "")<<", over capacity"<<endl;
g_stats.overCapacityDrops++;
return 0;
dc->d_tag=ctag;
dc->d_qhash=qhash;
dc->d_query = question;
- dc->setRemote(&fromaddr);
+ dc->setRemote(fromaddr);
+ dc->setSource(source);
dc->setLocal(destaddr);
+ dc->setDestination(destination);
dc->d_tcp=false;
dc->d_policyTags = policyTags;
dc->d_data = data;
SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
+ g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
+
g_networkTimeoutMsec = ::arg().asNum("network-timeout");
g_initialDomainMap = parseAuthAndForwards();
for(expired_t::iterator i=expired.begin() ; i != expired.end(); ++i) {
shared_ptr<TCPConnection> conn=any_cast<shared_ptr<TCPConnection> >(i->second);
if(g_logCommonErrors)
- L<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toString() <<endl;
+ L<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toStringWithPort() <<endl;
t_fdm->removeReadFD(i->first);
}
}
::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level")="no";
+ ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
+
::arg().setCmd("help","Provide a helpful message");
::arg().setCmd("version","Print version string");
::arg().setCmd("config","Output blank configuration");
webserver.cc webserver.hh \
ws-api.cc ws-api.hh \
ws-recursor.cc ws-recursor.hh \
+ xpf.cc xpf.hh \
zoneparser-tng.cc zoneparser-tng.hh
if !HAVE_LUA_HPP
test-signers.cc \
test-syncres_cc.cc \
test-tsig.cc \
+ test-xpf_cc.cc \
testrunner.cc \
tsigverifier.cc tsigverifier.hh \
unix_utility.cc \
validate.cc validate.hh \
validate-recursor.cc validate-recursor.hh \
+ xpf.cc xpf.hh \
zoneparser-tng.cc zoneparser-tng.hh
testrunner_LDFLAGS = \
- Default: yes
If a PID file should be written to `socket-dir`_
+
+``xpf-allow-from``
+-------------
+.. versionadded:: 4.1.0
+
+- IP ranges, separated by commas
+- Default: empty
+
+This is an experimental implementation of `draft-bellis-dnsop-xpf`.
+The server will trust XPF records found in queries sent from those netmasks (both IPv4 and IPv6),
+and will adjust queries' source and destination accordingly. This is especially useful when the recursor
+is placed behind a proxy like dnsdist.
+Note that the `allow-from`_ setting is still applied to the original source address, and thus access restriction
+should be done on the proxy.
#include "ednssubnet.hh"
#include "iputils.hh"
-/* extract a specific EDNS0 option from a pointer on the beginning rdLen of the OPT RR */
-int getEDNSOption(char* optRR, size_t len, uint16_t wantedOption, char ** optionValue, size_t * optionValueSize);
-
BOOST_AUTO_TEST_SUITE(ednsoptions_cc)
static void getRawQueryWithECSAndCookie(const DNSName& name, const Netmask& ecs, const std::string& clientCookie, const std::string& serverCookie, std::vector<uint8_t>& query)
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+
+#include "xpf.hh"
+
+BOOST_AUTO_TEST_SUITE(xpf_cc)
+
+BOOST_AUTO_TEST_CASE(test_generateXPFPayload) {
+
+ /* Mixing v4 with v6 should throw */
+ BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("192.0.2.1"), ComboAddress("2001:db8::1")), std::runtime_error);
+ BOOST_CHECK_THROW(generateXPFPayload(false, ComboAddress("2001:db8::1"), ComboAddress("192.0.2.1")), std::runtime_error);
+
+ {
+ /* v4 payload over UDP */
+ ComboAddress source("192.0.2.1:53");
+ ComboAddress destination("192.0.2.2:65535");
+
+ auto payload = generateXPFPayload(false, source, destination);
+ BOOST_CHECK_EQUAL(payload.size(), 14);
+ BOOST_CHECK_EQUAL(payload.at(0), 4);
+ BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+ BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+ BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+ }
+
+ {
+ /* v4 payload over TCP */
+ ComboAddress source("192.0.2.1:53");
+ ComboAddress destination("192.0.2.2:65535");
+
+ auto payload = generateXPFPayload(true, source, destination);
+ BOOST_CHECK_EQUAL(payload.size(), 14);
+ BOOST_CHECK_EQUAL(payload.at(0), 4);
+ BOOST_CHECK_EQUAL(payload.at(1), 6);
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+ BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+ BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+ }
+
+ {
+ /* v6 payload over UDP */
+ ComboAddress source("[2001:db8::1]:42");
+ ComboAddress destination("[::1]:65535");
+
+ auto payload = generateXPFPayload(false, source, destination);
+ BOOST_CHECK_EQUAL(payload.size(), 38);
+ BOOST_CHECK_EQUAL(payload.at(0), 6);
+ BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+ BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+ BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+ }
+
+ {
+ /* v6 payload over TCP */
+ ComboAddress source("[2001:db8::1]:42");
+ ComboAddress destination("[::1]:65535");
+
+ auto payload = generateXPFPayload(true, source, destination);
+ BOOST_CHECK_EQUAL(payload.size(), 38);
+ BOOST_CHECK_EQUAL(payload.at(0), 6);
+ BOOST_CHECK_EQUAL(payload.at(1), 6);
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination));
+ BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+ BOOST_CHECK_EQUAL(parsedDestination.toStringWithPort(), destination.toStringWithPort());
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_parseXPFPayload) {
+
+ /* invalid sizes */
+ {
+ ComboAddress source;
+ ComboAddress destination;
+
+ BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 0, source, &destination), false);
+ BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 13, source, &destination), false);
+ BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 15, source, &destination), false);
+ BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 37, source, &destination), false);
+ BOOST_CHECK_EQUAL(parseXPFPayload(nullptr, 39, source, &destination), false);
+ }
+
+
+ {
+ /* invalid protocol */
+ ComboAddress source("[2001:db8::1]:42");
+ ComboAddress destination("[::1]:65535");
+
+ auto payload = generateXPFPayload(true, source, destination);
+ /* set protocol to 0 */
+ payload.at(1) = 0;
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+ }
+
+ {
+ /* invalid version */
+ ComboAddress source("[2001:db8::1]:42");
+ ComboAddress destination("[::1]:65535");
+
+ auto payload = generateXPFPayload(true, source, destination);
+ /* set version to 0 */
+ payload.at(0) = 0;
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+ }
+
+ {
+ /* payload too short (v6 size with v4 payload) */
+ ComboAddress source("192.0.2.1:53");
+ ComboAddress destination("192.0.2.2:65535");
+
+
+ auto payload = generateXPFPayload(true, source, destination);
+ /* set version to 6 */
+ payload.at(0) = 6;
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+ }
+
+ {
+ /* payload too long (v6 size with v4 payload) */
+ ComboAddress source("[2001:db8::1]:42");
+ ComboAddress destination("[::1]:65535");
+
+
+ auto payload = generateXPFPayload(true, source, destination);
+ /* set version to 4 */
+ payload.at(0) = 4;
+
+ ComboAddress parsedSource;
+ ComboAddress parsedDestination;
+ BOOST_CHECK_EQUAL(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, &parsedDestination), false);
+ }
+
+ {
+ /* v4 payload over UDP */
+ ComboAddress source("192.0.2.1:53");
+ ComboAddress destination("192.0.2.2:65535");
+
+ auto payload = generateXPFPayload(false, source, destination);
+ BOOST_CHECK_EQUAL(payload.size(), 14);
+ BOOST_CHECK_EQUAL(payload.at(0), 4);
+ BOOST_CHECK_EQUAL(payload.at(1), 17);
+
+ ComboAddress parsedSource;
+ BOOST_CHECK(parseXPFPayload(payload.c_str(), payload.size(), parsedSource, nullptr));
+ BOOST_CHECK_EQUAL(parsedSource.toStringWithPort(), source.toStringWithPort());
+ }
+
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+../xpf.cc
\ No newline at end of file
--- /dev/null
+../xpf.hh
\ No newline at end of file
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program 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 "xpf.hh"
+
+std::string generateXPFPayload(bool tcp, const ComboAddress& remote, const ComboAddress& local)
+{
+ if (remote.sin4.sin_family != local.sin4.sin_family) {
+ throw std::runtime_error("The XPF local and remote addresses must be of the same family");
+ }
+
+ std::string ret;
+ const uint8_t version = remote.isIPv4() ? 4 : 6;
+ const uint8_t protocol = tcp ? 6 : 17;
+ const size_t addrSize = remote.isIPv4() ? sizeof(remote.sin4.sin_addr.s_addr) : sizeof(remote.sin6.sin6_addr.s6_addr);
+ const uint16_t remotePort = remote.sin4.sin_port;
+ const uint16_t localPort = local.sin4.sin_port;
+
+ ret.reserve(sizeof(version) + sizeof(protocol) + (addrSize * 2) + sizeof(remotePort) + sizeof(localPort));
+
+ ret.append(reinterpret_cast<const char*>(&version), sizeof(version));
+ ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
+
+ if (remote.isIPv4()) {
+ assert(addrSize == sizeof(remote.sin4.sin_addr.s_addr));
+ ret.append(reinterpret_cast<const char*>(&remote.sin4.sin_addr.s_addr), addrSize);
+ }
+ else {
+ assert(addrSize == sizeof(remote.sin6.sin6_addr.s6_addr));
+ ret.append(reinterpret_cast<const char*>(&remote.sin6.sin6_addr.s6_addr), addrSize);
+ }
+
+ if (remote.isIPv4()) {
+ assert(addrSize == sizeof(local.sin4.sin_addr.s_addr));
+ ret.append(reinterpret_cast<const char*>(&local.sin4.sin_addr.s_addr), addrSize);
+ }
+ else {
+ assert(addrSize == sizeof(local.sin6.sin6_addr.s6_addr));
+ ret.append(reinterpret_cast<const char*>(&local.sin6.sin6_addr.s6_addr), addrSize);
+ }
+
+ ret.append(reinterpret_cast<const char*>(&remotePort), sizeof(remotePort));
+ ret.append(reinterpret_cast<const char*>(&localPort), sizeof(localPort));
+
+ return ret;
+}
+
+bool parseXPFPayload(const char* payload, size_t len, ComboAddress& source, ComboAddress* destination)
+{
+ static const size_t addr4Size = sizeof(source.sin4.sin_addr.s_addr);
+ static const size_t addr6Size = sizeof(source.sin6.sin6_addr.s6_addr);
+ uint8_t version;
+ uint8_t protocol;
+ uint16_t sourcePort;
+ uint16_t destinationPort;
+
+ if (len != (sizeof(version) + sizeof(protocol) + (addr4Size * 2) + sizeof(sourcePort) + sizeof(destinationPort)) &&
+ len != (sizeof(version) + sizeof(protocol) + (addr6Size * 2) + sizeof(sourcePort) + sizeof(destinationPort))) {
+ return false;
+ }
+
+ size_t pos = 0;
+
+ memcpy(&version, payload + pos, sizeof(version));
+ pos += sizeof(version);
+
+ if (version != 4 && version != 6) {
+ return false;
+ }
+
+ memcpy(&protocol, payload + pos, sizeof(protocol));
+ pos += sizeof(protocol);
+
+ if (protocol != 6 && protocol != 17) {
+ return false;
+ }
+
+ const size_t addrSize = version == 4 ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr);
+ if (len - pos != ((addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort))) {
+ return false;
+ }
+
+ source = makeComboAddressFromRaw(version, payload + pos, addrSize);
+ pos += addrSize;
+ if (destination != nullptr) {
+ *destination = makeComboAddressFromRaw(version, payload + pos, addrSize);
+ }
+ pos += addrSize;
+
+ memcpy(&sourcePort, payload + pos, sizeof(sourcePort));
+ pos += sizeof(sourcePort);
+ source.sin4.sin_port = sourcePort;
+
+ memcpy(&destinationPort, payload + pos, sizeof(destinationPort));
+ pos += sizeof(destinationPort);
+ if (destination != nullptr) {
+ destination->sin4.sin_port = destinationPort;
+ }
+
+ return true;
+}
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program 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.
+ */
+
+#pragma once
+
+#include <iputils.hh>
+
+std::string generateXPFPayload(bool tcp, const ComboAddress& remote, const ComboAddress& local);
+bool parseXPFPayload(const char* payload, size_t len, ComboAddress& source, ComboAddress* destination);
+
--- /dev/null
+#!/usr/bin/env python
+
+import dns
+from dnsdisttests import DNSDistTest
+
+class XPFTest(DNSDistTest):
+ """
+ dnsdist is configured to add XPF to the query
+ """
+
+ _xpfCode = 65422
+ _config_template = """
+ newServer{address="127.0.0.1:%s", addXPF=true}
+ """
+
+ def checkMessageHasXPF(self, msg, expectedValue):
+ self.assertGreaterEqual(len(msg.additional), 1)
+
+ found = False
+ for add in msg.additional:
+ if add.rdtype == self._xpfCode:
+ found = True
+ self.assertEquals(add.rdclass, dns.rdataclass.IN)
+ self.assertEquals(add.ttl, 0)
+ xpfData = add.to_rdataset()[0].to_text()
+ # skip the ports
+ self.assertEquals(xpfData[:26], expectedValue[:26])
+
+ self.assertTrue(found)
+
+ def testXPF(self):
+ """
+ XPF
+ """
+ name = 'xpf.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+ # 0x04 is IPv4, 0x11 (17) is UDP then 127.0.0.1 as source and destination
+ # and finally the ports, zeroed because we have no way to know them beforehand
+ xpfData = "\# 14 04117f0000017f00000100000000"
+ rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
+ rrset = dns.rrset.from_rdata(name, 60, rdata)
+ expectedQuery.additional.append(rrset)
+
+ response = dns.message.make_response(expectedQuery)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ receivedResponse.id = response.id
+
+ self.assertEquals(receivedQuery, expectedQuery)
+ self.checkMessageHasXPF(receivedQuery, xpfData)
+ self.assertEquals(response, receivedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+ # 0x04 is IPv4, 0x06 (6) is TCP then 127.0.0.1 as source and destination
+ # and finally the ports, zeroed because we have no way to know them beforehand
+ xpfData = "\# 14 04067f0000017f00000100000000"
+ rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
+ rrset = dns.rrset.from_rdata(name, 60, rdata)
+ expectedQuery.additional.append(rrset)
+
+ response = dns.message.make_response(expectedQuery)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ receivedResponse.id = response.id
+
+ self.assertEquals(receivedQuery, expectedQuery)
+ self.checkMessageHasXPF(receivedQuery, xpfData)
+ self.assertEquals(response, receivedResponse)