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
This commit is contained in:
Pat Whittingslow
2026-05-13 15:31:18 -03:00
committed by GitHub
parent 7d5830d7ab
commit a2970b923d
44 changed files with 1938 additions and 395 deletions
+14 -5
View File
@@ -4,6 +4,7 @@ import (
"log/slog"
"math"
"net"
"net/netip"
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
@@ -104,7 +105,7 @@ func (c *Client) isClosed() bool {
return c.state == CQueryIdle || c.state == CQueryAborted
}
func (c *Client) MessageCopyTo(dst *Message) (done bool, err error) {
func (c *Client) ResponseCopyTo(dst *Message) (done bool, err error) {
if !c.respFlags.IsResponse() {
return false, nil
}
@@ -116,11 +117,19 @@ func (c *Client) MessageCopyTo(dst *Message) (done bool, err error) {
return true, nil
}
func (c *Client) Answers() []Resource {
if c.state != CQueryDone {
return nil
func (c *Client) ResponseAnswerLookup(dst []netip.Addr, host string) (uint16, error) {
if !c.respFlags.IsResponse() {
return 0, nil
}
return c.msg.Answers
rcode := c.respFlags.ResponseCode()
if rcode != 0 {
return 0, rcode
}
return c.msg.WriteAnswers(dst, host)
}
func (c *Client) ResponseFlags() (HeaderFlags, bool) {
return c.respFlags, c.respFlags.IsResponse()
}
func (c *Client) Abort() {
+50
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"math"
"net/netip"
"slices"
"strconv"
"strings"
@@ -59,10 +60,38 @@ type ResourceHeader struct {
Length uint16
}
// Name is a wire representation of a DNS name.
type Name struct {
data []byte
}
// EqualString checks if the name receiver matches the strname string (non-wire formatted) name.
func (n Name) EqualString(strname string) bool {
data := n.data
for len(data) > 0 {
labelLen := int(data[0])
if labelLen == 0 {
return strname == "" || strname == "."
}
if len(data) < 1+labelLen {
return false
}
label := data[1 : 1+labelLen]
var seg string
idx := strings.IndexByte(strname, '.')
if idx < 0 {
seg, strname = strname, ""
} else {
seg, strname = strname[:idx], strname[idx+1:]
}
if len(seg) != len(label) || seg != string(label) {
return false
}
data = data[1+labelLen:]
}
return false
}
// NamesEqual reports whether two DNS names are equal by comparing
// their wire-format representations directly. This is case-sensitive;
// for case-insensitive comparison use [NamesEqualFold].
@@ -270,6 +299,27 @@ func (m *Message) AppendTo(buf []byte, txid uint16, flags HeaderFlags) (_ []byte
return buf, nil
}
func (m *Message) WriteAnswers(dst []netip.Addr, host string) (n uint16, err error) {
for i := range m.Answers {
if int(n) >= len(dst) {
return n, lneto.ErrExhausted
}
ans := &m.Answers[i]
hdr := ans.Header()
if !hdr.Name.EqualString(host) {
continue
}
var ok bool
dst[n], ok = netip.AddrFromSlice(ans.RawData())
if !ok {
err = lneto.ErrInvalidAddr
} else {
n++
}
}
return n, err
}
func (m *Message) Len() uint16 {
return SizeHeader + m.lenResources()
}
+11 -10
View File
@@ -2,6 +2,7 @@ package dns
import (
"fmt"
"net/netip"
"strings"
"testing"
)
@@ -288,23 +289,23 @@ func TestClient_ReceivesDNSResponse(t *testing.T) {
}
// Check the client received the answer.
answers := client.Answers()
if len(answers) != 1 {
t.Fatalf("expected 1 answer, got %d", len(answers))
var addrs [4]netip.Addr
answers, err := client.ResponseAnswerLookup(addrs[:], hostname)
if answers != 1 {
t.Fatalf("expected 1 answer, got %d", answers)
}
data := answers[0].RawData()
if len(data) != 4 {
t.Fatalf("expected 4 bytes in answer, got %d", len(data))
addr := addrs[0]
if !addr.Is4() {
t.Fatalf("expected 4 bytes in answer, got %d", addr.BitLen()/8)
}
if [4]byte(data) != wantIP {
t.Errorf("expected IP %v, got %v", wantIP, data)
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.MessageCopyTo(&lookup)
done, err := client.ResponseCopyTo(&lookup)
if err != nil {
t.Fatal("MessageCopyTo error:", err)
}