def fake_query(qname, rdtype=rdatatype.A, rdclass=rdataclass.IN, count=1, fake_txt=False): """Fake a DNS query, returning count responses to the request Three kinds of lookups are faked: 1. A query for A records for a service will return the count as requested in the test. This simulates lookups for the ipa-ca A record. To force a difference in responses one can vary the count. 2. AAAA records are not yet supported, return no answer 3. TXT queries will return the Kerberos realm fake_txt will set an invalid Kerberos realm entry to provoke a warning. """ m = message.Message() if rdtype == rdatatype.A: fqdn = DNSName(qname) fqdn = fqdn.make_absolute() answers = Answer(fqdn, rdataclass.IN, rdatatype.A, m, raise_on_no_answer=False) rlist = rrset.from_text_list(fqdn, 86400, rdataclass.IN, rdatatype.A, gen_addrs(count)) answers.rrset = rlist elif rdtype == rdatatype.AAAA: raise NoAnswer(response=Response('no AAAA')) elif rdtype == rdatatype.TXT: if fake_txt: realm = 'FAKE_REALM' else: realm = m_api.env.realm qname = DNSName('_kerberos.' + m_api.env.domain) qname = qname.make_absolute() answers = Answer(qname, rdataclass.IN, rdatatype.TXT, m, raise_on_no_answer=False) rlist = rrset.from_text_list(qname, 86400, rdataclass.IN, rdatatype.TXT, [realm]) answers.rrset = rlist return answers
def make_notify(qname): """ according to RFC1035 and RFC1996 """ qname = name.from_text(qname) rdtype = rdatatype.SOA rdclass = rdataclass.IN m = message.Message() m.flags |= dns.flags.AA m.flags |= dns.opcode.to_flags(dns.opcode.NOTIFY) m.find_rrset(m.question, qname, rdclass, rdtype, create=True, force_unique=True) return m
def query_uri(hosts): """ Return a list containing two answers, one for each uri type """ answers = [] if version.MAJOR < 2 or (version.MAJOR == 2 and version.MINOR == 0): m = message.Message() elif version.MAJOR == 2 and version.MINOR > 0: m = message.QueryMessage() # pylint: disable=E1101 m = message.make_response(m) # pylint: disable=E1101 rdtype = rdatatype.URI for name in ('_kerberos.', '_kpasswd.'): qname = DNSName(name + m_api.env.domain) qname = qname.make_absolute() if version.MAJOR < 2: # pylint: disable=unexpected-keyword-arg answer = Answer(qname, rdataclass.IN, rdtype, m, raise_on_no_answer=False) # pylint: enable=unexpected-keyword-arg else: if version.MAJOR == 2 and version.MINOR > 0: question = rrset.RRset(qname, rdataclass.IN, rdtype) m.question = [question] answer = Answer(qname, rdataclass.IN, rdtype, m) rl = [] for host in hosts: rlist = rrset.from_text_list( qname, 86400, rdataclass.IN, rdatatype.URI, [ '0 100 "krb5srv:m:tcp:%s."' % host, '0 100 "krb5srv:m:udp:%s."' % host, ]) rl.extend(rlist) answer.rrset = rl answers.append(answer) return answers
def gethostbyname(hostname, timeout): """ Translate a host name to IPv4 address. Currently this method contains an example. You will have to replace this example with the algorithm described in section 5.3.3 in RFC 1034. Args: hostname (str): the hostname to resolve Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timeout) # Create and send query question = message.Question(hostname, Type.A, Class.IN) header = message.Header(9001, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = 1 query = message.Message(header, [question]) sock.sendto(query.to_bytes(), ('198.41.0.4', 53)) # 198.41.0.4 # Receive response data = sock.recv(512) response = message.Message.from_bytes(data) # for byte in data: # print [byte] # Get data for question in response.questions: print 'question:', question.qname, Type.by_value[ question.qtype], Class.by_value[question.qclass] addresses = list() for answer in response.answers: if answer.type_ == Type.A: addresses.append(answer.rdata.data) print 'answer:', answer.rdata.data, Type.by_value[ answer.type_], Class.by_value[ answer.class_], answer.name, answer.ttl for authority in response.authorities: print 'authority:', authority.rdata.data, Type.by_value[ authority.type_], Class.by_value[ authority.class_], authority.name, authority.ttl aliases = list() for additional in response.additionals: if additional.type_ == Type.CNAME: aliases.append(additional.rdata.data) print 'additional:', additional.rdata.data, Type.by_value[ additional.type_], Class.by_value[ additional.class_], additional.name, additional.ttl # print('aliases:') # for alias in aliases: # print(alias) # print('addresses:') # for address in addresses: # print(address) return hostname, aliases, addresses
def run(self): logging.debug('running with %s and %s', self.args, self.kwargs) # server socket is bound to an internal (127) address to provide resolution inside the node s_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s_server.bind((self.ip, self.port)) logging.debug('server socket bound:%s, %s and %d', s_server, self.ip, self.port) #client socket is bound to the standard address so that it can be used to make upstream requests s_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s_client.bind(('', 0)) logging.debug('client socket bound:%s, %s', s_client, str(s_client.getsockname())) # c_*_resolver interacts upstream # s_*_resolver interacts with local processes serving them dns requests threads = [ procinspect.ProcInspect(name='proc_inspect', args=(self.proc_inspect_req, self.proc_inspect_rsp), kwargs={}), TxResolver(name='c_tx_resolver', args=(s_client, self.client_tx_queue), kwargs={}), RxResolver(name='c_rx_resolver', args=(s_client, self.client_rx_queue), kwargs={}), TxResolver(name='s_tx_resolver', args=(s_server, self.server_tx_queue), kwargs={}), RxResolver(name='s_rx_resolver', args=(s_server, self.server_rx_queue), kwargs={}), ] for t in threads: logging.debug('starting thread: %s', t.name) t.start() #local vars used in select processing and maintaining state of dns queries. request_state = {} #inputs = [s_server, s_client] inputs = [] outputs = [] timeout = 0.2 # select on sockets with timeout, assuming that the recv will fire before the select, # may need to change to some other type of inter thread signalling as this select may only work on timeout while True: readable, writable, exceptional = select.select( inputs, outputs, inputs, timeout) for s in readable: logging.debug('select readable: %s', s) for s in exceptional: logging.debug('select exceptional: %s', s) if not (readable or writable or exceptional): #logging.debug('select timeout:') pass # with viable data on a socket or a timeout attempt to check for the various # async conditions # # # # RESPONSE PROCESSING # # # [>> 1] check for any proc_inspect_responses # # if there is a proc inspect response -- # State Transition # Wait_for_first_response -> Wait_for_response_A # OR # Wait_for_response_B -> Idle try: qitem = self.proc_inspect_rsp.get_nowait() except queue.Empty: pass else: lport, mid, pi_rsp = qitem ###logging.debug('client_rxqueue: lport %d, m.id %d, proc_ident_rsp ,%s', lport,mid,pi_rsp) #find the address of the original requesting process from the stored state if mid in request_state: client_addr, t_id, t_question, lp, dns_rsp, _ = request_state[ m.id] if lp != lport: logging.debug( 'client_rxqueue: lport %d not found in request_state, m.id %d', lport, mid) else: if dns_rsp is not None: #1 send the dns response to client ( TODO this needs to be modified to use localhost address based on procinfo) ###logging.debug('client_rxqueue: sending to server_tx %s, %d,proc_info %s',client_addr, m.id,m) server_qitem = (client_addr, m) self.server_tx_queue.put(server_qitem) del request_state[m.id] else: # store pi_rsp and wait for dns_rsp to arrive request_state[mid] = (client_addr, t_id, t_question, lport, dns_rsp, pi_rsp) else: logging.debug( 'client_rxqueue: m.id not found, message dumped - %d', m.id) # # [>> 2] is there a dns response from upstream, find the address of the local process # that requested and process it downstream # check client_rx_queue and sent to server_tx_queue # # if there is a proc inspect response -- # State Transition # Wait_for_first_response -> Wait_for_response_B # OR # Wait_for_response_A -> Idle try: qitem = self.client_rx_queue.get_nowait() except queue.Empty: pass else: addr, m = qitem ###logging.debug('client_rxqueue: addr %s, m.id ,%d, m.quest, %s, m.ans, %s', addr, m.id, m.question, m.answer) #find the address of the original requesting process from the stored state if m.id in request_state: client_addr, t_id, t_question, lport, _, pi_rsp = request_state[ m.id] dns_rsp = m # # POLICY CHANGE # if the requested name resultion is in the target_map insert this as the answer instead of # using the upstream answer. # TODO: assuming its an A record we are looking for q = m.question[0].name dns_q_name = str(q).rstrip('.') ###logging.debug(' dns_q_name: %s, %s', dns_q_name, q) if dns_q_name in self.target_map: target_result = self.target_map[dns_q_name][ 'hostlocal'] dns_result = dns.rrset.from_text( dns_q_name + '.', 300, 'in', 'a', target_result) logging.debug(' dns_result: %s', dns_result) dns_rsp.answer = [dns_result] #if have both responses act, otherwise wait for second response if pi_rsp is not None: #1 send the dns response to client ( TODO this needs to be modified to use localhost address based on procinfo) ###logging.debug('client_rxqueue: sending to server_tx %s, %d,proc_info %s',client_addr, m.id,m) server_qitem = (client_addr, m) self.server_tx_queue.put(server_qitem) del request_state[m.id] else: # copy the dns response into state and wait for proc_info response request_state[m.id] = (client_addr, t_id, t_question, lport, dns_rsp, pi_rsp) else: logging.debug( 'client_rxqueue: m.id not found, message dumped - %d', m.id) # # # # REQUEST PROCESSING # # # # [>> 3] is there a dns query from a local process, process it upstream # check server_rx_queue and sent to client_tx_queue # # State Transition Idle->Wait_for_first_response # -- this is the initial state transition, a client process has requested a dns resolution, # initiate a request to get process info details, # initiate an upstead DNS request based on the received info. # transition to waiting for responses for either of these two requests. try: qitem = self.server_rx_queue.get_nowait() except queue.Empty: pass else: addr, m = qitem lip, lport = addr ###logging.debug('server_rxqueue: addr %s, m.id %d, m.quest %s, m.ans, %s', addr, m.id, m.question, m.answer) # issue a proc identification request based on localport self.proc_inspect_req.put((lport, m.id)) # maintain a mapping of dns query id to requesting address so when response comes back # the response can be sent downstream to the proper process if m.id in request_state: logging.debug( 'server_rxqueue: already in request state should not be rx ,%s', m.id) request_state[m.id] = ( addr, m.id, m.question, lport, None, None ) # (first NONE is for DNS resposne, Second for Proc Response) # issue a generic dns request (TODO this is hardcoded and needs to change) dns_res_addr = ('44.1.13.1', 53) #dns_res_addr = ('10.33.5.10',53) client_qitem = (dns_res_addr, m) ###logging.debug('server_rxqueue: sending to client_tx %s, %d',dns_res_addr, m.id) self.client_tx_queue.put(client_qitem) # [>> 4 ] is there a ???? # try: msg = self.resolver_req.get_nowait() except queue.Empty: pass else: logging.debug('resolver request received: %s', msg) if msg.req_type == "resolver_policy_add": logging.debug('resolver policy add:') args_dict = msg.args_dict resolver_args = args_dict['args'] resolver_kwarg = args_dict['kwargs'] self.target_map = resolver_kwarg['target_map'] logging.debug('target_map: %s', self.target_map) response = message.Message(id=msg.id, data='ok') self.resolver_rsp.put(response) else: response = message.Message( id=msg.id, data='nok: message type not processed') self.resolver_rsp.put(response) #cleanup threads for t in threads: logging.debug('joining thread', t.name) t.join() time.sleep(2) logging.debug('exiting') return
def send_queries(self): # Create query question = message.Question(self.SNAME, Type.A, Class.IN) header = message.Header(42, 0, 1, 0, 0, 0) header.qr = 0 header.opcode = 0 header.rd = 1 query = message.Message(header, [question]) query_bytes = query.to_bytes() def send_query(ind, server_data): server_name = server_data[0] server_ip = server_data[1] sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) if not server_ip: if server_name: try: a_rr = self.CACHE.lookup(server_name, Type.A, Class.IN)[0] server_ip = a_rr.rdata.data except IndexError: ns_resolver = Resolver(self.caching, self.CACHE) _, addresses, _ = ns_resolver.gethostbyname( server_name) if addresses: server_ip = addresses[0] else: results[ind] = -1 return else: results[ind] = -1 return try_count = 0 max_tries = 3 while try_count < max_tries: try: sock.sendto(query_bytes, (server_ip, 53)) except: print('Unable to send to: ' + str(server_ip)) try: results[ind] = { 'data': sock.recv(512), 'server': server_data } return except socket.timeout: try_count += 1 print('timeout ' + str(try_count)) results[ind] = -1 results = list() for ind, server in enumerate(self.SLIST): results.append(None) Thread(target=send_query, args=(ind, server)).start() while True: if not all(r in [None, -1] for r in results): # if result is found response = None server_data = None for i, r in enumerate(results): if r not in [None, -1]: response = message.Message.from_bytes(r['data']) server_data = r['server'] results[i] = -1 break print('--------\nserver: ' + str(server_data[0]) + ', ' + str(server_data[1])) self.show_response(response) self.analyze_response(response) if self.addresses: return if all(r == -1 for r in results): # all results timed out or didn't return anything raise ResolverException(RCode.NXDomain) sleep(0.01)
def fake_query(qname, rdtype=rdatatype.A, rdclass=rdataclass.IN, count=1, fake_txt=False): """Fake a DNS query, returning count responses to the request Three kinds of lookups are faked: 1. A query for A/AAAA records for a service will return the count as requested in the test. This simulates lookups for the ipa-ca A/AAAA record. To force a difference in responses one can vary the count. 2. TXT queries will return the Kerberos realm fake_txt will set an invalid Kerberos realm entry to provoke a warning. """ if version.MAJOR < 2 or (version.MAJOR == 2 and version.MINOR == 0): m = message.Message() elif version.MAJOR == 2 and version.MINOR > 0: m = message.QueryMessage() m = message.make_response(m) if rdtype in (rdatatype.A, rdatatype.AAAA): fqdn = DNSName(qname) fqdn = fqdn.make_absolute() if version.MAJOR < 2: # pylint: disable=unexpected-keyword-arg answers = Answer(fqdn, rdataclass.IN, rdtype, m, raise_on_no_answer=False) # pylint: enable=unexpected-keyword-arg else: if version.MAJOR == 2 and version.MINOR > 0: question = rrset.RRset(fqdn, rdataclass.IN, rdtype) m.question = [question] answers = Answer(fqdn, rdataclass.IN, rdtype, m) rlist = rrset.from_text_list(fqdn, 86400, rdataclass.IN, rdtype, gen_addrs(rdtype, count)) answers.rrset = rlist elif rdtype == rdatatype.TXT: if fake_txt: realm = 'FAKE_REALM' else: realm = m_api.env.realm qname = DNSName('_kerberos.' + m_api.env.domain) qname = qname.make_absolute() if version.MAJOR < 2: # pylint: disable=unexpected-keyword-arg answers = Answer(qname, rdataclass.IN, rdatatype.TXT, m, raise_on_no_answer=False) # pylint: enable=unexpected-keyword-arg else: if version.MAJOR == 2 and version.MINOR > 0: question = rrset.RRset(qname, rdataclass.IN, rdtype) m.question = [question] answers = Answer(qname, rdataclass.IN, rdatatype.TXT, m) rlist = rrset.from_text_list(qname, 86400, rdataclass.IN, rdatatype.TXT, [realm]) answers.rrset = rlist return answers