PageRenderTime 47ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/DNSServer.py

https://github.com/yoyonbb303/PlexConnect
Python | 445 lines | 368 code | 9 blank | 68 comment | 0 complexity | 31a22d5e7b31ba502490f0fd964f53d3 MD5 | raw file
  1. #!/usr/bin/env python
  2. """
  3. Source:
  4. http://code.google.com/p/minidns/source/browse/minidns
  5. """
  6. """
  7. Header
  8. 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
  9. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  10. | ID |
  11. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  12. |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
  13. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  14. | QDCOUNT |
  15. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  16. | ANCOUNT |
  17. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  18. | NSCOUNT |
  19. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  20. | ARCOUNT |
  21. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  22. Query
  23. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  24. | |
  25. / QNAME /
  26. | |
  27. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  28. | QTYPE |
  29. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  30. | QCLASS |
  31. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  32. ResourceRecord
  33. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  34. | |
  35. / NAME /
  36. | |
  37. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  38. | TYPE |
  39. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  40. | CLASS |
  41. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  42. | TTL |
  43. | |
  44. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  45. | RDLENGTH |
  46. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
  47. | |
  48. / RDATA /
  49. | |
  50. +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  51. Source: http://doc-tcpip.org/Dns/named.dns.message.html
  52. """
  53. """
  54. prevent aTV update
  55. Source: http://forum.xbmc.org/showthread.php?tid=93604
  56. loopback to 127.0.0.1...
  57. mesu.apple.com
  58. appldnld.apple.com
  59. appldnld.apple.com.edgesuite.net
  60. """
  61. import sys
  62. import socket
  63. import struct
  64. from multiprocessing import Pipe # inter process communication
  65. import signal
  66. import Settings
  67. from Debug import * # dprint()
  68. """
  69. Hostname/DNS conversion
  70. Hostname: 'Hello.World'
  71. DNSdata: '<len(Hello)>Hello<len(World)>World<NULL>
  72. """
  73. def HostToDNS(Host):
  74. DNSdata = '.'+Host+'\0' # python 2.6: bytearray()
  75. i=0
  76. while i<len(DNSdata)-1:
  77. next = DNSdata.find('.',i+1)
  78. if next==-1:
  79. next = len(DNSdata)-1
  80. DNSdata = DNSdata[:i] + chr(next-i-1) + DNSdata[i+1:] # python 2.6: DNSdata[i] = next-i-1
  81. i = next
  82. return DNSdata
  83. def DNSToHost(DNSdata, i, followlink=True):
  84. Host = ''
  85. while DNSdata[i]!='\0':
  86. nlen = ord(DNSdata[i])
  87. if nlen & 0xC0:
  88. if followlink:
  89. Host = Host + DNSToHost(DNSdata, ((ord(DNSdata[i]) & 0x3F)<<8) + ord(DNSdata[i+1]) , True)+'.'
  90. break
  91. else:
  92. Host = Host + DNSdata[i+1:i+nlen+1]+'.'
  93. i+=nlen+1
  94. Host = Host[:-1]
  95. return Host
  96. def printDNSdata(paket):
  97. # HEADER
  98. print "ID {0:04x}".format((ord(paket[0])<<8)+ord(paket[1]))
  99. print "flags {0:02x} {1:02x}".format(ord(paket[2]), ord(paket[3]))
  100. print "OpCode "+str((ord(paket[2])>>3)&0x0F)
  101. print "RCode "+str((ord(paket[3])>>0)&0x0F)
  102. qdcount = (ord(paket[4])<<8)+ord(paket[5])
  103. ancount = (ord(paket[6])<<8)+ord(paket[7])
  104. nscount = (ord(paket[8])<<8)+ord(paket[9])
  105. arcount = (ord(paket[10])<<8)+ord(paket[11])
  106. print "Count - QD, AN, NS, AR:", qdcount, ancount, nscount, arcount
  107. adr = 12
  108. # QDCOUNT (query)
  109. for i in range(qdcount):
  110. print "QUERY"
  111. host = DNSToHost(paket, adr)
  112. """
  113. for j in range(len(host)+2+4):
  114. print ord(paket[adr+j]),
  115. print
  116. """
  117. adr = adr + len(host) + 2
  118. print host
  119. print "type "+str((ord(paket[adr+0])<<8)+ord(paket[adr+1]))
  120. print "class "+str((ord(paket[adr+2])<<8)+ord(paket[adr+3]))
  121. adr = adr + 4
  122. # ANCOUNT (resource record)
  123. for i in range(ancount):
  124. print "ANSWER"
  125. print ord(paket[adr])
  126. if ord(paket[adr]) & 0xC0:
  127. print"link"
  128. adr = adr + 2
  129. else:
  130. host = DNSToHost(paket, adr)
  131. adr = adr + len(host) + 2
  132. print host
  133. _type = (ord(paket[adr+0])<<8)+ord(paket[adr+1])
  134. _class = (ord(paket[adr+2])<<8)+ord(paket[adr+3])
  135. print "type, class: ", _type, _class
  136. adr = adr + 4
  137. print "ttl"
  138. adr = adr + 4
  139. rdlength = (ord(paket[adr+0])<<8)+ord(paket[adr+1])
  140. print "rdlength", rdlength
  141. adr = adr + 2
  142. if _type==1:
  143. print "IP:",
  144. for j in range(rdlength):
  145. print ord(paket[adr+j]),
  146. print
  147. elif _type==5:
  148. print "redirect:", DNSToHost(paket, adr)
  149. else:
  150. print "type unsupported:",
  151. for j in range(rdlength):
  152. print ord(paket[adr+j]),
  153. print
  154. adr = adr + rdlength
  155. def printDNSdata_raw(DNSdata):
  156. # hex code
  157. for i in range(len(DNSdata)):
  158. if i % 16==0:
  159. print
  160. print "{0:02x}".format(ord(DNSdata[i])),
  161. print
  162. # printable characters
  163. for i in range(len(DNSdata)):
  164. if i % 16==0:
  165. print
  166. if (ord(DNSdata[i])>32) & (ord(DNSdata[i])<128):
  167. print DNSdata[i],
  168. else:
  169. print ".",
  170. print
  171. def parseDNSdata(paket):
  172. def getWord(DNSdata, addr):
  173. return (ord(DNSdata[addr])<<8)+ord(DNSdata[addr+1])
  174. DNSstruct = {}
  175. adr = 0
  176. # header
  177. DNSstruct['head'] = { \
  178. 'id': getWord(paket, adr+0), \
  179. 'flags': getWord(paket, adr+2), \
  180. 'qdcnt': getWord(paket, adr+4), \
  181. 'ancnt': getWord(paket, adr+6), \
  182. 'nscnt': getWord(paket, adr+8), \
  183. 'arcnt': getWord(paket, adr+10) }
  184. adr = adr + 12
  185. # query
  186. DNSstruct['query'] = []
  187. for i in range(DNSstruct['head']['qdcnt']):
  188. DNSstruct['query'].append({})
  189. host_nolink = DNSToHost(paket, adr, followlink=False)
  190. host_link = DNSToHost(paket, adr, followlink=True)
  191. DNSstruct['query'][i]['host'] = host_link
  192. adr = adr + len(host_nolink)+2
  193. DNSstruct['query'][i]['type'] = getWord(paket, adr+0)
  194. DNSstruct['query'][i]['class'] = getWord(paket, adr+2)
  195. adr = adr + 4
  196. # resource records
  197. DNSstruct['resrc'] = []
  198. for i in range(DNSstruct['head']['ancnt'] + DNSstruct['head']['nscnt'] + DNSstruct['head']['arcnt']):
  199. DNSstruct['resrc'].append({})
  200. host_nolink = DNSToHost(paket, adr, followlink=False)
  201. host_link = DNSToHost(paket, adr, followlink=True)
  202. DNSstruct['resrc'][i]['host'] = host_link
  203. adr = adr + len(host_nolink)+2
  204. DNSstruct['resrc'][i]['type'] = getWord(paket, adr+0)
  205. DNSstruct['resrc'][i]['class'] = getWord(paket, adr+2)
  206. DNSstruct['resrc'][i]['ttl'] = (getWord(paket, adr+4)<<16)+getWord(paket, adr+6)
  207. DNSstruct['resrc'][i]['rdlen'] = getWord(paket, adr+8)
  208. adr = adr + 10
  209. DNSstruct['resrc'][i]['rdata'] = []
  210. if DNSstruct['resrc'][i]['type']==5: # 5=redirect, evaluate name
  211. host = DNSToHost(paket, adr, followlink=True)
  212. DNSstruct['resrc'][i]['rdata'] = host
  213. adr = adr + DNSstruct['resrc'][i]['rdlen']
  214. DNSstruct['resrc'][i]['rdlen'] = len(host)
  215. else: # 1=IP, ...
  216. for j in range(DNSstruct['resrc'][i]['rdlen']):
  217. DNSstruct['resrc'][i]['rdata'].append( paket[adr+j] )
  218. adr = adr + DNSstruct['resrc'][i]['rdlen']
  219. return DNSstruct
  220. def encodeDNSstruct(DNSstruct):
  221. def appendWord(DNSdata, val):
  222. DNSdata.append((val>>8) & 0xFF)
  223. DNSdata.append( val & 0xFF)
  224. DNS = bytearray()
  225. # header
  226. appendWord(DNS, DNSstruct['head']['id'])
  227. appendWord(DNS, DNSstruct['head']['flags'])
  228. appendWord(DNS, DNSstruct['head']['qdcnt'])
  229. appendWord(DNS, DNSstruct['head']['ancnt'])
  230. appendWord(DNS, DNSstruct['head']['nscnt'])
  231. appendWord(DNS, DNSstruct['head']['arcnt'])
  232. # query
  233. for i in range(DNSstruct['head']['qdcnt']):
  234. host = HostToDNS(DNSstruct['query'][i]['host'])
  235. DNS.extend(bytearray(host))
  236. appendWord(DNS, DNSstruct['query'][i]['type'])
  237. appendWord(DNS, DNSstruct['query'][i]['class'])
  238. # resource records
  239. for i in range(DNSstruct['head']['ancnt'] + DNSstruct['head']['nscnt'] + DNSstruct['head']['arcnt']):
  240. host = HostToDNS(DNSstruct['resrc'][i]['host']) # no 'packing'/link - todo?
  241. DNS.extend(bytearray(host))
  242. appendWord(DNS, DNSstruct['resrc'][i]['type'])
  243. appendWord(DNS, DNSstruct['resrc'][i]['class'])
  244. appendWord(DNS, (DNSstruct['resrc'][i]['ttl']>>16) & 0xFFFF)
  245. appendWord(DNS, (DNSstruct['resrc'][i]['ttl'] ) & 0xFFFF)
  246. appendWord(DNS, DNSstruct['resrc'][i]['rdlen'])
  247. if DNSstruct['resrc'][i]['type']==5: # 5=redirect, hostname
  248. host = HostToDNS(DNSstruct['resrc'][i]['rdata'])
  249. DNS.extend(bytearray(host))
  250. else:
  251. DNS.extend(DNSstruct['resrc'][i]['rdata'])
  252. return DNS
  253. def printDNSstruct(DNSstruct):
  254. for i in range(DNSstruct['head']['qdcnt']):
  255. print "query:", DNSstruct['query'][i]['host']
  256. for i in range(DNSstruct['head']['ancnt'] + DNSstruct['head']['nscnt'] + DNSstruct['head']['arcnt']):
  257. print "resrc:",
  258. print DNSstruct['resrc'][i]['host']
  259. if DNSstruct['resrc'][i]['type']==1:
  260. print "->IP:",
  261. for j in range(DNSstruct['resrc'][i]['rdlen']):
  262. print ord(DNSstruct['resrc'][i]['rdata'][j]),
  263. print
  264. elif DNSstruct['resrc'][i]['type']==5:
  265. print "->alias:", DNSstruct['resrc'][i]['rdata']
  266. else:
  267. print "->unknown type"
  268. def Run(cmdPipe, param):
  269. if not __name__ == '__main__':
  270. signal.signal(signal.SIGINT, signal.SIG_IGN)
  271. dinit(__name__, param) # init logging, DNSServer process
  272. cfg_IP_self = param['IP_self']
  273. cfg_Port_DNSServer = param['CSettings'].getSetting('port_dnsserver')
  274. cfg_IP_DNSMaster = param['CSettings'].getSetting('ip_dnsmaster')
  275. try:
  276. DNS = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  277. DNS.settimeout(5.0)
  278. DNS.bind((cfg_IP_self, int(cfg_Port_DNSServer)))
  279. except Exception, e:
  280. dprint(__name__, 0, "Failed to create socket on UDP port {0}: {1}", cfg_Port_DNSServer, e)
  281. sys.exit(1)
  282. try:
  283. DNS_forward = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  284. DNS_forward.settimeout(5.0)
  285. except Exception, e:
  286. dprint(__name__, 0, "Failed to create socket on UDP port 49152: {0}", e)
  287. sys.exit(1)
  288. intercept = [param['HostToIntercept']]
  289. restrain = []
  290. if param['CSettings'].getSetting('prevent_atv_update')=='True':
  291. restrain = ['mesu.apple.com', 'appldnld.apple.com', 'appldnld.apple.com.edgesuite.net']
  292. dprint(__name__, 0, "***")
  293. dprint(__name__, 0, "DNSServer: Serving DNS on {0} port {1}.", cfg_IP_self, cfg_Port_DNSServer)
  294. dprint(__name__, 1, "intercept: {0} => {1}", intercept, cfg_IP_self)
  295. dprint(__name__, 1, "restrain: {0} => 127.0.0.1", restrain)
  296. dprint(__name__, 1, "forward other to higher level DNS: "+cfg_IP_DNSMaster)
  297. dprint(__name__, 0, "***")
  298. try:
  299. while True:
  300. # check command
  301. if cmdPipe.poll():
  302. cmd = cmdPipe.recv()
  303. if cmd=='shutdown':
  304. break
  305. # do your work (with timeout)
  306. try:
  307. data, addr = DNS.recvfrom(1024)
  308. dprint(__name__, 1, "DNS request received!")
  309. dprint(__name__, 1, "Source: "+str(addr))
  310. #print "incoming:"
  311. #printDNSdata(data)
  312. # analyse DNS request
  313. # todo: how about multi-query messages?
  314. opcode = (ord(data[2]) >> 3) & 0x0F # Opcode bits (query=0, inversequery=1, status=2)
  315. if opcode == 0: # Standard query
  316. domain = DNSToHost(data, 12)
  317. dprint(__name__, 1, "Domain: "+domain)
  318. paket=''
  319. if domain in intercept:
  320. dprint(__name__, 1, "***intercept request")
  321. paket+=data[:2] # 0:1 - ID
  322. paket+="\x81\x80" # 2:3 - flags
  323. paket+=data[4:6] # 4:5 - QDCOUNT - should be 1 for this code
  324. paket+=data[4:6] # 6:7 - ANCOUNT
  325. paket+='\x00\x00' # 8:9 - NSCOUNT
  326. paket+='\x00\x00' # 10:11 - ARCOUNT
  327. paket+=data[12:] # original query
  328. paket+='\xc0\x0c' # pointer to domain name/original query
  329. paket+='\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04' # response type, ttl and resource data length -> 4 bytes
  330. paket+=str.join('',map(lambda x: chr(int(x)), cfg_IP_self.split('.'))) # 4bytes of IP
  331. dprint(__name__, 1, "-> DNS response: "+cfg_IP_self)
  332. elif domain in restrain:
  333. dprint(__name__, 1, "***restrain request")
  334. paket+=data[:2] # 0:1 - ID
  335. paket+="\x81\x80" # 2:3 - flags
  336. paket+=data[4:6] # 4:5 - QDCOUNT - should be 1 for this code
  337. paket+=data[4:6] # 6:7 - ANCOUNT
  338. paket+='\x00\x00' # 8:9 - NSCOUNT
  339. paket+='\x00\x00' # 10:11 - ARCOUNT
  340. paket+=data[12:] # original query
  341. paket+='\xc0\x0c' # pointer to domain name/original query
  342. paket+='\x00\x01\x00\x01\x00\x00\x00\x3c\x00\x04' # response type, ttl and resource data length -> 4 bytes
  343. paket+='\x7f\x00\x00\x01' # 4bytes of IP - 127.0.0.1, loopback
  344. dprint(__name__, 1, "-> DNS response: "+cfg_IP_self)
  345. else:
  346. dprint(__name__, 1, "***forward request")
  347. DNS_forward.sendto(data, (cfg_IP_DNSMaster, 53))
  348. paket, addr_master = DNS_forward.recvfrom(1024)
  349. # todo: double check: ID has to be the same!
  350. # todo: spawn thread to wait in parallel
  351. dprint(__name__, 1, "-> DNS response from higher level")
  352. #print "-> respond back:"
  353. #printDNSdata(paket)
  354. # todo: double check: ID has to be the same!
  355. DNS.sendto(paket, addr)
  356. except socket.timeout:
  357. pass
  358. except socket.error as e:
  359. dprint(__name__, 1, "Warning: DNS error ({0}): {1}", e.errno, e.strerror)
  360. except KeyboardInterrupt:
  361. signal.signal(signal.SIGINT, signal.SIG_IGN) # we heard you!
  362. dprint(__name__, 0, "^C received.")
  363. finally:
  364. dprint(__name__, 0, "Shutting down.")
  365. DNS.close()
  366. DNS_forward.close()
  367. if __name__ == '__main__':
  368. cmdPipe = Pipe()
  369. cfg = Settings.CSettings()
  370. param = {}
  371. param['CSettings'] = cfg
  372. param['IP_self'] = '192.168.178.20' # IP_self?
  373. param['baseURL'] = 'http://'+ param['IP_self'] +':'+ cfg.getSetting('port_webserver')
  374. param['HostToIntercept'] = 'trailers.apple.com'
  375. Run(cmdPipe[1], param)