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.5710.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->namebuffdaemon->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

6、靜態分配地址(IP MAC綁定)

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

7、不同的域名指定不同的 DNS 進行解析

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

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/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容