dnsmasq是openwrt一个重要的进程,里面提供了两个重要的功能。一个是dhcp server,给lan口使用的,另一个是dns功能,维护路由器的dns信息,而且支持ipv4和ipv6。
从/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=
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
在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
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++;
}
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
有时候发现其他网络都可以正常访问,就公司内网的网络范文不了,那就是域名劫持在作祟。
由于上级dns返回的地址是个私有局域网地址,所以被看作是一次域名劫持,从而丢弃了解析的结果。
所以我们只需要设置rebind protection = 0
,也就是反域名劫持保护关闭即可。
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
uci add dhcp host
uci set dhcp.@host[-1].name="example-host"
uci set dhcp.@host[-1].ip="192.168.1.230"
uci set dhcp.@host[-1].mac="00:a0:24:5a:33:69"
uci commit dhcp
/etc/init.d/dnsmasq restart
重启dnsmasq之后,我们可以看到/var/etc/dnsmasq.conf
里面多了如下信息
dhcp-host=be:67:76:30:3c:a6,192.168.1.230,example-host
这样设备重新连接的时候,就会直接分配该固定IP给设备。
这里面其实还隐含了一个功能,就是设备hostname的设置,设备一般会有自己的名字,比如Iphone\HUAWEI_P30等,这时候如果我们想给这个设备重命令,则设置该name字段即可。
uci add dhcp host
uci set dhcp.@host[-1].name="example-host"
uci set dhcp.@host[-1].mac="00:a0:24:5a:33:69"
uci commit dhcp
uci add dhcp domain
uci set dhcp.@domain[-1].name="example-host"
uci set dhcp.@domain[-1].mac="00:a0:24:5a:33:69"
uci commit dhcp
这是dhcp.leases里面的名称就变了
root@Openwrt:/# cat /tmp/dhcp.leases
1654886650 00:a0:24:5a:33:69 192.168.18.230 example-host 01:00:a0:24:5a:33:69
DNSmasq 可以设置不同的域名指定不同的 DNS 进行解析,修改 /etc/dnsmasq.conf 文件即可,若不对域名设置 DNS,则从上游 DNS 获取记录。
uci设置:
uci add_list dhcp.@dnsmasq[0].server=/taobao.com/114.114.114.114
uci add_list dhcp.@dnsmasq[0].server=/google.com/8.8.8.8
实际生效的/etc/dnsmasq.conf
#指定淘宝使用114 DNS进行解析
server=/taobao.com/114.114.114.114
#google指定8.8.8.8进行解析
server=/google.com/8.8.8.8
也可以对指定的域名进行解析,相当于就是本地 hosts 指向,可以利用这个功能实现广告屏蔽等效果。DNSmasq 也可以对域名进行泛解析,填写 *.moewah.com, 这样的格式即可。
uci设置:
uci add_list dhcp.@dnsmasq[0].address=/ad.youku.com/127.0.0.1
uci add_list dhcp.@dnsmasq[0].address=/ad.iqiyi.com/127.0.0.1
uci add_list dhcp.@dnsmasq[0].address=/*.moewah.com/127.0.0.1
实际生效的/etc/dnsmasq.conf
#将广告域名指向到127.0.0.1实现广告屏蔽
address=/ad.youku.com/127.0.0.1
address=/ad.iqiyi.com/127.0.0.1
#对moewah.com进行泛解析
address=/*.moewah.com/119.23.184.172
Linux自建DNS服务器:DNSmasq安装与配置:https://www.vvso.cn/xlbk/20396.html
DNSmasq详细解析及详细配置:http://www.taodudu.cc/news/show-3688136.html?action=onClick
https://openwrt.org/zh/docs/guide-user/base-system/dhcp
https://openwrt.org/zh/docs/guide-user/base-system/dhcp.dnsmasq
https://www.freesion.com/article/6254617201/