Netgate Discussion Forum
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Search
    • Register
    • Login

    Unbound (DNS Resolver) and Python module How-to

    DHCP and DNS
    1
    1
    4.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • dragoangelD
      dragoangel
      last edited by dragoangel

      Hi, want to share how Python module can be used with Undound.
      Unbound support only one python module at once.
      In my case I was need 2 tasks in one module:

      • return loopback (IPv6 ::1 and IPv4 127.0.0.1) for some TLDs
      • remove AAAA (IPv6) records from resolving for some domains (and all their subdomains) to workaround issues related to their broken realization of IPv6 stack

      So I googled for some examples at Internet and with basic knowledge of python I ended in with this:

      def init(id, cfg):
          return True
      
      def deinit(id):
          return True
      
      def inform_super(id, qstate, superqstate, qdata):
          return True
      
      localhost_domains = [
          "localdomain.",
          "localtest.me.",
          "lvh.me."
      ]
      
      ignore_aaaa_domains = [
          "lync.com.",
          "netflix.com.",
          "nflxso.net."
      ]
      
      def operate(id, event, qstate, qdata):
          if event == MODULE_EVENT_NEW or event == MODULE_EVENT_PASS:
      
              if qstate.qinfo.qname_str.endswith(tuple(localhost_domains)):
                  if (qstate.qinfo.qtype == RR_TYPE_AAAA):
                      msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_AAAA, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA)
                      msg.answer.append("%s 10 IN AAAA ::1" % qstate.qinfo.qname_str)
                  if (qstate.qinfo.qtype == RR_TYPE_A):
                      msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA)
                      msg.answer.append("%s 10 IN A 127.0.0.1" % qstate.qinfo.qname_str)
                  if (qstate.qinfo.qtype == RR_TYPE_ANY):
                      msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_AAAA, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA)
                      msg.answer.append("%s 10 IN AAAA ::1" % qstate.qinfo.qname_str)
                      msg.answer.append("%s 10 IN A 127.0.0.1" % qstate.qinfo.qname_str)
                  if not msg.set_return_msg(qstate):
                      qstate.ext_state[id] = MODULE_ERROR
                      return True
                  # We don't need validation, result is valid
                  qstate.return_msg.rep.security = 2
                  qstate.return_rcode = RCODE_NOERROR
                  qstate.ext_state[id] = MODULE_FINISHED
                  log_info("mod.py: returned localhost for %s" % qstate.qinfo.qname_str)
                  return True
      
              if qstate.qinfo.qname_str.endswith(tuple(ignore_aaaa_domains)):
                  if (qstate.qinfo.qtype == RR_TYPE_AAAA):
                      msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA)
                      if not msg.set_return_msg(qstate):
                          qstate.ext_state[id] = MODULE_ERROR
                          return True
                      # We don't need validation, result is valid
                      qstate.return_msg.rep.security = 2
                      qstate.return_rcode = RCODE_NOERROR
                      qstate.ext_state[id] = MODULE_FINISHED
                      log_info("mod.py: removed AAAA record from %s" % qstate.qinfo.qname_str)
                      return True
      
              qstate.ext_state[id] = MODULE_WAIT_MODULE
              return True
      
          if event == MODULE_EVENT_MODDONE:
              qstate.ext_state[id] = MODULE_FINISHED
              return True
      
          log_err("mod.py: error in module occured")
          qstate.ext_state[id] = MODULE_ERROR
          return True
      
      log_info("mod.py: script loaded")
      

      If you know how exchange it - critics are welcome.

      To not broke all in case of full-wipe-restore I use Filer plugin, more: after change of this file in this plugin - Unbound will automatically reload by itself via start script command, config:
      File: /var/unbound/mod.py
      Description: Unbound python module
      Permissions: 644
      File Contents: python script above, modify domains list to your needs, and not forget . at end of each domain
      Script/Command: unbound-control -c /var/unbound/unbound.conf reload
      Execute mode: Background (default)

      Another examples can be found at https://github.com/NLnetLabs/unbound/tree/master/pythonmod/examples

      Latest stable pfSense on 2x XG-7100 and 1x Intel Xeon Server, running mutiWAN, he.net IPv6, pfBlockerNG-devel, HAProxy-devel, Syslog-ng, Zabbix-agent, OpenVPN, IPsec site-to-site, DNS-over-TLS...
      Unifi AP-AC-LR with EAP RADIUS, US-24

      1 Reply Last reply Reply Quote 1
      • First post
        Last post
      Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.