├── CNAME ├── LICENSE.md ├── README.md ├── _config.yml ├── hc2.png ├── main.py └── microDNSSrv.py /CNAME: -------------------------------------------------------------------------------- 1 | microdnssrv.hc2.fr -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MicroDNSSrv is a micro DNS server for MicroPython to simply respond to A queries (principally used on ESP32 and [Pycom](http://www.pycom.io) modules) 2 | 3 | ![HC²](hc2.png "HC²") 4 | 5 | Very easy to integrate and very light with one file only : 6 | - `"microDNSSrv.py"` 7 | 8 | Simple but effective : 9 | - Use it to embed a fast DNS server in yours modules 10 | - Simply responds to A queries (only) 11 | - Use a list of multiple domains 12 | - Include wildcards in the scheme of names 13 | - Use it to make a captive portal simply 14 | 15 | ### Using *microDNSSrv* main class : 16 | 17 | | Name | Function | 18 | | - | - | 19 | | Constructor | `mds = MicroDNSSrv()` | 20 | | Start DNS server | `mds.Start()` | 21 | | Stop DNS server | `mds.Stop()` | 22 | | Check if DNS server is running | `mds.IsStarted()` | 23 | | Set the domain names list | `mds.SetDomainsList(domainsList)` | 24 | 25 | ### Basic example : 26 | ```python 27 | from microDNSSrv import MicroDNSSrv 28 | domainsList = { 29 | "test.com" : "1.1.1.1", 30 | "*test2.com" : "2.2.2.2", 31 | "*google*" : "192.168.4.1", 32 | "*.toto.com" : "192.168.4.1", 33 | "www.site.*" : "192.168.4.1" } 34 | mds = MicroDNSSrv(domainsList) 35 | if mds.Start() : 36 | print("MicroDNSSrv started.") 37 | else : 38 | print("Error to starts MicroDNSSrv...") 39 | ``` 40 | 41 | ### Using *microDNSSrv* speedly creation of the class : 42 | ```python 43 | from microDNSSrv import MicroDNSSrv 44 | if MicroDNSSrv.Create( { 45 | "test.com" : "1.1.1.1", 46 | "*test2.com" : "2.2.2.2", 47 | "*google*" : "192.168.4.1", 48 | "*.toto.com" : "192.168.4.1", 49 | "www.site.*" : "192.168.4.1" } ) : 50 | print("MicroDNSSrv started.") 51 | else : 52 | print("Error to starts MicroDNSSrv...") 53 | ``` 54 | 55 | ### Using for a captive portal : 56 | ```python 57 | MicroDNSSrv.Create({ '*' : '192.168.0.254' }) 58 | ``` 59 | - Can be used with [MicroWebSrv](http://microwebsrv.hc2.fr) easily. 60 | 61 | 62 | 63 | ### By JC`zic for [HC²](https://www.hc2.fr) ;') 64 | 65 | *Keep it simple, stupid* :+1: 66 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /hc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jczic/MicroDNSSrv/4cd90f6b258939979f32f5cf6e085356105f23e0/hc2.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | 2 | from network import WLAN 3 | from microDNSSrv import MicroDNSSrv 4 | 5 | # ---------------------------------------------------------------------------- 6 | 7 | print() 8 | print("=======================================================================") 9 | print() 10 | 11 | WLAN().init(mode=WLAN.AP, ssid='Test MicroDNSSrv', auth=(WLAN.WPA2, 'azerty123')) 12 | 13 | if MicroDNSSrv.Create( { 14 | "test.com" : "1.1.1.1", 15 | "*test2.com" : "2.2.2.2", 16 | "*google*" : "192.168.4.1", 17 | "*.toto.com" : "192.168.4.1", 18 | "www.site.*" : "192.168.4.1" } ) : 19 | print("MicroDNSSrv started.") 20 | else : 21 | print("Error to starts MicroDNSSrv...") 22 | 23 | print() 24 | print("=======================================================================") 25 | print() 26 | 27 | # ---------------------------------------------------------------------------- 28 | -------------------------------------------------------------------------------- /microDNSSrv.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr) 4 | """ 5 | 6 | from _thread import start_new_thread 7 | from re import match 8 | import socket 9 | import gc 10 | 11 | class MicroDNSSrv : 12 | 13 | # ============================================================================ 14 | # ===( Speed Creation )======================================================= 15 | # ============================================================================ 16 | 17 | def Create(domainsList) : 18 | mds = MicroDNSSrv() 19 | if mds.SetDomainsList(domainsList) and mds.Start() : 20 | return mds 21 | return None 22 | 23 | # ============================================================================ 24 | # ===( Utils )================================================================ 25 | # ============================================================================ 26 | 27 | def _tryStartThread(func, args=()) : 28 | for x in range(10) : 29 | try : 30 | gc.collect() 31 | start_new_thread(func, args) 32 | return True 33 | except : 34 | global _dns_thread_id 35 | try : 36 | _dns_thread_id += 1 37 | except : 38 | _dns_thread_id = 0 39 | try : 40 | start_new_thread('DNS_THREAD_%s' % _dns_thread_id, func, args) 41 | return True 42 | except : 43 | pass 44 | return False 45 | 46 | # ---------------------------------------------------------------------------- 47 | 48 | def _ipV4StrToBytes(ipStr) : 49 | try : 50 | parts = ipStr.split('.') 51 | if len(parts) == 4 : 52 | return bytes( [ int(parts[0]), 53 | int(parts[1]), 54 | int(parts[2]), 55 | int(parts[3]) ] ) 56 | except : 57 | pass 58 | return None 59 | 60 | # ---------------------------------------------------------------------------- 61 | 62 | def _getAskedDomainName(packet) : 63 | try : 64 | queryType = (packet[2] >> 3) & 15 65 | qCount = (packet[4] << 8) | packet[5] 66 | if queryType == 0 and qCount == 1 : 67 | pos = 12 68 | domName = '' 69 | while True : 70 | domPartLen = packet[pos] 71 | if (domPartLen == 0) : 72 | break 73 | domName += ('.' if len(domName) > 0 else '') \ 74 | + packet[ pos+1 : pos+1+domPartLen ].decode() 75 | pos += 1+domPartLen 76 | return domName 77 | except : 78 | pass 79 | return None 80 | 81 | # ---------------------------------------------------------------------------- 82 | 83 | def _getPacketAnswerA(packet, ipV4Bytes) : 84 | 85 | try : 86 | 87 | queryEndPos = 12 88 | while True : 89 | domPartLen = packet[queryEndPos] 90 | if (domPartLen == 0) : 91 | break 92 | queryEndPos += 1 + domPartLen 93 | queryEndPos += 5 94 | 95 | return b''.join( [ 96 | packet[:2], # Query identifier 97 | b'\x85\x80', # Flags and codes 98 | packet[4:6], # Query question count 99 | b'\x00\x01', # Answer record count 100 | b'\x00\x00', # Authority record count 101 | b'\x00\x00', # Additional record count 102 | packet[12:queryEndPos], # Query question 103 | b'\xc0\x0c', # Answer name as pointer 104 | b'\x00\x01', # Answer type A 105 | b'\x00\x01', # Answer class IN 106 | b'\x00\x00\x00\x1E', # Answer TTL 30 secondes 107 | b'\x00\x04', # Answer data length 108 | ipV4Bytes ] ) # Answer data 109 | 110 | except : 111 | pass 112 | 113 | return None 114 | 115 | # ============================================================================ 116 | # ===( Constructor )========================================================== 117 | # ============================================================================ 118 | 119 | def __init__(self) : 120 | self._domList = { } 121 | self._started = False 122 | 123 | # ============================================================================ 124 | # ===( Server Thread )======================================================== 125 | # ============================================================================ 126 | 127 | def _serverProcess(self) : 128 | self._started = True 129 | while True : 130 | try : 131 | packet, cliAddr = self._server.recvfrom(256) 132 | domName = MicroDNSSrv._getAskedDomainName(packet) 133 | if domName : 134 | domName = domName.lower() 135 | ipB = self._domList.get(domName, None) 136 | if not ipB : 137 | for domChk in self._domList.keys() : 138 | if domChk.find('*') >= 0 : 139 | r = domChk.replace('.', '\.').replace('*', '.*') + '$' 140 | if match(r, domName) : 141 | ipB = self._domList.get(domChk, None) 142 | break 143 | if not ipB : 144 | ipB = self._domList.get('*', None) 145 | if ipB : 146 | packet = MicroDNSSrv._getPacketAnswerA(packet, ipB) 147 | if packet : 148 | self._server.sendto(packet, cliAddr) 149 | except : 150 | if not self._started : 151 | break 152 | 153 | # ============================================================================ 154 | # ===( Functions )============================================================ 155 | # ============================================================================ 156 | 157 | def Start(self) : 158 | if not self._started : 159 | self._server = socket.socket( socket.AF_INET, 160 | socket.SOCK_DGRAM, 161 | socket.IPPROTO_UDP ) 162 | self._server.setsockopt( socket.SOL_SOCKET, 163 | socket.SO_REUSEADDR, 164 | 1 ) 165 | self._server.bind(('0.0.0.0', 53)) 166 | self._server.setblocking(True) 167 | return MicroDNSSrv._tryStartThread(self._serverProcess) 168 | return False 169 | 170 | # ---------------------------------------------------------------------------- 171 | 172 | def Stop(self) : 173 | if self._started : 174 | self._started = False 175 | self._server.close() 176 | return True 177 | return False 178 | 179 | # ---------------------------------------------------------------------------- 180 | 181 | def IsStarted(self) : 182 | return self._started 183 | 184 | # ---------------------------------------------------------------------------- 185 | 186 | def SetDomainsList(self, domainsList) : 187 | if domainsList and isinstance(domainsList, dict) : 188 | o = { } 189 | for dom, ip in domainsList.items() : 190 | if isinstance(dom, str) and len(dom) > 0 : 191 | ipB = MicroDNSSrv._ipV4StrToBytes(ip) 192 | if ipB : 193 | o[dom.lower()] = ipB 194 | continue 195 | break 196 | if len(o) == len(domainsList) : 197 | self._domList = o 198 | return True 199 | return False 200 | 201 | # ============================================================================ 202 | # ============================================================================ 203 | # ============================================================================ 204 | 205 | --------------------------------------------------------------------------------