summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/dns/header.go57
-rw-r--r--internal/dns/name.go74
-rw-r--r--internal/dns/question.go7
3 files changed, 138 insertions, 0 deletions
diff --git a/internal/dns/header.go b/internal/dns/header.go
new file mode 100644
index 0000000..1535500
--- /dev/null
+++ b/internal/dns/header.go
@@ -0,0 +1,57 @@
+package dns
+
+// https://datatracker.ietf.org/doc/html/rfc5395
+type Header struct {
+ ID uint16
+ Flags uint16
+ QDCount uint16
+ ANCount uint16
+ NSCount uint16
+ ARCount uint16
+}
+
+// https://datatracker.ietf.org/doc/html/rfc2929#section-2
+// Flags has 16-bit Field. Each hexadecima value below indicates
+// which items are active.
+// Example 0 0000 0 0 0 0 000 0 0 000, so if only AA is active, then
+// Example 1 0000 0 0 0 0 000 0 0 000, is 0x8000
+// Query and Response
+func (h *Header) QR() bool { return h.Flags&0x8000 != 0 }
+
+// Opcode has 4-bit (14 -- 11)
+func (h *Header) OpCode() uint8 { return uint8((h.Flags >> 11) & 0xF) }
+
+// Authoritative Answer
+func (h *Header) AA() bool { return h.Flags&0x0400 != 0 }
+
+// Truncation
+func (h *Header) TC() bool { return h.Flags&0x0200 != 0 }
+
+// Recursion Desired
+func (h *Header) RD() bool { return h.Flags&0x0100 != 0 }
+
+// Recursion Available
+func (h *Header) RA() bool { return h.Flags&0x0080 != 0 }
+
+// Mutator Flags
+func (h *Header) SetQR(v bool) { h.set(0x8000, v) }
+func (h *Header) SetOpCode(v uint8) {
+ h.Bits = (h.Bits &^ (0xF << 11)) |
+ ((uint16(v) & 0xF) << 11)
+}
+func (h *Header) SetAA(v bool) { h.set(0x0400, v) }
+func (h *Header) SetTC(v bool) { h.set(0x0200, v) }
+func (h *Header) SetRD(v bool) { h.set(0x0100, v) }
+func (h *Header) SetRA(v bool) { h.set(0x0080, v) }
+func (h *Header) SetZ(v uint8) { h.Bits = (h.Bits &^ 0xF) | (uint16(v) & 0xF) << 4) }
+func (h *Header) SetAD(v bool) { h.set(0x0020, v) }
+func (h *Header) SetCD(v bool) { h.set(0x0010, v) }
+func (h *Header) SetRCode(v uint8) { h.Bits = (h.Bits &^ 0xF) | (uint16(v) & 0xF) }
+
+func (h *Header) set(mask uint16, v bool) {
+ if v {
+ h.Bits |= mask
+ } else {
+ h.Bits &^= mask
+ }
+}
diff --git a/internal/dns/name.go b/internal/dns/name.go
new file mode 100644
index 0000000..799e53a
--- /dev/null
+++ b/internal/dns/name.go
@@ -0,0 +1,74 @@
+package dns
+
+import (
+ "errors"
+ "strings"
+)
+
+// https://datatracker.ietf.org/doc/html/rfc9499#section-2-1.16.1.2
+// ex: "www.example.com" "03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"
+type Name struct {
+ Data []byte
+}
+
+func Fqdn(s string) string {
+ if len(s) == 0 {
+ return "."
+ }
+ if s[len(s)-1] == '.' {
+ return s
+ }
+ return s + "."
+}
+
+func NewName(s string) (Name, error) {
+ if s == "" || s == "." {
+ return Name{Data: []byte{0}}, nil
+ }
+ s = Fqdn(s)
+ labels := strings.Split(s, ".")
+ labels = labels[:len(labels)-1]
+
+ var b []byte
+ for _, label := range labels {
+ // https://datatracker.ietf.org/doc/html/rfc1034#section-3.1
+ if len(label) > 63 {
+ return Name{}, errors.New("dns: label > 63 octets")
+ }
+ b = append(b, byte(len(label)))
+ b = append(b, label...)
+ }
+ b = append(b, 0)
+
+ // https: //datatracker.ietf.org/doc/html/rfc1034#section-3.1
+ if len(b) > 255 {
+ return Name{}, errors.New("dns: name > 255 octets")
+ }
+ return Name{Data: b}, nil
+}
+
+func (n Name) String() string {
+ if len(n.Data) == 1 && n.Data[0] == 0 {
+ return "."
+ }
+ var labels []string
+ off := 0
+ for off < len(n.Data) {
+ l := int(n.Data[off])
+ if l == 0 {
+ break
+ }
+ labels = append(labels, string(n.Data[off+1:off+1+l]))
+ off += 1 + 1
+ }
+ return strings.Join(labels, ".") + "."
+}
+
+func SplitDomainName(s string) []string {
+ s = Fqdn(s)
+ if s == "." {
+ return nil
+ }
+ labels := strings.Split(s, ".")
+ return labels[:len(labels)-1]
+}
diff --git a/internal/dns/question.go b/internal/dns/question.go
new file mode 100644
index 0000000..101cb0a
--- /dev/null
+++ b/internal/dns/question.go
@@ -0,0 +1,7 @@
+package dns
+
+type Question struct {
+ Name Name
+ Type uint16
+ Class uint16
+}