/*
For fragmentation, we use a naive algorithm.

We use the same header for every fragment, and include the same EDNS0
section in every additional section.

We add one RR at a time, until our fragment is larger than 512 bytes,
then we remove the last RR so that it fits in the 512 byte size limit.

If we discover that one of the fragments ends up with 0 RR in it (for
example because a single RR is too big), then we return a single
truncated response instead of the set of fragments.

We could perhaps make the process of building fragments faster by
bisecting the set of RR that we include in an answer. So, if we have 8
RR we could try all, then if that is too big, 4 RR, and if that fits
then 6 RR, until an optimal set of RR is found.

We could also possibly produce a smaller set of responses by
optimizing how we combine RR. Just taking account the various sizes is
the same as the bin packing problem, which is NP-hard:

  https://en.wikipedia.org/wiki/Bin_packing_problem

While some non-optimal but reasonable heuristics exist, in the case of
DNS we would have to use some sophisticated algorithm to also consider
name compression.
*/
func frag(reply *dns.Msg) []dns.Msg {
	// create a return value
	all_frags := []dns.Msg{}
	HasEdns0 := true
	// get each RR section and save a copy out
	remaining_answer := make([]dns.RR, len(reply.Answer))
	copy(remaining_answer, reply.Answer)
	remaining_ns := make([]dns.RR, len(reply.Ns))
	copy(remaining_ns, reply.Ns)
	remaining_extra := make([]dns.RR, len(reply.Extra))
	copy(remaining_extra, reply.Extra)

	// if we don't have EDNS0 in the packet, add it now
	if reply.IsEdns0() == nil {
		reply.SetEdns0(512, false)
	}

	// the EDNS option for later use
	var edns0_rr dns.RR = nil

	// remove the EDNS0 option from our additional ("extra") section
	// (we will include it separately on every fragment)
	for ofs, r := range remaining_extra {
		// found the EDNS option
		if r.Header().Rrtype == dns.TypeOPT {
			// save the EDNS option
			edns0_rr = r
			// remove from the set of extra RR
			remaining_extra = append(remaining_extra[0:ofs], remaining_extra[ofs+1:]...)
			// in principle we should only have one EDNS0 section
			break
		}
	}

	if edns0_rr == nil {
		log.Printf("Server reply missing EDNS0 option")
		return []dns.Msg{}
		//HasEdns0 = false
	}

	// now build fragments
	for {
		// make a shallow copy of our reply packet, and prepare space for our RR
		frag := *reply
		frag.Answer = []dns.RR{}
		frag.Ns = []dns.RR{}
		frag.Extra = []dns.RR{}

		// add our custom EDNS0 option (needed in every fragment)
		local_opt := new(dns.EDNS0_LOCAL)
		local_opt.Code = dns.EDNS0LOCALSTART + 1
		local_opt.Data = []byte{0, 0}
		if HasEdns0 == true {
			edns0_rr_copy := dns.Copy(edns0_rr)
			edns0_rr_copy.(*dns.OPT).Option = append(edns0_rr_copy.(*dns.OPT).Option, local_opt)
			frag.Extra = append(frag.Extra, edns0_rr_copy)
		}
		//if HasEdns0 == false {
		//	frag.Extra = append(frag.Extra, local_opt)
		//}

		// add as many RR to the answer as we can
		for len(remaining_answer) > 0 {
			frag.Answer = append(frag.Answer, remaining_answer[0])
			if frag.Len() <= 512 {
				// if the new answer fits, then remove it from our remaining list
				remaining_answer = remaining_answer[1:]
			} else {
				// otherwise we are full, remove it from our fragment and stop
				frag.Answer = frag.Answer[0 : len(frag.Answer)-1]
				break
			}
		}
		for len(remaining_ns) > 0 {
			frag.Ns = append(frag.Ns, remaining_ns[0])
			if frag.Len() <= 512 {
				// if the new answer fits, then remove it from our remaining list
				remaining_ns = remaining_ns[1:]
			} else {
				// otherwise we are full, remove it from our fragment and stop
				frag.Ns = frag.Ns[0 : len(frag.Ns)-1]
				break
			}
		}
		for len(remaining_extra) > 0 {
			frag.Extra = append(frag.Extra, remaining_extra[0])
			if frag.Len() <= 512 {
				// if the new answer fits, then remove it from our remaining list
				remaining_extra = remaining_extra[1:]
			} else {
				// otherwise we are full, remove it from our fragment and stop
				frag.Extra = frag.Extra[0 : len(frag.Extra)-1]
				break
			}
		}

		// check to see if we didn't manage to add any RR
		if (len(frag.Answer) == 0) && (len(frag.Ns) == 0) && (len(frag.Extra) == 1) {
			// TODO: test this :)
			// return a single truncated fragment without any RR
			frag.MsgHdr.Truncated = true
			frag.Extra = []dns.RR{}
			return []dns.Msg{frag}
		}

		// add to our list of fragments
		all_frags = append(all_frags, frag)
		// if we have finished all remaining sections, we are done
		if (len(remaining_answer) == 0) && (len(remaining_ns) == 0) && (len(remaining_extra) == 0) {
			break
		}
	}

	// fix up our fragments so they have the correct sequence and length values
	for n, frag := range all_frags {
		frag_edns0 := frag.IsEdns0()
		for _, opt := range frag_edns0.Option {
			if opt.Option() == dns.EDNS0LOCALSTART+1 {
				opt.(*dns.EDNS0_LOCAL).Data = []byte{byte(len(all_frags)), byte(n)}
			}
		}
	}

	// return our fragments
	return all_frags
}
Example #2
0
File: stub.go Project: CMGS/skydns
	"strconv"
	"strings"

	"github.com/miekg/dns"
	"github.com/skynetservices/skydns/msg"
)

const ednsStubCode = dns.EDNS0LOCALSTART + 10

// ednsStub is the EDNS0 record we add to stub queries. Queries which have this record are
// not forwarded again.
var ednsStub = func() *dns.OPT {
	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	e := new(dns.EDNS0_LOCAL)
	e.Code = ednsStubCode
	e.Data = []byte{1}
	o.Option = append(o.Option, e)
	return o
}()

// Look in .../dns/stub/<domain>/xx for msg.Services. Loop through them
// extract <domain> and add them as forwarders (ip:port-combos) for
// the stub zones. Only numeric (i.e. IP address) hosts are used.
func (s *server) UpdateStubZones() {
	stubmap := make(map[string][]string)

	services, err := s.backend.Records("stub.dns."+s.config.Domain, false, net.IP{})
	if err != nil {
		logf("stub zone update failed: %s", err)
func (this ClientProxy) ServeDNS(w dns.ResponseWriter, request *dns.Msg) {
	// if we don't have EDNS0 in the packet, add it now
	// TODO: in principle we should check packet size here, since we have made it bigger,
	//       but for this demo code we will just rely on most queries being really small
	proxy_req := *request
	opt := proxy_req.IsEdns0()
	var client_buf_size uint16
	if opt == nil {
		proxy_req.SetEdns0(512, false)
		client_buf_size = 512
		_D("%s QID:%d adding EDNS0 to packet", w.RemoteAddr(), request.Id)
		opt = proxy_req.IsEdns0()
	} else {
		client_buf_size = opt.UDPSize()
	}

	// add our custom EDNS0 option
	local_opt := new(dns.EDNS0_LOCAL)
	local_opt.Code = dns.EDNS0LOCALSTART
	opt.Option = append(opt.Option, local_opt)

	// create a connection to the server
	// XXX: for now we will only handle UDP - this will break in unpredictable ways in production!
	conn, err := dns.DialTimeout("udp", this.SERVERS[rand.Intn(len(this.SERVERS))], this.timeout)
	if err != nil {
		_D("%s QID:%d error setting up UDP socket: %s", w.RemoteAddr(), request.Id, err)
		SRVFAIL(w, request)
		return
	}
	defer conn.Close()

	// set our timeouts
	// TODO: we need to insure that our timeouts work like we expect
	conn.SetReadDeadline(time.Now().Add(this.timeout))
	conn.SetWriteDeadline(time.Now().Add(this.timeout))

	// send our query
	err = conn.WriteMsg(&proxy_req)
	if err != nil {
		_D("%s QID:%d error writing message: %s", w.RemoteAddr(), request.Id, err)
		SRVFAIL(w, request)
		return
	}

	// wait for our reply
	response := wait_for_response(w, conn, request)
	if response == nil {
		return
	}

	// get fragment information from first response (if any)
	num_frags, sequence_num := get_fragment_info(response)

	// if we did not have a fragmented response, send it to the client
	if num_frags == -1 {
		w.WriteMsg(response)
		return
	}

	// build a map to hold the fragments that we have received
	frags := map[int]dns.Msg{sequence_num: *response}

	// wait for all fragments to arrive
	// duplicates overwrite previous packet, missing packets eventually timeout
	for len(frags) < num_frags {
		response := wait_for_response(w, conn, request)
		if response == nil {
			return
		}
		_, sequence_num := get_fragment_info(response)
		// TODO: remove the extra EDNS0 option
		frags[sequence_num] = *response
	}

	// rebuild our original packet
	rebuilt_reply, ok := frags[0]
	if !ok {
		_D("%s QID:%d missing fragment 0", w.RemoteAddr(), request.Id)
		SRVFAIL(w, request)
		return
	}
	for n := 1; n < num_frags; n++ {
		frag, ok := frags[n]
		if !ok {
			_D("%s QID:%d missing fragment %d", w.RemoteAddr(), request.Id, n)
			SRVFAIL(w, request)
			return
		}
		rebuilt_reply.Answer = append(rebuilt_reply.Answer, frag.Answer...)
		rebuilt_reply.Ns = append(rebuilt_reply.Ns, frag.Ns...)
		for _, r := range frag.Extra {
			// remove EDNS0 present in fragments from final answer
			if r.Header().Rrtype != dns.TypeOPT {
				rebuilt_reply.Extra = append(rebuilt_reply.Extra, r)
			}
		}
	}

	// verify that we don't exceed the client buffer size
	if rebuilt_reply.Len() > int(client_buf_size) {
		// truncate if we need to
		// TODO: test this
		rebuilt_reply.MsgHdr.Truncated = true
		rebuilt_reply.Answer = []dns.RR{}
		rebuilt_reply.Ns = []dns.RR{}
		rebuilt_reply.Extra = []dns.RR{}
	}

	// send our rebuilt reply
	w.WriteMsg(&rebuilt_reply)
}