Files
Pat Whittingslow a2970b923d add ipv6 to xnet.StackAsync (#107)
* add ipv6 to xnet.StackAsync

* dns improvements

* improve DNS workings of StackAsync

* add tentative ICMPv6

* work on prefixes and fix some small bugs, plan UDP/TCP6

* fix bugs in StackAsync and ipv4.Prefix.Contains

* update arpsubtable

* completely remove legacy internet.StackIP for StackIPv4/v6

* ipv4/ipv6 tcp/udp

* add TCP6/UDP6 dialing APIs

* add xnet.Stack6 interface

* more ipv6 integration into StackAsync; various tweaks to lneto and documentation+TODOs

* add stack6 tests

* replace netip.Prefix with ipv4.Prefix where it makes sense
2026-05-13 15:31:18 -03:00

319 lines
7.8 KiB
Go

package dns
import (
"fmt"
"net/netip"
"strings"
"testing"
)
var defaultMessageFlags = NewClientHeaderFlags(OpCodeQuery, true)
func TestNameString(t *testing.T) {
var name Name
domain := "foo.bar.org"
domainSplit := strings.Split(domain, ".")
for i, label := range domainSplit {
name.AddLabel(label)
s := name.String()
if s != strings.Join(domainSplit[:i+1], ".")+"." {
t.Fatalf("unexpected name string %q", s)
}
}
}
func TestNameAppendDecode(t *testing.T) {
const domain = "foo.bar.org"
name, err := NewName(domain)
if err != nil {
t.Fatal(err)
} else if name.String() != domain+"." {
t.Fatalf("unexpected name string %q", name.String())
}
var buf [512]byte
b, err := name.AppendTo(buf[:0])
if err != nil {
t.Fatal(err)
}
if uint16(len(b)) != name.Len() {
t.Fatalf("unexpected name length %d", len(b))
}
if b[len(b)-1] != 0 {
t.Fatalf("unexpected name terminator byte after construction: %q", b[len(b)-1])
}
var name2 Name
n, err := name2.Decode(b, 0)
if err != nil {
t.Fatal(err)
}
if n != name.Len() {
t.Errorf("unexpected name parsed length %q (%d), want %q (%d)", name.data, n, b, name.Len())
}
if name2.String() != name.String() {
t.Errorf("unexpected name string %q, want %q", name2.String(), name.String())
}
// Re-decode.
const okvalidName = "\x03www\x02go\x03dev\x00"
_, err = name.Decode([]byte(okvalidName), 0)
if err != nil {
t.Error("got error decoding valid name", err)
} else if name.String() != "www.go.dev." {
t.Error("unexpected name string", name.String())
}
b, err = name.AppendTo(buf[:0])
if err != nil {
t.Fatal(err)
}
if b[len(b)-1] != 0 {
t.Fatalf("unexpected name terminator byte after decoding: %q", b[len(b)-1])
}
if string(b) != okvalidName {
t.Errorf("unexpected name bytes after decode %q, want %q", b, okvalidName)
}
// Decode invalid name.
const invalidName = "\x03w.w\x02go\x03dev\x00"
_, err = name.Decode([]byte(invalidName), 0)
if err == nil {
t.Error("expected error for invalid name")
} else if err != errInvalidName {
t.Errorf("unexpected error %v, want %v", err, errInvalidName)
}
}
func TestMessageAppendEncode(t *testing.T) {
var tests = []struct {
Message Message
error error
}{
{
Message: Message{
Questions: []Question{
{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
},
},
Answers: []Resource{
{
header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
},
},
},
}
var buf [512]byte
for _, tt := range tests {
b, err := tt.Message.AppendTo(buf[:0], 123, defaultMessageFlags)
if err != nil {
t.Fatal(err)
}
var msg Message
msg.LimitResourceDecoding(uint16(len(tt.Message.Questions)), uint16(len(tt.Message.Answers)), uint16(len(tt.Message.Authorities)), uint16(len(tt.Message.Additionals)))
_, incomplete, err := msg.Decode(b)
if err != nil {
t.Fatal(err)
} else if incomplete {
t.Fatal("incomplete parse")
}
if msg.String() != tt.Message.String() {
t.Errorf("mismatch message strings after append/decode:\n%s\n%s", tt.Message.String(), msg.String())
}
}
}
func TestMessageAppendEncodeIncompleteOK(t *testing.T) {
var tests = []struct {
Message Message
error error
}{
{
Message: Message{
Questions: []Question{
{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
},
},
Answers: []Resource{
{
header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
{
header: ResourceHeader{
Name: MustNewName("."),
Type: TypeA,
Class: ClassINET,
TTL: 256,
Length: 3,
},
data: []byte{1, 2, 3},
},
},
},
},
}
var buf [512]byte
for _, tt := range tests {
b, err := tt.Message.AppendTo(buf[:0], 123, defaultMessageFlags)
if err != nil {
t.Fatal(err)
}
var msg Message
// Limit answers to 1 to test incomplete parsing (message has 2 answers).
msg.LimitResourceDecoding(uint16(len(tt.Message.Questions)), 1, uint16(len(tt.Message.Authorities)), uint16(len(tt.Message.Additionals)))
_, incomplete, err := msg.Decode(b)
if err != nil && !incomplete {
t.Fatal(err)
} else if !incomplete {
t.Fatal("expected incomplete parse")
}
tt.Message.Answers = tt.Message.Answers[:1] // Trim to match the limited decode.
if msg.String() != tt.Message.String() {
t.Errorf("mismatch message strings after append/decode:\n%s\n%s", tt.Message.String(), msg.String())
}
}
}
func (m *Message) String() string {
// s := fmt.Sprintf("Message: %#v\n", &m.Header)
var s strings.Builder
if len(m.Questions) > 0 {
s.WriteString("-- Questions\n")
for _, q := range m.Questions {
s.WriteString(fmt.Sprintf("%#v\n", q))
}
}
if len(m.Answers) > 0 {
s.WriteString("-- Answers\n")
for _, a := range m.Answers {
s.WriteString(fmt.Sprintf("%#v\n", a))
}
}
if len(m.Authorities) > 0 {
s.WriteString("-- Authorities\n")
for _, ns := range m.Authorities {
s.WriteString(fmt.Sprintf("%#v\n", ns))
}
}
if len(m.Additionals) > 0 {
s.WriteString("-- Additionals\n")
for _, e := range m.Additionals {
s.WriteString(fmt.Sprintf("%#v\n", e))
}
}
return s.String()
}
func TestDecodeMessage(t *testing.T) {
var data = []byte{
0x84, 0x05, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0b, 0x77, 0x68, 0x69,
0x74, 0x74, 0x69, 0x6c, 0x65, 0x61, 0x6b, 0x73, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00,
0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x1e, 0xaf, 0x00, 0x04, 0xc6, 0x31, 0x17,
0x91, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
var msg Message
msg.LimitResourceDecoding(5, 5, 5, 5)
off, incomplete, err := msg.Decode(data)
if incomplete || err != nil {
t.Fatal(incomplete, err, off)
}
}
func TestClient_ReceivesDNSResponse(t *testing.T) {
const hostname = "example.com"
const txid = uint16(12345)
const clientPort = uint16(54321)
wantIP := [4]byte{93, 184, 216, 34}
// Build a DNS response message.
name := MustNewName(hostname)
responseMsg := Message{
Questions: []Question{{
Name: name,
Type: TypeA,
Class: ClassINET,
}},
Answers: []Resource{
NewResource(name, TypeA, ClassINET, 300, wantIP[:]),
},
}
// Response flags: QR=1 (response), RD=1, RA=1.
responseFlags := HeaderFlags(1<<15 | 1<<8 | 1<<7)
var buf [512]byte
dnsPayload, err := responseMsg.AppendTo(buf[:0], txid, responseFlags)
if err != nil {
t.Fatal("failed to build DNS response:", err)
}
// Set up the DNS client.
var client Client
client.StartResolve(clientPort, txid, ResolveConfig{
Questions: []Question{{
Name: MustNewName(hostname),
Type: TypeA,
Class: ClassINET,
}},
EnableRecursion: true,
})
// Simulate sending by calling Encapsulate (changes state to AwaitResponse).
var dummy [512]byte
client.Encapsulate(dummy[:], 0, 0)
// Call Demux with DNS payload.
err = client.Demux(dnsPayload, 0)
if err != nil {
t.Fatal("Client Demux error:", err)
}
// Check the client received the answer.
var addrs [4]netip.Addr
answers, err := client.ResponseAnswerLookup(addrs[:], hostname)
if answers != 1 {
t.Fatalf("expected 1 answer, got %d", answers)
}
addr := addrs[0]
if !addr.Is4() {
t.Fatalf("expected 4 bytes in answer, got %d", addr.BitLen()/8)
}
if addr.As4() != wantIP {
t.Errorf("expected IP %v, got %v", wantIP, addr.String())
}
// Test MessageCopyTo as well.
var lookup Message
lookup.LimitResourceDecoding(1, 1, 0, 0)
done, err := client.ResponseCopyTo(&lookup)
if err != nil {
t.Fatal("MessageCopyTo error:", err)
}
if !done {
t.Fatal("expected done=true")
}
if len(lookup.Answers) != 1 {
t.Fatalf("MessageCopyTo: expected 1 answer, got %d", len(lookup.Answers))
}
}