a go dns packet parser

feature: add parsing of edns records according to rfc2671

+179 -6
+4 -1
README.md
··· 2 2 3 3 this is a go package for packing/unpacking dns packets. 4 4 5 + > which we expect to be so popular that it would be a waste of wire space 6 + - rfc2671 4.2 7 + 5 8 ## Spec 6 9 [x] 103{4,5} - DNS standard 7 10 ··· 25 28 26 29 [ ] 2065 - DNSSEC (updated in later RFCs) 27 30 28 - [ ] 2671 - EDNS record 31 + [x] 2671 - EDNS record 29 32 30 33 [ ] 2782 - SRV record 31 34
+26
message.go
··· 7 7 8 8 // Decode decodes a DNS packet. 9 9 func (m *Message) Decode(buf []byte) (err error) { 10 + // gets checked when parsing additional records 11 + m.HasEDNS = false 12 + 10 13 offset, err := m.Header.Decode(buf, 0) 11 14 if err != nil { 12 15 return fmt.Errorf("failed to decode message header: %w", err) ··· 53 56 return fmt.Errorf("failed to decode additional record #%d: %w", i+1, err) 54 57 } 55 58 59 + if rr.RType == OPTType { 60 + opt, ok := rr.RData.(*OPT) 61 + if !ok { 62 + // this should never fail 63 + return fmt.Errorf("unable to parse RData as OPT") 64 + } 65 + 66 + m.HasEDNS = true 67 + m.ExtendedRCode = uint8(rr.TTL >> 24 & 0xFF000000) 68 + m.EDNSVersion = uint8((rr.TTL & 0x00FF0000) >> 16) 69 + m.EDNSFlags = uint16(rr.TTL & 0x0000FFFF) 70 + m.EDNSOptions = opt.Options 71 + m.UDPSize = uint16(rr.RClass) 72 + } 73 + 56 74 m.Additional = append(m.Additional, rr) 75 + } 76 + 77 + if !m.HasEDNS { 78 + m.ExtendedRCode = 0 79 + m.EDNSVersion = 0 80 + m.EDNSFlags = 0 81 + m.EDNSOptions = make([]EDNSOption, 0) 82 + m.UDPSize = 512 // default in rfc-1035 section 2.3.4. 57 83 } 58 84 59 85 return nil
+46
message_test.go
··· 176 176 wantErrType: &BufferOverflowError{}, 177 177 wantErrMsg: "failed to decode answer record #1:", 178 178 }, 179 + { 180 + name: "EDNS Record", 181 + input: []byte{0xea, 0x7c, 0x1, 0x20, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6c, 0x6f, 0x62, 0x73, 0x74, 0x65, 0x2, 0x72, 0x73, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x29, 0x4, 0xd0, 0x0, 0x64, 0x0, 0x0, 0x0, 0x8, 0x0, 0x64, 0x0, 0x4, 0x66, 0x6f, 0x6f, 0xa}, 182 + wantErr: false, 183 + expected: Message{ 184 + Header: Header{ 185 + ID: 0xea7c, 186 + QR: false, 187 + OPCode: 0, 188 + RD: true, 189 + RCode: 0, 190 + QDCount: 1, 191 + ARCount: 1, 192 + }, 193 + Question: []Question{ 194 + { 195 + QName: "lobste.rs", 196 + QType: AType, 197 + QClass: IN, 198 + }, 199 + }, 200 + Answer: []ResourceRecord{}, 201 + Additional: []ResourceRecord{ 202 + { 203 + Name: "", 204 + RType: OPTType, 205 + RClass: 1232, 206 + TTL: 6553600, 207 + RDLength: 8, 208 + RData: &OPT{ 209 + []EDNSOption{ 210 + { 211 + Code: uint16(100), 212 + Data: []byte("foo\n"), 213 + }, 214 + }, 215 + }, 216 + }, 217 + }, 218 + Authority: []ResourceRecord{}, 219 + }, 220 + }, 179 221 } 180 222 181 223 for _, tt := range tests { ··· 205 247 assert.Equal(t, tt.expected.Answer, m.Answer, "Answer section mismatch") 206 248 assert.Equal(t, tt.expected.Authority, m.Authority, "Authority section mismatch") 207 249 assert.Equal(t, tt.expected.Additional, m.Additional, "Additional section mismatch") 250 + 251 + b, err := m.Encode() 252 + assert.NoError(t, err, "Expected no error on round trip") 253 + assert.Equal(t, tt.input, b, "Expected equal inputs on round trip") 208 254 } 209 255 }) 210 256 }
+78 -5
resource_record.go
··· 617 617 return len(buf), fmt.Errorf("x25 record: string segment length %d exceeds RDLENGTH boundary %d", strLen, endOffset) 618 618 } 619 619 620 - strBytes, return_offset, err := getSlice(buf, nextOffsetAfterLen, int(strLen)) 620 + strBytes, offset, err := getSlice(buf, nextOffsetAfterLen, int(strLen)) 621 621 if err != nil { 622 622 return len(buf), fmt.Errorf("x25 record: failed to read string data (length %d): %w", strLen, err) 623 623 } ··· 629 629 } 630 630 } 631 631 632 - return return_offset, nil 632 + return offset, nil 633 633 } 634 634 635 635 func (x *X25) Encode(bytes []byte, offsets *map[string]uint16) ([]byte, error) { ··· 751 751 return fmt.Sprintf("%d %s", rt.Preference, rt.IntermediateHost) 752 752 } 753 753 754 + func (opt *OPT) Decode(buf []byte, offset int, rdlength int) (int, error) { 755 + // s := offset 756 + opt.Options = make([]EDNSOption, 0) 757 + 758 + if rdlength == 0 { 759 + return offset, nil 760 + } 761 + 762 + endOffset := offset + rdlength 763 + curOffset := offset 764 + for curOffset < endOffset { 765 + // need 4 bytes for both code and length 766 + if offset+4 > endOffset { 767 + return offset, fmt.Errorf("OPT record: truncated option header at offset: %d", offset) 768 + } 769 + 770 + optCode, offset, err := getU16(buf, offset) 771 + if err != nil { 772 + return offset, fmt.Errorf("OPT Record: failed to read option code: %w", err) 773 + } 774 + 775 + optLength, offset, err := getU16(buf, offset) 776 + if err != nil { 777 + return offset, fmt.Errorf("OPT Record: failed to read option length: %w", err) 778 + } 779 + 780 + if offset+int(optLength) > endOffset { 781 + return offset, fmt.Errorf("OPT Record: failed to read option data of length: %d", offset) 782 + } 783 + 784 + optData, offset, err := getSlice(buf, offset, int(optLength)) 785 + if err != nil { 786 + return offset, fmt.Errorf("OPT Record: failed to read option data: %w", err) 787 + } 788 + 789 + opt.Options = append(opt.Options, EDNSOption{ 790 + Code: optCode, 791 + Data: optData, 792 + }) 793 + 794 + curOffset = offset 795 + } 796 + 797 + return offset, nil 798 + } 799 + 800 + func (opt *OPT) Encode(bytes []byte, offsets *map[string]uint16) ([]byte, error) { 801 + for _, opt := range opt.Options { 802 + bytes = binary.BigEndian.AppendUint16(bytes, opt.Code) 803 + bytes = binary.BigEndian.AppendUint16(bytes, uint16(len(opt.Data))) 804 + bytes = append(bytes, opt.Data...) 805 + } 806 + 807 + return bytes, nil 808 + } 809 + 810 + func (opt OPT) String() string { 811 + var result strings.Builder 812 + result.WriteString("OPT [") 813 + for i, o := range opt.Options { 814 + if i < len(opt.Options)-1 { 815 + result.WriteString(fmt.Sprintf("%d <%x>,", o.Code, o.Data)) 816 + } else { 817 + result.WriteString(fmt.Sprintf("%d <%x>,", o.Code, o.Data)) 818 + } 819 + } 820 + result.WriteString("]") 821 + 822 + return result.String() 823 + } 824 + 754 825 func (r *Reserved) Decode(buf []byte, offset int, rdlength int) (int, error) { 755 826 var err error 756 827 r.Bytes, offset, err = getSlice(buf, offset, int(rdlength)) ··· 844 915 r.RData = &ISDN{} 845 916 case 21: 846 917 r.RData = &RT{} 918 + case 41: 919 + r.RData = &OPT{} 847 920 default: 848 921 r.RData = &Reserved{} 849 922 } ··· 870 943 bytes = binary.BigEndian.AppendUint16(bytes, uint16(r.RClass)) 871 944 bytes = binary.BigEndian.AppendUint32(bytes, r.TTL) 872 945 873 - rdata_start := len(bytes) 946 + rdataStart := len(bytes) 874 947 bytes = binary.BigEndian.AppendUint16(bytes, 0) 875 948 bytes, err = r.RData.Encode(bytes, offsets) 876 949 if err != nil { 877 950 return nil, fmt.Errorf("rr encode: failed to encode RData for %s (%s): %w", r.Name, r.RType.String(), err) 878 951 } 879 952 880 - rdata_length := uint16(len(bytes) - rdata_start - 2) 881 - binary.BigEndian.PutUint16(bytes[rdata_start:rdata_start+2], rdata_length) 953 + rdataLength := uint16(len(bytes) - rdataStart - 2) 954 + binary.BigEndian.PutUint16(bytes[rdataStart:rdataStart+2], rdataLength) 882 955 883 956 return bytes, nil 884 957 }
+25
types.go
··· 45 45 NXDOMAIN 46 46 NOTIMP 47 47 REFUSED 48 + 49 + BADVERS = 16 48 50 ) 49 51 50 52 func (r RCode) String() string { ··· 61 63 return "NOTIMP" 62 64 case REFUSED: 63 65 return "REFUSED" 66 + case BADVERS: 67 + return "BADVERS" 64 68 default: 65 69 return fmt.Sprintf("RESERVED(%d)", int(r)) 66 70 } ··· 88 92 X25Type = 19 89 93 ISDNType = 20 90 94 RTType = 21 95 + 96 + OPTType = 41 91 97 92 98 AXFRType = 252 93 99 MAILBType = 253 ··· 139 145 return "ISDN" 140 146 case RTType: 141 147 return "RT" 148 + case OPTType: 149 + return "OPT" 142 150 case AXFRType: 143 151 return "AXFR" 144 152 case MAILBType: ··· 185 193 Answer []ResourceRecord // Answer contains a slice of resource records. 186 194 Authority []ResourceRecord // Authority contains a slice of resource records. 187 195 Additional []ResourceRecord // Additional contains a slice of resource records. 196 + 197 + // EDNS information 198 + HasEDNS bool // If the DNS message detected EDNS 199 + ExtendedRCode uint8 // first 8 bits to use if edns. 200 + EDNSVersion uint8 // EDNS version of sender. 201 + EDNSFlags uint16 // EDNS specific flag. 202 + EDNSOptions []EDNSOption // EDNS options. 203 + UDPSize uint16 // Max UDP size of sender. 188 204 189 205 // offsets is a map of domains pointing to the seen offset used 190 206 // in domain compression. ··· 357 373 type RT struct { 358 374 Preference uint16 359 375 IntermediateHost string 376 + } 377 + 378 + type EDNSOption struct { 379 + Code uint16 380 + Data []byte 381 + } 382 + 383 + type OPT struct { 384 + Options []EDNSOption 360 385 } 361 386 362 387 // Reserved represents a record that is not yet implemented.