Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/net/dns/packet.rb
Views: 11780
# -*- coding: binary -*-1require 'logger'2require 'net/dns/names/names'3require 'net/dns/dns'4require 'net/dns/header'5require 'net/dns/question'6require 'net/dns/rr'78module Net # :nodoc:9module DNS1011# =Name12#13# Net::DNS::Packet - DNS packet object class14#15# =Synopsis16#17# require 'net/dns/packet'18#19# =Description20#21# The Net::DNS::Packet class represents an entire DNS packet,22# divided in his main section:23#24# * Header (instance of Net::DNS::Header)25# * Question (array of Net::DNS::Question objects)26# * Answer, Authority, Additional (each formed by an array of Net::DNS::RR27# objects)28#29# You can use this class whenever you need to create a DNS packet, whether30# in an user application, in a resolver instance (have a look, for instance,31# at the Net::DNS::Resolver#send method) or for a nameserver.32#33# Some example:34#35# # Create a packet36# packet = Net::DNS::Packet.new("www.example.com")37# mx = Net::DNS::Packet.new("example.com", Net::DNS::MX)38#39# # Getting packet binary data, suitable for network transmission40# data = packet.data41#42# A packet object can be created from binary data too, like an43# answer packet just received from a network stream:44#45# packet = Net::DNS::Packet::parse(data)46#47# Each part of a packet can be gotten by the right accessors:48#49# header = packet.header # Instance of Net::DNS::Header class50# question = packet.question # Instance of Net::DNS::Question class51#52# # Iterate over additional RRs53# packet.additional.each do |rr|54# puts "Got an #{rr.type} record"55# end56#57# Some iterators have been written to easy the access of those RRs,58# which are often the most important. So instead of doing:59#60# packet.answer.each do |rr|61# if rr.type == Net::DNS::RR::Types::A62# # do something with +rr.address+63# end64# end65#66# we can do:67#68# packet.each_address do |ip|69# # do something with +ip+70# end71#72# Be sure you don't miss all the iterators in the class documentation.73#74# =Logging facility75#76# As Net::DNS::Resolver class, Net::DNS::Packet class has its own logging77# facility too. It work in the same way the other one do, so you can78# maybe want to override it or change the file descriptor.79#80# packet = Net::DNS::Packet.new("www.example.com")81# packet.logger = $stderr82#83# # or even84# packet.logger = Logger.new("/tmp/packet.log")85#86# If the Net::DNS::Packet class is directly instantiated by the Net::DNS::Resolver87# class, like the great majority of the time, it will use the same logger facility.88#89# Logger level will be set to Logger::Debug if $DEBUG variable is set.90#91# =Error classes92#93# Some error classes has been defined for the Net::DNS::Packet class,94# which are listed here to keep a light and browsable main documentation.95# We have:96#97# * PacketArgumentError: Generic argument error for class Net::DNS::Packet98# * PacketError: Generic Packet error99#100# =Copyright101#102# Copyright (c) 2006 Marco Ceresa103#104# All rights reserved. This program is free software; you may redistribute105# it and/or modify it under the same terms as Ruby itself.106#107class Packet108109include Names110111attr_reader :header, :question, :answer, :authority, :additional112attr_reader :answerfrom, :answersize113114# Create a new instance of Net::DNS::Packet class. Arguments are the115# canonical name of the resource, an optional type field and an optional116# class field. The record type and class can be omitted; they default117# to +A+ and +IN+.118#119# packet = Net::DNS::Packet.new("www.example.com")120# packet = Net::DNS::Packet.new("example.com", Net::DNS::MX)121# packet = Net::DNS::Packet.new("example.com",Net::DNS::TXT,Net::DNS::CH)122#123# This class no longer instantiate object from binary data coming from124# network streams. Please use Net::DNS::Packet.new_from_data instead.125#126def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)127@header = Net::DNS::Header.new(:qdCount => 1)128@question = [Net::DNS::Question.new(name,type,cls)]129@answer = []130@authority = []131@additional = []132@logger = Logger.new $stdout133@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN134end135136# Create a new instance of Net::DNS::Packet class from binary data, taken137# out by a network stream. For example:138#139# # udp_socket is an UDPSocket waiting for a response140# ans = udp_socket.recvfrom(1500)141# packet = Net::DNS::Packet::parse(ans)142#143# An optional +from+ argument can be used to specify the information144# of the sender. If data is passed as is from a Socket#recvfrom call,145# the method will accept it.146#147# Be sure that your network data is clean from any UDP/TCP header,148# especially when using RAW sockets.149#150def Packet.parse(*args)151o = allocate152o.send(:new_from_data, *args)153o154end155156157# Checks if the packet is a QUERY packet158def query?159@header.opCode == Net::DNS::Header::QUERY160end161162# Return the packet object in binary data, suitable163# for sending across a network stream.164#165# packet_data = packet.data166# puts "Packet is #{packet_data.size} bytes long"167#168def data169qdcount=ancount=nscount=arcount=0170data = @header.data171headerlength = data.length172173@question.each do |question|174data += question.data175qdcount += 1176end177@answer.each do |rr|178data += rr.data#(data.length)179ancount += 1180end181@authority.each do |rr|182data += rr.data#(data.length)183nscount += 1184end185@additional.each do |rr|186next if rr.nil?187data += rr.data#(data.length)188arcount += 1189end190191@header.qdCount = qdcount192@header.anCount = ancount193@header.nsCount = nscount194@header.arCount = arcount195196@header.data + data[Net::DNS::HFIXEDSZ..data.size]197end198199# Same as Net::DNS::Packet#data, but implements name compression200# (see RFC1025) for a considerable save of bytes.201#202# packet = Net::DNS::Packet.new("www.example.com")203# puts "Size normal is #{packet.data.size} bytes"204# puts "Size compressed is #{packet.data_comp.size} bytes"205#206def data_comp207offset = 0208compnames = {}209qdcount=ancount=nscount=arcount=0210data = @header.data211headerlength = data.length212213@question.each do |question|214str,offset,names = question.data215data += str216compnames.update(names)217qdcount += 1218end219220@answer.each do |rr|221str,offset,names = rr.data(offset,compnames)222data += str223compnames.update(names)224ancount += 1225end226227@authority.each do |rr|228str,offset,names = rr.data(offset,compnames)229data += str230compnames.update(names)231nscount += 1232end233234@additional.each do |rr|235str,offset,names = rr.data(offset,compnames)236data += str237compnames.update(names)238arcount += 1239end240241@header.qdCount = qdcount242@header.anCount = ancount243@header.nsCount = nscount244@header.arCount = arcount245246@header.data + data[Net::DNS::HFIXEDSZ..data.size]247end248249# Inspect method250def inspect251retval = ""252if @answerfrom != "0.0.0.0:0" and @answerfrom253retval << ";; Answer received from #@answerfrom (#{@answersize} bytes)\n;;\n"254end255256retval << ";; HEADER SECTION\n"257retval << @header.inspect258259retval << "\n"260section = (@header.opCode == "UPDATE") ? "ZONE" : "QUESTION"261retval << ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'}):\n"262@question.each do |qr|263retval << ";; " + qr.inspect + "\n"264end265266unless @answer.size == 0267retval << "\n"268section = (@header.opCode == "UPDATE") ? "PREREQUISITE" : "ANSWER"269retval << ";; #{section} SECTION (#{@header.anCount} record#{@header.anCount == 1 ? '' : 's'}):\n"270@answer.each do |rr|271retval << rr.inspect + "\n"272end273end274275unless @authority.size == 0276retval << "\n"277section = (@header.opCode == "UPDATE") ? "UPDATE" : "AUTHORITY"278retval << ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '' : 's'}):\n"279@authority.each do |rr|280retval << rr.inspect + "\n"281end282end283284unless @additional.size == 0285retval << "\n"286retval << ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '' : 's'}):\n"287@additional.each do |rr|288retval << rr.inspect + "\n"289end290end291292retval293end294295296# Wrapper to Header#truncated?297#298def truncated?299@header.truncated?300end301302# Assign a Net::DNS::Header object to a Net::DNS::Packet303# instance.304#305def header=(object)306if object.kind_of? Net::DNS::Header307@header = object308else309raise PacketArgumentError, "Argument must be a Net::DNS::Header object"310end311end312313# Assign a Net::DNS::Question object, or an array of314# Questions objects, to a Net::DNS::Packet instance.315#316def question=(object)317case object318when Array319if object.all? {|x| x.kind_of? Net::DNS::Question}320@question = object321else322raise PacketArgumentError, "Some of the elements is not an Net::DNS::Question object"323end324when Net::DNS::Question325@question = [object]326else327raise PacketArgumentError, "Invalid argument, not a Question object nor an array of objects"328end329end330331# Assign a Net::DNS::RR object, or an array of332# RR objects, to a Net::DNS::Packet instance answer333# section.334#335def answer=(object)336case object337when Array338if object.all? {|x| x.kind_of? Net::DNS::RR}339@answer = object340else341raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"342end343when Net::DNS::RR344@answer = [object]345else346raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"347end348end349350# Assign a Net::DNS::RR object, or an array of351# RR objects, to a Net::DNS::Packet instance additional352# section.353#354def additional=(object)355case object356when Array357if object.all? {|x| x.kind_of? Net::DNS::RR}358@additional = object359else360raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"361end362when Net::DNS::RR363@additional = [object]364else365raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"366end367end368369# Assign a Net::DNS::RR object, or an array of370# RR objects, to a Net::DNS::Packet instance authority371# section.372#373def authority=(object)374case object375when Array376if object.all? {|x| x.kind_of? Net::DNS::RR}377@authority = object378else379raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"380end381when Net::DNS::RR382@authority = [object]383else384raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"385end386end387388# Iterate for every address in the +answer+ section of a389# Net::DNS::Packet object.390#391# packet.each_address do |ip|392# ping ip.to_s393# end394#395# As you can see in the documentation for Net::DNS::RR::A class,396# the address returned is an instance of IPAddr class.397#398def each_address399@answer.each do |elem|400next unless elem.class == Net::DNS::RR::A401yield elem.address402end403end404405# Iterate for every nameserver in the +answer+ section of a406# Net::DNS::Packet object.407#408# packet.each_nameserver do |ns|409# puts "Nameserver found: #{ns}"410# end411#412def each_nameserver413@answer.each do |elem|414next unless elem.class == Net::DNS::RR::NS415yield elem.nsdname416end417end418419# Iterate for every exchange record in the +answer+ section420# of a Net::DNS::Packet object.421#422# packet.each_mx do |pref,name|423# puts "Mail exchange #{name} has preference #{pref}"424# end425#426def each_mx427@answer.each do |elem|428next unless elem.class == Net::DNS::RR::MX429yield elem.preference,elem.exchange430end431end432433# Iterate for every canonical name in the +answer+ section434# of a Net::DNS::Packet object.435#436# packet.each_cname do |cname|437# puts "Canonical name: #{cname}"438# end439#440def each_cname441@answer.each do |elem|442next unless elem.class == Net::DNS::RR::CNAME443yield elem.cname444end445end446447# Iterate for every pointer in the +answer+ section of a448# Net::DNS::Packet object.449#450# packet.each_ptr do |ptr|451# puts "Pointer for resource: #{ptr}"452# end453#454def each_ptr455@answer.each do |elem|456next unless elem.class == Net::DNS::RR::PTR457yield elem.ptrdname458end459end460461# Chacks whether a query has returned a NXDOMAIN error,462# meaning the domain name queried doesn't exists.463#464# %w[a.com google.com ibm.com d.com].each do |domain|465# response = Net::DNS::Resolver.new.send(domain)466# puts "#{domain} doesn't exist" if response.nxdomain?467# end468# #=> a.com doesn't exist469# #=> d.com doesn't exist470#471def nxdomain?472header.rCode == Net::DNS::Header::NAME473end474475private476477# New packet from binary data478def new_from_data(data, from = nil)479unless from480if data.kind_of? Array481data,from = data482else483from = [0,0,"0.0.0.0","unknown"]484end485end486487@answerfrom = from[2] + ":" + from[1].to_s488@answersize = data.size489@logger = Logger.new $stdout490@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN491492#------------------------------------------------------------493# Header section494#------------------------------------------------------------495offset = Net::DNS::HFIXEDSZ496@header = Net::DNS::Header.parse(data[0..offset-1])497498@logger.debug ";; HEADER SECTION"499@logger.debug @header.inspect500501#------------------------------------------------------------502# Question section503#------------------------------------------------------------504section = @header.opCode == "UPDATE" ? "ZONE" : "QUESTION"505@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"506507@question = []508@header.qdCount.times do509qobj,offset = parse_question(data,offset)510@question << qobj511@logger.debug ";; #{qobj.inspect}"512end513514#------------------------------------------------------------515# Answer/prerequisite section516#------------------------------------------------------------517section = @header.opCode == "UPDATE" ? "PREREQUISITE" : "ANSWER"518@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"519520@answer = []521@header.anCount.times do522if (rrobj, new_offset = Net::DNS::RR.parse_packet(data, offset))523@answer << rrobj524@logger.debug rrobj.inspect525offset = new_offset526else527@logger.warn "Failed to parse RR packet from offset: #{offset}"528_, offset = dn_expand(data, offset)529_, _, _, rdlength = data.unpack("@#{offset} n2 N n")530offset += RRFIXEDSZ + rdlength531end532end533534#------------------------------------------------------------535# Authority/update section536#------------------------------------------------------------537section = @header.opCode == "UPDATE" ? "UPDATE" : "AUTHORITY"538@logger.debug ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '': 's'})"539540@authority = []541@header.nsCount.times do542rrobj,offset = Net::DNS::RR.parse_packet(data,offset)543@authority << rrobj544@logger.debug rrobj.inspect545end546547#------------------------------------------------------------548# Additional section549#------------------------------------------------------------550@logger.debug ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '': 's'})"551552@additional = []553@header.arCount.times do554rrobj,offset = Net::DNS::RR.parse_packet(data,offset)555@additional << rrobj556@logger.debug rrobj.inspect557end558559end # new_from_data560561562# Parse question section563def parse_question(data,offset)564size = (dn_expand(data,offset)[1]-offset) + 2*Net::DNS::INT16SZ565return [Net::DNS::Question.parse(data[offset,size]), offset+size]566rescue StandardError => err567raise PacketError, "Caught exception, maybe packet malformed => #{err}"568end569570end # class Packet571572end # module DNS573end # module Net574575class PacketError < StandardError # :nodoc:576end577class PacketArgumentError < ArgumentError # :nodoc:578end579580581