From 670b26ba3dd74fab5d06e9895268256121365e75 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 14 Aug 2025 13:11:06 +0000 Subject: [PATCH] lnwire: add encoding/decoding for DNS addr Co-authored-by: Elle Mouton --- lnwire/lnwire.go | 40 ++++++++++++++++++++++++++++++++++++++++ lnwire/message_test.go | 21 ++++++++++++++++++++- lnwire/writer.go | 29 +++++++++++++++++++++++++++++ lnwire/writer_test.go | 36 ++++++++++++++++++++++++++++-------- 4 files changed, 117 insertions(+), 9 deletions(-) diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 1b47e5778..86f7caa61 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -44,6 +44,10 @@ const ( // v3OnionAddrLen is the length of a version 3 Tor onion service address // (35 bytes decoded onion + 2 bytes port). v3OnionAddrLen = 37 + + // dnsAddrOverhead is the fixed overhead for a DNS address: 1 byte for + // the hostname length and 2 bytes for the port. + dnsAddrOverhead = 3 ) // PkScript is simple type definition which represents a raw serialized public @@ -70,6 +74,9 @@ const ( // v3OnionAddr denotes a version 3 Tor (prop224) onion service address. v3OnionAddr addressType = 4 + + // dnsAddr denotes a DNS address. + dnsAddr addressType = 5 ) // WriteElement is a one-stop shop to write the big endian representation of @@ -347,6 +354,11 @@ func WriteElement(w *bytes.Buffer, element interface{}) error { return err } + case *DNSAddress: + if err := WriteDNSAddress(w, e); err != nil { + return err + } + case *OpaqueAddrs: if err := WriteOpaqueAddrs(w, e); err != nil { return err @@ -820,6 +832,34 @@ func ReadElement(r io.Reader, element interface{}) error { } addrBytesRead += v3OnionAddrLen + case dnsAddr: + var hostnameLen [1]byte + _, err := io.ReadFull(addrBuf, hostnameLen[:]) + if err != nil { + return err + } + + hostname := make([]byte, hostnameLen[0]) + _, err = io.ReadFull(addrBuf, hostname) + if err != nil { + return err + } + + var port [2]byte + _, err = io.ReadFull(addrBuf, port[:]) + if err != nil { + return err + } + + address = &DNSAddress{ + Hostname: string(hostname), + Port: binary.BigEndian.Uint16( + port[:], + ), + } + addrBytesRead += dnsAddrOverhead + + uint16(len(hostname)) + default: // If we don't understand this address type, // we just store it along with the remaining diff --git a/lnwire/message_test.go b/lnwire/message_test.go index d42d57911..4f95cb0ef 100644 --- a/lnwire/message_test.go +++ b/lnwire/message_test.go @@ -1000,13 +1000,32 @@ func randV3OnionAddr(t testing.TB, r *rand.Rand) *tor.OnionAddr { return &tor.OnionAddr{OnionService: onionService, Port: addrPort} } +// randDNSAddr generates a random DNS address for testing purposes. +func randDNSAddr(t testing.TB, r *rand.Rand) *lnwire.DNSAddress { + t.Helper() + + var domain [1]byte + _, err := r.Read(domain[:]) + require.NoError(t, err) + + var port [2]byte + _, err = r.Read(port[:]) + require.NoError(t, err, "unable to read port") + + return &lnwire.DNSAddress{ + Hostname: string(domain[:]), + Port: uint16(port[0]), + } +} + func randAddrs(t testing.TB, r *rand.Rand) []net.Addr { tcp4Addr := randTCP4Addr(t, r) tcp6Addr := randTCP6Addr(t, r) v2OnionAddr := randV2OnionAddr(t, r) v3OnionAddr := randV3OnionAddr(t, r) + dnsAddr := randDNSAddr(t, r) - return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr} + return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr, dnsAddr} } func randAlias(r *rand.Rand) lnwire.NodeAlias { diff --git a/lnwire/writer.go b/lnwire/writer.go index fa6247de0..ddf67e8a2 100644 --- a/lnwire/writer.go +++ b/lnwire/writer.go @@ -35,6 +35,9 @@ var ( // ErrNilOpaqueAddrs is returned when the supplied address is nil. ErrNilOpaqueAddrs = errors.New("cannot write nil OpaqueAddrs") + // ErrNilDNSAddress is returned when the supplied address is nil. + ErrNilDNSAddress = errors.New("cannot write nil DNS address") + // ErrNilPublicKey is returned when a nil pubkey is used. ErrNilPublicKey = errors.New("cannot write nil pubkey") @@ -364,6 +367,28 @@ func WriteOnionAddr(buf *bytes.Buffer, addr *tor.OnionAddr) error { return WriteUint16(buf, uint16(addr.Port)) } +// WriteDNSAddress appends the DNS address to the provided buffer. +func WriteDNSAddress(buf *bytes.Buffer, addr *DNSAddress) error { + if addr == nil { + return ErrNilDNSAddress + } + + // Write the descriptor, the hostname length, and the hostname. + if _, err := buf.Write([]byte{byte(dnsAddr)}); err != nil { + return err + } + + if err := WriteUint8(buf, uint8(len(addr.Hostname))); err != nil { + return err + } + + if _, err := buf.WriteString(addr.Hostname); err != nil { + return err + } + + return WriteUint16(buf, addr.Port) +} + // WriteOpaqueAddrs appends the payload of the given OpaqueAddrs to buffer. func WriteOpaqueAddrs(buf *bytes.Buffer, addr *OpaqueAddrs) error { if addr == nil { @@ -397,6 +422,10 @@ func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error { if err := WriteOpaqueAddrs(addrBuf, a); err != nil { return err } + case *DNSAddress: + if err := WriteDNSAddress(addrBuf, a); err != nil { + return err + } default: return ErrNilNetAddress } diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index 3e2550443..bb2bada06 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -559,7 +559,8 @@ func TestWriteOnionAddr(t *testing.T) { } func TestWriteNetAddrs(t *testing.T) { - buf := new(bytes.Buffer) + t.Parallel() + tcpAddr := &net.TCPAddr{ IP: net.IP{127, 0, 0, 1}, Port: 8080, @@ -568,6 +569,10 @@ func TestWriteNetAddrs(t *testing.T) { OnionService: "abcdefghijklmnop.onion", Port: 9065, } + dnsAddr := &DNSAddress{ + Hostname: "example.com", + Port: 8080, + } testCases := []struct { name string @@ -587,24 +592,27 @@ func TestWriteNetAddrs(t *testing.T) { { // Check empty address slice. name: "empty address slice", - addr: []net.Addr{}, expectedErr: nil, // Use two bytes to encode the address size. expectedBytes: []byte{0, 0}, }, { // Check a successful writes of a slice of addresses. - name: "two addresses", - addr: []net.Addr{tcpAddr, onionAddr}, + name: "multiple addresses", + addr: []net.Addr{tcpAddr, onionAddr, dnsAddr}, expectedErr: nil, expectedBytes: []byte{ - // 7 bytes for TCP and 13 bytes for onion. - 0x0, 0x14, + // 7 bytes for TCP and 13 bytes for onion, + // 15 bytes for DNS. + 0x0, 0x23, // TCP address. 0x1, 0x7f, 0x0, 0x0, 0x1, 0x1f, 0x90, // Onion address. 0x3, 0x0, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x54, 0xb6, 0x35, 0xcf, 0x23, 0x69, + // DNS address. + 0x5, 0xb, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x1f, 0x90, }, }, } @@ -612,13 +620,25 @@ func TestWriteNetAddrs(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - oldLen := buf.Len() + buf := new(bytes.Buffer) err := WriteNetAddrs(buf, tc.addr) require.Equal(t, tc.expectedErr, err) - bytesWritten := buf.Bytes()[oldLen:buf.Len()] + bytesWritten := buf.Bytes()[:buf.Len()] require.Equal(t, tc.expectedBytes, bytesWritten) + + if tc.expectedErr != nil { + return + } + + // Read the addresses from the buffer and ensure + // they match the original addresses. + var addrs []net.Addr + err = ReadElement(buf, &addrs) + require.NoError(t, err) + + require.Equal(t, tc.addr, addrs) }) } }