[PYTHON] Create your own DNS server with Twisted

Introduction

Have you ever thought "I want to make my own DNS server"? ʻApt-get install bind9`, not a DNS server program.

"That's not necessary, you need to create your own block access to unnecessary resources on web pages or work productivity It's about the time when you can't access the site that drops the data. "

However, if you could control the DNS response freely with Python, you could do something more interesting.

Create a DNS server with Twisted

Suddenly I was curious about how to make my own DNS server with Python.

There is a method to build a DNS server using Twisted in the article found on Qiita. http://qiita.com/futoase/items/174883d7fa8d2dd4240a

After reviewing the latest documentation, I found that it was easier than I expected to create a custom DNS server that dynamically assembles responses.

http://twistedmatrix.com/documents/current/names/howto/custom-server.html

This is fantastic! Your dreams will spread!

What can it be used for

Since the response contents can be assembled with Python code, it seems that various interesting things can be done.

For example, Retty created its own DNS server to make it easier to switch to a test environment. By combining with the wireless LAN settings, the same request as the production can be sent to the test environment just by connecting to a specific access point.

In the corporate network, the DNS server for the test environment created by Twisted is running, and when you inquire about the host name of the production environment, the IP address of the result of inquiring the host name of the test environment is returned. By using a wireless LAN that supports VLANs and multiple ESSIDs, clients connected to a specific access point name will automatically query the DNS server for the test environment. As a result, even if the request is for the same URL as the production environment, if you contact the DNS server for the test environment, you will be directed to the test environment. This is basically DNS Spoo ... Oh, it looks like someone has come.

In addition, it may be possible to link with other services such as AWS to create a DNS server that returns an appropriate response according to the configuration and status of the service.

Let's do it right away

So let's create a DNS server that works with the AWS API.

If you ask for the ID of an EC2 instance like ʻi-abcd1234.ec2.local`, try to return the IP address assigned to the instance.

Package preparation

After making Python 2 and pip available, put Twisted in. As of Twisted-15.5.0, it didn't work because some of the DNS server related code didn't seem to support Python 3.

[user@localhost]$ pip2 install twisted

In this example, we will access AWS, so install Boto 3 and the AWS CLI and set the credentials.

[user@localhost]$ pip2 install boto3 awscli
[user@localhost]$ aws configure

The following samples have been confirmed to work with Python-2.7.10, Twisted-15.5.0 and boto3-1.2.3.

Make a resolver

According to Twisted Documents (http://twistedmatrix.com/documents/current/names/howto/custom-server.html#a-server-which-computes-responses-dynamically), dynamic name resolution It seems that the quickest way to achieve this is to create and register a custom resolver. It's very easy because Twisted handles the protocol processing.

So, in response to a query to the ʻec2.local` domain, we will consider the first label as the EC2 instance ID and create a resolver that responds with that IP address.

You can do this by inheriting twisted.names.common.ResolverBase and overridinglookupAddress ().

resolver.py


# coding=utf-8

import boto3
import botocore
from twisted.internet import defer
from twisted.logger import Logger
from twisted.names import common, dns, error

log = Logger()

ec2 = boto3.resource('ec2')


def find_ec2_address(instance_id):
    if instance_id:
        try:
            instance = ec2.Instance(instance_id)
            #Instance if you need a public IP address.public_ip_Can be obtained from address.
            address = instance.private_ip_address
            log.debug('Found {instance!r} address={address}', instance=instance, address=address)
            return address
        except botocore.exceptions.ClientError:
            log.failure('Failed to find {instance_id}', instance_id=instance_id)


class EC2ResourceResolver(common.ResolverBase):
    #Domain to be dynamically resolved
    _suffix = '.ec2.local'
    _ttl = 30

    def _should_resolve(self, name):
        return name.lower().endswith(self._suffix)

    def lookupAddress(self, name, timeout=None):
        log.debug('Looking up address {name}', name=name)

        if self._should_resolve(name):
            #Consider the first label as the EC2 instance ID.
            instance_id = name.split('.', 1)[0]
            address = find_ec2_address(instance_id)

            answer, authority, additional = common.EMPTY_RESULT
            if address:
                answer = [
                    dns.RRHeader(
                            name=name,
                            ttl=self._ttl,
                            payload=dns.Record_A(address=b'%s' % (address,), ttl=self._ttl),
                            auth=True)
                ]

            return defer.succeed((answer, authority, additional))

        # error.DomainError()If you fail with, it will contact the next resolver.
        return defer.fail(error.DomainError(name))

    lookupAllRecords = lookupAddress

    #To avoid repeated queries to other resolvers
    #Other query types are also resolved as empty results in the target domain.
    def _lookup(self, name, cls, type, timeout):
        if self._should_resolve(name):
            return defer.succeed(common.EMPTY_RESULT)
        return defer.fail(error.DomainError(name))

Oops, Twisted is an asynchronous programming framework, but it's blocked by calling boto ... I'm worried that it's not well-behaved, but this time it's a sample, so let's move on.

Make the server body

Next, create a DNS cache server that uses this resolver for name resolution.

ec2-dns-server.py


#!/usr/bin/env python2
# coding=utf-8

import sys

from twisted.internet import reactor
from twisted.names import client, dns, server
from twisted.python import log

from resolver import EC2ResourceResolver


def main():
    log.startLogging(sys.stderr)

    factory = server.DNSServerFactory(
        clients=[
            EC2ResourceResolver(),
            client.Resolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)])
        ]
    )
    protocol = dns.DNSDatagramProtocol(factory)

    reactor.listenUDP(10053, protocol)
    reactor.listenTCP(10053, factory)

    reactor.run()


if __name__ == '__main__':
    raise SystemExit(main())

In this example, it listens for DNS queries on port 10053 and relays unresolved queries to Google Public DNS (8.8.8.8, 8.8.4.4).

If you want to get this relay destination from /etc/resolv.conf

            client.Resolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)])

Part of

            client.Resolver(resolv='/etc/resolv.conf')

It is OK if you rewrite it to.

Try to move

Let's move it.

[user@localhost]$ python2 ec2-dns-server.py
2015-12-24 20:35:49+0900 [-] Log opened.
2015-12-24 20:35:49+0900 [-] DNSDatagramProtocol starting on 10053
2015-12-24 20:35:49+0900 [-] Starting protocol <twisted.names.dns.DNSDatagramProtocol object at 0x6fffe4bccd0>
2015-12-24 20:35:49+0900 [-] DNSServerFactory starting on 10053
2015-12-24 20:35:49+0900 [-] Starting factory <twisted.names.server.DNSServerFactory instance at 0x6fffe4c61b8>

I will test it with dig to see if it works properly

[user@localhost]$ dig -p 10053 @localhost i-abcd1234.ec2.local +norecurse +short
10.11.25.25

If the EC2 instance address is returned like this, it is working properly.

The possibilities are endless

DNS may be an affordable service to use as a service discovery window, as AWS endpoints provide various DNS names (xxxx.amazonaws.com).

[RFC 2782](https: // www.) Used by Active Directory is a way to find the location of a service by abstract name using DNS. Aliase using an SRV record (ietf.org/rfc/rfc2782.txt) or, more simply, a CNAME as in RFC 2219 There seems to be a way. The method of assigning an alias with CNAME has the advantage of being highly versatile, and in fact, on AWS [Multi-AZ of RDS for Oracle Database](http://aws.typepad.com/aws_japan/2012/05/multi-az- option-for-amazon-rds-for-oracle-database.html) and Autodiscover ElastiCache Nodes (https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/UserGuide/AutoDiscovery.HowAutoDiscoveryWorks.html) ) Seems to be used.

However, even if you try to abstract the location of the service with CNAME, there are various restrictions with CNAME. For example, in a configuration where the environment changes dynamically by raising or lowering the instance, the problem of how to maintain and manage the correspondence to the real host remains.

So ["Convention over Configuration"](https://ja.wikipedia.org/wiki/%E8%A8%AD%E5%AE%9A%E3%82%88%E3%82%8A%E8%A6% If you give a resource such as an EC2 instance a name that is easy to handle from the program based on 8F% E7% B4% 84), you can create a DNS service that responds dynamically using the resource's meta data. That's right. Failover by excluding slaves that have replication delays such as MySQL (I can do that with HAProxy), more Your dreams may spread.

Recommended Posts

Create your own DNS server with Twisted
Create your own Composite Value with SQLAlchemy
Memo to create your own Box with Pepper's Python
Create your own Django middleware
Reinforcement learning 23 Create and use your own module with Colaboratory
Solve your own maze with Q-learning
How to create your own Transform
Create your own name resolution service
[Django] Create your own 403, 404, 500 error pages
Train UGATIT with your own dataset
Solve your own maze with DQN
Your own Twitter client made with Django
Create your own Linux commands in Python
Easily build a DNS server using Twisted
[Reinforcement learning] DQN with your own library
Create wordcloud from your tweet with python3
[LLDB] Create your own command in Python
To import your own module with jupyter
Create your first app with Django startproject
Publish your own Python library with Homebrew
Create your own virtual camera with Python + OpenCV and apply original effects
Try to make your own AWS-SDK with bash
Argument implementation (with code) in your own language
Make your own module quickly with setuptools (python)
Train Stanford NER Tagger with your own data
[Machine learning] Create a machine learning model by performing transfer learning with your own data set
Create a wheel of your own OpenCV module
Create a "Hello World" (HTTP) server with Tornado
Create an API server quickly with Python + Falcon
Make your own music player with Bottle0.13 + jPlayer2.5!
Steps to install your own library with pip
Flow of creating your own package with setup.py with python
Call your own C library with Go using cgo
Create your own Big Data in Python for validation
Create your own Random Dot Stereogram (RDS) in Python.
Write your own activation function with Pytorch (hard sigmoid)
Let's call your own C ++ library with Python (Preferences)
Define your own distance function with k-means of scikit-learn
[Blender × Python] Create your own function & summary so far
Create a home music server with Centos8 + Universal Media Server
Create a fake Minecraft server in Python with Quarry
Create games with Pygame
Proxy server with Docker
Create filter with scipy
DNS server in Python ....
Local server with python
Until you can install your own Python library with pip
Try sorting your own objects with priority queue in Python
Run the intellisense of your own python library with VScode.
Set up your own web server within your Pepper app project
Learn "x86 architecture learned with your own emulator" with Manjaro (Linux)
Try HeloWorld in your own language (with How to & code)