/* 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 }
"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) }