24-Openwrt dnsmasq
dnsmasq是openwrt一个重要的进程,里面提供了两个重要的功能。一个是dhcp server,给lan口使用的,另一个是dns功能,维护路由器的dns信息,而且支持ipv4和ipv6。
1、 dnsmasq启动过程
从/etc/init.d/dnsmasq start脚本启动
root@Openwrt:/# cat /etc/config/dhcp
config dnsmasq
option domainneeded '1'
option boguspriv '1'
option filterwin2k '0'
option localise_queries '1'
option rebind_protection '0'
option rebind_localhost '1'
option local '/lan/'
option domain 'lan'
option expandhosts '1'
option nonegcache '0'
option authoritative '1'
option readethers '1'
option leasefile '/tmp/dhcp.leases'
option resolvfile '/tmp/resolv.conf.auto'
option nonwildcard '1'
option localservice '1'
config dhcp 'lan'
option interface 'lan'
option start '100'
option limit '150'
option force '1'
option ignore '0'
option leasetime '12h'
start_service函数里面会读取/etc/config/dhcp和/etc/config/networek下面的配置文件,然后集成出一份新的配置文件/var/etc/dnsmasq.conf
,如下:
# auto-generated config file from /etc/config/dhcp
conf-file=/etc/dnsmasq.conf
dhcp-authoritative
domain-needed
log-queries
localise-queries
read-ethers
bogus-priv
expand-hosts
bind-interfaces
local-service
domain=lan
server=/lan/
dhcp-leasefile=/tmp/dhcp.leases
resolv-file=/tmp/resolv.conf.auto
addn-hosts=/tmp/hosts
conf-dir=/tmp/dnsmasq.d
dhcp-broadcast=tag:needs-broadcast
dhcp-range=lan,192.168.18.100,192.168.18.249,255.255.255.0,12h
比如uci里面添加了option logqueries 1
,那么就会在/var/etc/dnsmasq.conf
里面添加log-queries
,这时候c代码里面会解析。
{ "log-queries", 2, 0, 'q' },
{ "log-facility", 1, 0 ,'8' },
uci的所有配置在官网可以看到http://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
-q, --log-queries
Log the results of DNS queries handled by dnsmasq. Enable a full cache dump on receipt of SIGUSR1. If the argument "extra" is supplied, ie --log-queries=extra then the log has extra information at the start of each line. This consists of a serial number which ties together the log lines associated with an individual query, and the IP address of the requestor.
-8, --log-facility=<facility>
Set the facility to which dnsmasq will send syslog entries, this defaults to DAEMON, and to LOCAL0 when debug mode is in operation. If the facility given contains at least one '/' character, it is taken to be a filename, and dnsmasq logs to the given file, instead of syslog. If the facility is '-' then dnsmasq logs to stderr. (Errors whilst reading configuration will still go to syslog, but all output from a successful startup, and all output whilst running, will go exclusively to the file.) When logging to a file, dnsmasq will close and reopen the file when it receives SIGUSR2. This allows the log file to be rotated without stopping dnsmasq.
启动进程
root@openwrt:/# ps | grep dns
21019 nobody 1024 S /usr/sbin/dnsmasq -C /var/etc/dnsmasq.conf -k -x /var/run/dnsmasq/dnsmasq.pid
21241 root 1520 S grep dns
dnsmasq.c的main()函数启动,一系列的初始化后,最终在while(1)里面处理逻辑,如下:
while (1)
{
int t, timeout = -1;
poll_reset();
/* if we are out of resources, find how long we have to wait
for some to come free, we'll loop around then and restart
listening for queries */
if ((t = set_dns_listeners(now)) != 0)
timeout = t * 1000;
/* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
if (daemon->tftp_trans ||
(option_bool(OPT_DBUS) && !daemon->dbus))
timeout = 250;
/* Wake every second whilst waiting for DAD to complete */
else if (is_dad_listeners())
timeout = 1000;
#ifdef HAVE_DHCP
if (daemon->dhcp || daemon->relay4)
{
poll_listen(daemon->dhcpfd, POLLIN);
if (daemon->pxefd != -1)
poll_listen(daemon->pxefd, POLLIN);
}
#endif
#ifdef HAVE_INOTIFY
if (daemon->inotifyfd != -1)
poll_listen(daemon->inotifyfd, POLLIN);
#endif
#if defined(HAVE_LINUX_NETWORK)
poll_listen(daemon->netlinkfd, POLLIN);
#elif defined(HAVE_BSD_NETWORK)
poll_listen(daemon->routefd, POLLIN);
#endif
poll_listen(piperead, POLLIN);
#ifdef HAVE_SCRIPT
# ifdef HAVE_DHCP
while (helper_buf_empty() && do_script_run(now));
# endif
/* Refresh cache */
if (option_bool(OPT_SCRIPT_ARP))
find_mac(NULL, NULL, 0, now);
while (helper_buf_empty() && do_arp_script_run());
if (!helper_buf_empty())
poll_listen(daemon->helperfd, POLLOUT);
#endif
/* must do this just before select(), when we know no
more calls to my_syslog() can occur */
set_log_writer();
if (do_poll(timeout) < 0)
continue;
now = dnsmasq_time();
check_log_writer(0);
/* prime. */
enumerate_interfaces(1);
/* Check the interfaces to see if any have exited DAD state
and if so, bind the address. */
if (is_dad_listeners())
{
enumerate_interfaces(0);
/* NB, is_dad_listeners() == 1 --> we're binding interfaces */
create_bound_listeners(0);
warn_bound_listeners();
}
#if defined(HAVE_LINUX_NETWORK)
if (poll_check(daemon->netlinkfd, POLLIN))
netlink_multicast();
#elif defined(HAVE_BSD_NETWORK)
if (poll_check(daemon->routefd, POLLIN))
route_sock();
#endif
#ifdef HAVE_INOTIFY
if (daemon->inotifyfd != -1 && poll_check(daemon->inotifyfd, POLLIN) && inotify_check(now))
{
if (daemon->port != 0 && !option_bool(OPT_NO_POLL))
poll_resolv(1, 1, now);
}
#endif
if (poll_check(piperead, POLLIN))
async_event(piperead, now);
my_syslog(LOG_ERR, _("11111-check_dns_listeners"));
check_dns_listeners(now);
#ifdef HAVE_DHCP
if (daemon->dhcp || daemon->relay4)
{
if (poll_check(daemon->dhcpfd, POLLIN))
dhcp_packet(now, 0);
if (daemon->pxefd != -1 && poll_check(daemon->pxefd, POLLIN))
dhcp_packet(now, 1);
}
#endif
2、dns解析过程
在uci里面添加option logqueries 1
选项,重启dnsmasq,可以看到多出一些log,截取其中的一部分进行解析
query[A] www.baidu.com from 127.0.0.1
cached www.baidu.com is 220.181.38.148
query[AAAA] www.taobao.com from 127.0.0.1
forwarded www.taobao.com to 10.16.8.57
forwarded www.taobao.com to 10.16.8.206
query[AAAA] www.taobao.com.danuoyi.tbcache.com from 127.0.0.1
forwarded www.taobao.com.danuoyi.tbcache.com to 10.16.8.57
query[A] www.taobao.com from 127.0.0.1
forwarded www.taobao.com to 10.16.8.57
reply www.taobao.com is <CNAME>
reply www.taobao.com.danuoyi.tbcache.com is 103.15.99.90
reply www.taobao.com.danuoyi.tbcache.com is 103.15.99.91
第一部分为本地127.0.0.1请求www.baidu.com这个域名,这个域名在cache里面已经有了,所以直接从cache里面把结果返回。
第二部分为请求www.taobao.com的域名信息,这时候cache里面没有,所有就转达给dns服务器,使用ubus call network.interface.wan status
可以看到dns服务器就是上面的10.16.8.57
和10.16.8.206
这两个地址,然后得到reply返回值,得到淘宝的IP。
这两个地址是netifd在得到wan口的网关后,写入到resolv.conf.auto中,dnsmasq的配置项resolv-file=/tmp/resolv.conf.auto
在要转发的时候就根据这边的地址转发给上级
root@Openwrt:/# cat /tmp/resolv.conf.auto
# Interface wan
nameserver 202.96.128.86
nameserver 202.96.134.33
上面的流程是这样的,对应的代码在哪个函数里面
在main()函数的while里面,加了个打印,可以看到每次dns请求/回复都会经过check_dns_listeners()
函数
11111-check_dns_listeners
query[A] www.baidu.com from 127.0.0.1
forwarded www.baidu.com to 10.16.8.57
11111-check_dns_listeners
reply www.baidu.com is 140.143.178.227
check_dns_listeners()
函数里面做对应的判断,如果是请求的则调用
receive_query()
函数,在cache里面没有找到就调用forward_query()
函数转发到dns服务器,查到结果后就使用reply_query()
函数返回给对应的IP。
为了获取到哪个IP请求,请求的域名,在forward_query()函数里面打印,为daemon->namebuff
和daemon->addrbuff
。
if (errno == 0)
{
/* Keep info in case we want to re-send this packet */
daemon->srv_save = start;
daemon->packet_len = plen;
my_syslog(LOG_ERR, _("2222-namebuff:%s,addrbuff:%s\r\n"), daemon->namebuff,daemon->addrbuff);
if (!gotname)
strcpy(daemon->namebuff, "query");
if (start->addr.sa.sa_family == AF_INET)
log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff,
(struct all_addr *)&start->addr.in.sin_addr, NULL);
#ifdef HAVE_IPV6
else
log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff,
(struct all_addr *)&start->addr.in6.sin6_addr, NULL);
#endif
start->queries++;
forwarded = 1;
forward->sentto = start;
if (!forward->forwardall)
break;
forward->forwardall++;
}
3、DHCP请求过程
dhcp的请求过程也比较直观,DHCP请求4步,如下:
dnsmasq-dhcp[3294]: DHCPDISCOVER(br-lan) 30:ae:7b:e1:d3:8f
dnsmasq-dhcp[3294]: DHCPOFFER(br-lan) 192.168.18.150 30:ae:7b:e1:d3:8f
dnsmasq-dhcp[3294]: DHCPREQUEST(br-lan) 192.168.18.150 30:ae:7b:e1:d3:8f
dnsmasq-dhcp[3294]: DHCPACK(br-lan) 192.168.18.150 30:ae:7b:e1:d3:8f
这边的dhcp网段IP的获取范围就是上面/var/etc/dnsmasq.conf
里面的
dhcp-range=lan,192.168.18.100,192.168.18.249,255.255.255.0,12h
dhcp的设备列表都会被写入/tmp/dhcp.leases下
root@Openwrt:/# cat /tmp/dhcp.leases
1653726179 a0:a4:c5:1e:61:c1 192.168.18.127 LAPTOP-SFHGQM4K 01:a0:a4:c5:1e:61:c1
1653726146 30:ae:7b:e1:d3:8f 192.168.18.150 * 30:ae:7b:e1:d3:8f
4、rebind_protection域名劫持保护
有时候发现其他网络都可以正常访问,就公司内网的网络范文不了,那就是域名劫持在作祟。
由于上级dns返回的地址是个私有局域网地址,所以被看作是一次域名劫持,从而丢弃了解析的结果。
所以我们只需要设置rebind protection = 0
,也就是反域名劫持保护关闭即可。
5、默认网关域名
lan口的IP是可以一直变的,这样就会导致我们如果路由器丝印上面打印的是IP就会不准确,所以一般会打印一个域名,访问这个域名就会直接范文默认网关。
在/var/etc/dnsmasq.conf
里面有个配置
addn-hosts=/tmp/hosts
在/etc/init.d/dnsmasq启动脚本里面一般会有如下信息,就是将/etc/config/system里面的hostname内容,还有lan的默认网关地址,通过dhcp_domain_add
函数添加到//tmp/hosts/dhcp文件中。
# add own hostname
[ $ADD_LOCAL_HOSTNAME -eq 1 ] && {
local lanaddr lanaddr6
local ulaprefix="$(uci_get network @globals[0] ula_prefix)"
local hostname="$(uci_get system @system[0] hostname Lede)"
network_get_ipaddr lanaddr "lan" && {
dhcp_domain_add "" "$hostname" "$lanaddr"
}
[ -n "$ulaprefix" ] && network_get_ipaddrs6 lanaddr6 "lan" && {
for lanaddr6 in $lanaddr6; do
case "$lanaddr6" in
"${ulaprefix%%:/*}"*)
dhcp_domain_add "" "$hostname" "$lanaddr6"
;;
esac
done
}
}
所以我们只需要将uci里面的hostname改成我们想要的域名,不过这个方式不好,因为别的地方可能也会用到这个hostname的值
/etc/config/system
config system
option hostname 'test12345 test1234567'
所以我们最好自己再添加一个uci值,如ownhostname,然后把/etc/init.d/dnsmasq里面的uci_get改成ownhostname就可以了
config system
option ownhostname 'test12345 test1234567'
只有重启/etc/init.d/dnsmasq restart,可以看到 /tmp/hosts/dhcp下的内容变了
root@Openwrt:/# cat /tmp/hosts/dhcp
# auto-generated config file from /etc/config/dhcp
192.168.18.1 test12345 test1234567
ping测试下,获取用web访问测试下
C:\Users\lenovo>ping test12345.com
正在 Ping test12345.com [192.168.18.1] 具有 32 字节的数据:
来自 192.168.18.1 的回复: 字节=32 时间=1ms TTL=64
来自 192.168.18.1 的回复: 字节=32 时间=2ms TTL=64
来自 192.168.18.1 的回复: 字节=32 时间=2ms TTL=64
来自 192.168.18.1 的回复: 字节=32 时间=2ms TTL=64
其实还有一种改法,就是将我们需要的域名追加到/etc/hosts下面即可,这是linux默认的一种方式
root@Openwrt:/# cat /etc/hosts
127.0.0.1 localhost
192.168.18.1 test12345.com