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

這是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

https://openwrt.org/zh/docs/guide-user/base-system/dhcp

https://openwrt.org/zh/docs/guide-user/base-system/dhcp.dnsmasq

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章