在网络世界中,DNS(域名系统)在将人类友好的域名转换为计算机可以理解的IP地址方面发挥着关键作用。然而,设置和管理DNS服务器可能相当复杂,特别是当你需要一个既快速又灵活的解决方案时。这就是quick DNS的用武之地,它是一个轻量级且高效的DNS服务器,能够处理IPv4和IPv6查询,甚至可以在没有将地址绑定到接口的情况下工作。
概述
quick dns旨在提供一个非递归DNS服务器解决方案。它被设计为简单易设置和操作,即使在没有静态IP地址的环境中也能工作。服务器基于本地区域数据库响应DNS查询。
核心特性
- 支持IPv4和IPv6:能够处理IPv4和IPv6查询,使其成为现代网络的多功能选择。
- 非递归操作:它作为非递归服务器运行,意味着它不会将查询转发到其他DNS服务器,而是基于本地区域数据库响应。
- 可定制性:允许通过命令行参数进行定制,使管理员能够根据特定需求调整其行为。
- 效率:它旨在提供快速的DNS解析。
一个快速灵活的IPv4和IPv6非递归DNS服务器(C/C++代码实现)
int dns::build_error(const string &s)
{
err = "qdns::";
err += s;
if (errno) {
err += ": ";
err += strerror(errno);
}
return -1;
}
int dns::init(const map<string, string> &args)
{
if (args.count("laddr"))
io = new (nothrow) socket_provider();
else if (args.count("mon") > 0)
io = new (nothrow) usipp_provider();
if (!io)
return build_error("init: OOM");
if (io->init(args) < 0)
return build_error(string("init:") + io->why());
auto it = args.find("nxdomain");
if (it != args.end())
nxdomain = (strtoul(it->second.c_str(), NULL, 10) != 0);
if (args.count("resend") > 0)
resend = 1;
return 0;
}
int dns::loop()
{
if (!io)
return build_error("loop: no IO provider initialized");
string log = "";
int r = 0;
for (;;) {
client.pkt = "";
client.reply = "";
if (io->recv(client.pkt) < 0) {
cerr<<io->why()<<endl;
continue;
}
src = io->sender();
r = parse_packet(client.pkt, client.reply, log);
if (r == 0) {
// 返回0的回复等于重新发送的pkt
if (io->resend(client.reply) < 0) {
cerr<<src<<": "<<io->why()<<endl;
continue;
}
} else if (r > 0) {
if (io->reply(client.reply) < 0) {
cerr<<src<<": "<<io->why()<<endl;
continue;
}
} // in < 0 case, just log output
cout<<src<<": "<<log<<endl;
}
return 0;
}
int dns::parse_packet(const string &query, string &response, string &log)
{
using net_headers::dnshdr;
using net_headers::dns_type;
log = "invalid query";
response = "";
if (query.size() <= sizeof(dnshdr))
return -1;
const char *ptr = query.c_str(), *end_ptr = ptr + query.size();
dnshdr hdr;
memcpy(&hdr, query.c_str(), sizeof(dnshdr));
ptr += sizeof(dnshdr);
// dst端口53没有查询?
if (hdr.qr != 0 || hdr.opcode != 0)
return -1;
if (hdr.q_count != htons(1))
return -1;
// skip QNAME
auto qptr = ptr;
while (*ptr != 0 && ptr < end_ptr)
++ptr;
++ptr;
if (ptr + 2*sizeof(uint16_t) > end_ptr)
return -1;
uint16_t qtype = 0;
memcpy(&qtype, ptr, sizeof(qtype));
string qname = string(qptr, ptr - qptr);
string question = string(qptr, ptr + 2*sizeof(uint16_t) - qptr);
string fqdn = "";
if (qname2host(qname, fqdn) <= 0)
return -1;
switch (ntohs(qtype)) {
case dns_type::A:
log = "A? ";
break;
case dns_type::AAAA:
log = "AAAA? ";
break;
case dns_type::MX:
log = "MX? ";
break;
case dns_type::CNAME:
log = "CNAME? ";
break;
case dns_type::NS:
log = "NS? ";
break;
case dns_type::PTR:
log = "PTR? ";
break;
case dns_type::SRV:
log = "SRV? ";
break;
case dns_type::TXT:
log = "TXT? ";
break;
default:
char s[32];
snprintf(s, sizeof(s), "%d? ", ntohs(qtype));
log = s;
}
log += fqdn;
log += " -> ";
bool found_domain = 1;
auto it1 = exact_matches.find(make_pair(qname, qtype)), lit = it1;
if (lit == exact_matches.end()) {
string::size_type pos = string::npos, minpos = string::npos;
// 尝试找到最大的子字符串匹配
for (auto it2 = wild_matches.begin(); it2 != wild_matches.end(); ++it2) {
if ((pos = qname.find(it2->first.first)) == string::npos || it2->first.second != qtype)
continue;
if (pos < minpos && pos + it2->first.first.size() == qname.size()) {
minpos = pos;
lit = it2;
}
}
// 如果没有找到条目,NXDOMAIN
if (minpos == string::npos) {
found_domain = 0;
log += "NDXOMAIN ";
auto it3 = exact_matches.find(make_pair(string("\x9[forward]\0", 11), htons(dns_type::SOA)));
if (it3 != exact_matches.end())
lit = it3;
// if -R was given, we are firewalling router,
// so resend in case we cant resolve ourself
if (resend) {
log += "(resend)";
response = query;
return 0;
}
// NXDOMAIN answers prohibited (-X)
if (!nxdomain) {
log += "(nosend)";
return -1;
}
}
}
if (lit == exact_matches.end()) {
log += "no [forward], (nosend)";
return -1;
}
list<match *> &l = lit->second;
if (l.size() == 0) {
log += "NULL match. Missing -X?";
return -1;
}
match *m = l.front();
// TTL为1表示,只处理此客户端src一次
if (l.size() == 1 && m->ttl == htonl(1)) {
if (once.count(src) > 0) {
log += "(once, nosend)";
return -1;
}
once[src] = 1;
}
log += m->field;
// reply-hdr
dnshdr rhdr;
memcpy(&rhdr, &hdr, sizeof(hdr));
rhdr.qr = 1;
rhdr.aa = 0;
rhdr.tc = 0;
rhdr.ra = 0;
rhdr.unused = 0;
if (!found_domain)
rhdr.rcode = 3;
else
rhdr.rcode = 0;
rhdr.q_count = hdr.q_count;
rhdr.a_count = m->a_count;
rhdr.rra_count = m->rra_count;
rhdr.ad_count = m->ad_count;
response = string((char *)&rhdr, sizeof(rhdr));
response += question;
response += m->rr;
if (l.size() > 1) {
l.push_back(m);
l.pop_front();
}
return 1;
}
int dns::parse_zone(const string &file)
{
...
while (fgets(buf, sizeof(buf), f)) {
if (rr_kind == RR_KIND_MATCHING) {
link_rr = "";
dltype = 0;
}
memset(rr, 0, sizeof(rr));
rr_ptr = rr;
dname = "";
dlname = "";
ptr = buf;
while (*ptr == ' ' || *ptr == '\t')
++ptr;
if (*ptr == ';' || *ptr == '\n')
continue;
if (*ptr == '@') {
// wrong format? ignore!
if (sscanf(ptr + 1, "%255[^ \t]%*[ \t]%255[^ \t;\n]", name, ltype) != 2)
link_rr = "";
else {
link_rr = name;
rr_kind = RR_KIND_LINKING;
}
continue;
}
if (sscanf(ptr, "%255[^ \t]%*[ \t]%255[^ \t]%*[ \t]IN%*[ \t]%255[^ \t]%*[ \t]%255[^ \t;\n]", name, ttlb, type, field) != 4)
continue;
rr_kind = RR_KIND_MATCHING;
if (host2qname(name, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
// DNS type of current entry
if (strcasecmp(type, "A") == 0) {
dtype = htons(dns_type::A);
} else if (strcasecmp(type, "MX") == 0) {
dtype = htons(dns_type::MX);
} else if (strcasecmp(type, "AAAA") == 0) {
dtype = htons(dns_type::AAAA);
} else if (strcasecmp(type, "NS") == 0) {
dtype = htons(dns_type::NS);
} else if (strcasecmp(type, "CNAME") == 0) {
dtype = htons(dns_type::CNAME);
} else if (strcasecmp(type, "SOA") == 0) {
dtype = htons(dns_type::SOA);
} else if (strcasecmp(type, "SRV") == 0) {
dtype = htons(dns_type::SRV);
} else if (strcasecmp(type, "TXT") == 0) {
dtype = htons(dns_type::TXT);
} else if (strcasecmp(type, "PTR") == 0) {
dtype = htons(dns_type::PTR);
} else
continue;
ttl = htonl(strtoul(ttlb, NULL, 10));
match *m = nullptr;
// 如果链接到现有RR,则使用现有匹配
if (link_rr.size() > 0) {
if (host2qname(link_rr, dlname) <= 0)
continue;
// RR的DNS类型
if (strcasecmp(ltype, "A") == 0) {
dltype = htons(dns_type::A);
} else if (strcasecmp(ltype, "MX") == 0) {
dltype = htons(dns_type::MX);
} else if (strcasecmp(ltype, "AAAA") == 0) {
dltype = htons(dns_type::AAAA);
} else if (strcasecmp(ltype, "NS") == 0) {
dltype = htons(dns_type::NS);
} else if (strcasecmp(ltype, "CNAME") == 0) {
dltype = htons(dns_type::CNAME);
} else if (strcasecmp(ltype, "SOA") == 0) {
dltype = htons(dns_type::SOA);
} else if (strcasecmp(ltype, "SRV") == 0) {
dltype = htons(dns_type::SRV);
} else if (strcasecmp(ltype, "TXT") == 0) {
dltype = htons(dns_type::TXT);
} else if (strcasecmp(ltype, "PTR") == 0) {
dltype = htons(dns_type::PTR);
} else
continue;
if (exact_matches.count(make_pair(dlname, dltype)) > 0)
m = exact_matches.find(make_pair(dlname, dltype))->second.back();
else if (wild_matches.count(make_pair(dlname, dltype)) > 0)
m = wild_matches.find(make_pair(dlname, dltype))->second.back();
else
continue;
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
} else {
m = new match;
m->field = field;
if (name[0] == '*') {
off = 1;
if (name[1] == '.')
off = 2;
memmove(name, name + off, sizeof(name) - off);
m->mtype = QDNS_MATCH_WILD;
if (host2qname(name, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
// 通配符匹配前面的字节数错误
dname.erase(0, 1);
} else
m->mtype = QDNS_MATCH_EXACT;
memcpy(rr_ptr, &clbl, sizeof(clbl));
rr_ptr += sizeof(clbl);
m->fqdn = name;
// DNS encoded name
m->name = dname;
// TTL
m->ttl = ttl;
m->type = dtype;
m->a_count = 0;
m->ad_count = 0;
m->rra_count = 0;
}
uint16_t prt = 0;
switch (ntohs(dtype)) {
case dns_type::A:
in_addr in;
if (inet_pton(AF_INET, field, &in) != 1)
continue;
// construct RR as per RFC
rlen = htons(4);
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, &in, sizeof(in));
rr_ptr += sizeof(in);
if (dltype == htons(dns_type::SOA))
m->rr = string(rr, rr_ptr - rr) + m->rr;
else
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
case dns_type::MX:
if (host2qname(field, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
rlen = htons(dname.size() + sizeof(uint16_t));
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, &zero, sizeof(zero)); // preference
rr_ptr += sizeof(zero);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
if (dltype == htons(dns_type::SOA))
m->rr = string(rr, rr_ptr - rr) + m->rr;
else
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
case dns_type::AAAA:
in6_addr in6;
if (inet_pton(AF_INET6, field, &in6) != 1)
continue;
rlen = htons(sizeof(in6));
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, &in6, sizeof(in6));
rr_ptr += sizeof(in6);
if (dltype == htons(dns_type::SOA))
m->rr = string(rr, rr_ptr - rr) + m->rr;
else
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
case dns_type::NS:
if (host2qname(field, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
rlen = htons(dname.size());
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
if (dltype == htons(dns_type::SOA))
m->rr = string(rr, rr_ptr - rr) + m->rr;
else
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
case dns_type::CNAME:
m->type = dtype;
if (host2qname(field, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
rlen = htons(dname.size());
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
if (dltype == htons(dns_type::SOA))
m->rr = string(rr, rr_ptr - rr) + m->rr;
else
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
// Once a SOA has been linked in, no other RR's must be linked,
// as they must appear between answer and additional section
case dns_type::SOA:
m->type = dtype;
if (host2qname(field, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
rlen = htons(2*dname.size() + sizeof(soa_ints));
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
memcpy(rr_ptr, soa_ints, sizeof(soa_ints));
rr_ptr += sizeof(soa_ints);
m->rr += string(rr, rr_ptr - rr);
m->rra_count = htons(1);
break;
case dns_type::SRV:
m->type = dtype;
// avoid warning about unaligned &src.port access
if (sscanf(field, "%255[^:]:%hu:%hu:%hu", name, &prio, &weight, &prt) != 4)
continue;
srv.port = prt;
if (host2qname(name, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
srv.len = htons(dname.size() + 6);
srv.type = dtype;
srv._class = dclass;
srv.ttl = ttl;
srv.prio = htons(prio);
srv.weight = htons(weight);
srv.port = htons(srv.port);
memcpy(rr_ptr, &srv, sizeof(srv));
rr_ptr += sizeof(srv);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
case dns_type::TXT:
case dns_type::PTR:
m->type = dtype;
if (sscanf(field, "%255[^\n]", name) != 1)
continue;
if (host2qname(name, dname) <= 0)
continue;
if (dname.size() > 255)
continue;
rlen = htons(dname.size());
memcpy(rr_ptr, &dtype, sizeof(dtype));
rr_ptr += sizeof(dtype);
memcpy(rr_ptr, &dclass, sizeof(dclass));
rr_ptr += sizeof(dclass);
memcpy(rr_ptr, &ttl, sizeof(ttl));
rr_ptr += sizeof(ttl);
memcpy(rr_ptr, &rlen, sizeof(rlen));
rr_ptr += sizeof(rlen);
memcpy(rr_ptr, dname.c_str(), dname.size());
rr_ptr += dname.size();
m->rr += string(rr, rr_ptr - rr);
m->a_count += htons(1);
break;
default:
if (link_rr.size() == 0)
delete m;
continue;
}
// Only add new match if not linked to existing one
if (link_rr.size() == 0) {
if (m->mtype == QDNS_MATCH_EXACT)
exact_matches[make_pair(m->name, m->type)].push_back(m);
else
wild_matches[make_pair(m->name, m->type)].push_back(m);
}
++records;
}
fclose(f);
cout<<"Successfully loaded "<<records<<" Quantum-RR's.\n";
return 0;
}
int main(int argc, char **argv)
{
...
args["laddr"] = "0.0.0.0";
args["nxdomain"] = "1";
args["zone"] = "/dev/stdin";
while ((c = getopt(argc, argv, "l:p:M:6XRZ:f:")) != -1) {
switch (c) {
case 'f':
args["filter"] = string(optarg);
break;
case 'l':
args["laddr"] = string(optarg);
laddr_set = 1;
break;
case 'p':
args["lport"] = string(optarg);
break;
case 'M':
args["mon"] = string(optarg); // device
args.erase("laddr");
break;
case '6':
args["6"] = "1";
if (!laddr_set && args.count("mon") == 0)
args["laddr"] = "::";
break;
case 'R':
args["resend"] = "1";
break;
case 'X':
args["nxdomain"] = "0";
break;
case 'Z':
args["zone"] = string(optarg);
break;
default:
usage();
return 1;
}
}
dns::qdns *quantum_dns = new (nothrow) qdns::qdns();
if (quantum_dns->init(args) < 0) {
cerr<<quantum_dns->why()<<endl;
delete quantum_dns;
return -1;
}
if (quantum_dns->parse_zone(args["zone"]) < 0) {
cerr<<quantum_dns->why()<<endl;
delete quantum_dns;
return -1;
}
quantum_dns->loop();
delete quantum_dns;
return 0;
}
If you need the complete source code, please add the WeChat number (c17865354792)
代码的关键组件
代码的实现基于几个核心组件:
- 命令行参数解析:通过
getopt
函数解析命令行参数,设置服务器的工作模式。 - 区域数据库:使用区域文件(zonefile)来存储DNS记录,这些记录响应DNS查询。
- 网络接口抽象:通过
socket_provider
和usipp_provider
类抽象网络接口,支持普通的端口监听和特定设备的捕获模式。 - DNS查询处理:解析客户端发送的DNS查询,根据区域数据库构建响应。
- 响应发送:将构建好的DNS响应发送回客户端。
DNS的工作原理
DNS基于客户端-服务器模型运行,客户端发送查询以解析域名到IP地址,服务器响应请求的信息。quick dns实现了这一模型,注重效率和灵活性。
quick dns的实际应用
quick_dns 提供了多种命令行参数来配置其行为,以下是一些常用的参数:
- -Z zonefile:指定区域文件,默认为标准输入(stdin)。
- -X:如果区域文件中没有找到相应的资源记录(RR),不发送NXDOMAIN响应。
- -6:绑定到IPv6地址或在-M模式下使用IPv6捕获。
- -l local IPv4/6:绑定到指定的本地IP地址。
- -p local port(=53):绑定到指定的本地端口,默认为53。
- -M dev:在指定的设备上捕获,而不是监听端口,并响应非本机的查询。
- -R:在路由器上重传查询而不是发送NXDOMAIN,适用于具有两个NIC和DROP FORWARD策略的路由器。
示例1:基本启动
./quick_dns -l 0.0.0.0 -p 53 -Z baidu.com
这个命令将qdns绑定到本地地址0.0.0.0
(所有IPv4地址)和端口53
,并使用example.zone
作为区域文件。
示例2:使用IPv6
./quick_dns -6 -l :: -p 53 -Z baidu.com
这个命令将qdns绑定到本地IPv6地址::
和端口53
。
示例3:捕获模式
./quick_dns -M eth0 -Z baidu.com
这个命令将在网络设备eth0
上捕获DNS查询,并使用example.zone
作为区域文件。
示例4:不发送NXDOMAIN
./quick_dns -X -Z baidu.com
即使区域文件中没有找到相应的资源记录,这个命令也不会发送NXDOMAIN响应。
示例5:路由器重传查询
./quick_dns -R -Z baidu.com
这个命令在路由器上重传查询,适用于具有DROP FORWARD策略的网络环境。
结论
上面quick dns是一个功能强大的DNS服务器,它提供了灵活的配置选项和高效的查询处理能力。通过上述示例,你可以开始使用quick dns来管理你的DNS服务。无论是在本地网络还是复杂的路由器环境中,quick dns都能提供稳定的域名解析服务。
Welcome to follow WeChat official account【程序猿编码】