netadd: split ValidatenodeAnn into sig and field checks
Check that the node ann doesnt contain more than 1 DNS addr. This will ensure that we now start rejecting new node announcements with multiple DNS addrs since this check is called in the gossiper before persisting a node ann to our local graph. It also validates the DNS fields according to BOLT #7 specs.
This commit is contained in:
1 parent
670b26ba3d
commit
f0a20c9d04
3 files changed
+182
-2
No files matched your search
@@ -1,10 +1,29 @@
|
|||||||
package lnwire
|
package lnwire
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrEmptyDNSHostname is returned when a DNS hostname is empty.
|
||||||
|
ErrEmptyDNSHostname = errors.New("hostname cannot be empty")
|
||||||
|
|
||||||
|
// ErrZeroPort is returned when a DNS port is zero.
|
||||||
|
ErrZeroPort = errors.New("port cannot be zero")
|
||||||
|
|
||||||
|
// ErrHostnameTooLong is returned when a DNS hostname exceeds 255 bytes.
|
||||||
|
ErrHostnameTooLong = errors.New("DNS hostname length exceeds limit " +
|
||||||
|
"of 255 bytes")
|
||||||
|
|
||||||
|
// ErrInvalidHostnameCharacter is returned when a DNS hostname contains
|
||||||
|
// an invalid character.
|
||||||
|
ErrInvalidHostnameCharacter = errors.New("hostname contains invalid " +
|
||||||
|
"character")
|
||||||
|
)
|
||||||
|
|
||||||
// DNSAddress is used to represent a DNS address of a node.
|
// DNSAddress is used to represent a DNS address of a node.
|
||||||
type DNSAddress struct {
|
type DNSAddress struct {
|
||||||
// Hostname is the DNS hostname of the address. This MUST only contain
|
// Hostname is the DNS hostname of the address. This MUST only contain
|
||||||
@@ -29,3 +48,41 @@ func (d *DNSAddress) Network() string {
|
|||||||
func (d *DNSAddress) String() string {
|
func (d *DNSAddress) String() string {
|
||||||
return net.JoinHostPort(d.Hostname, strconv.Itoa(int(d.Port)))
|
return net.JoinHostPort(d.Hostname, strconv.Itoa(int(d.Port)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateDNSAddr validates that the DNS hostname is not empty and contains
|
||||||
|
// only ASCII characters and of max length 255 characters and port is non zero
|
||||||
|
// according to BOLT #7.
|
||||||
|
func ValidateDNSAddr(hostname string, port uint16) error {
|
||||||
|
if hostname == "" {
|
||||||
|
return ErrEmptyDNSHostname
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per BOLT 7, ports must not be zero for type 5 address (DNS address).
|
||||||
|
if port == 0 {
|
||||||
|
return ErrZeroPort
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hostname) > 255 {
|
||||||
|
return fmt.Errorf("%w: DNS hostname length %d",
|
||||||
|
ErrHostnameTooLong, len(hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if hostname contains only ASCII characters.
|
||||||
|
for i, r := range hostname {
|
||||||
|
// Check for valid hostname characters, excluding ASCII control
|
||||||
|
// characters (0-31), spaces, underscores, delete character
|
||||||
|
// (127), and the special characters (like /, \, @, #, $, etc.).
|
||||||
|
if !((r >= 'a' && r <= 'z') ||
|
||||||
|
(r >= 'A' && r <= 'Z') ||
|
||||||
|
(r >= '0' && r <= '9') ||
|
||||||
|
r == '-' ||
|
||||||
|
r == '.') {
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: hostname '%s' contains invalid "+
|
||||||
|
"character '%c' at position %d",
|
||||||
|
ErrInvalidHostnameCharacter, hostname, r, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package lnwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestValidateDNSAddr tests hostname and port validation per BOLT #7.
|
||||||
|
func TestValidateDNSAddr(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
hostname string
|
||||||
|
port uint16
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty hostname",
|
||||||
|
hostname: "",
|
||||||
|
port: 9735,
|
||||||
|
err: ErrEmptyDNSHostname,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero port",
|
||||||
|
hostname: "example.com",
|
||||||
|
port: 0,
|
||||||
|
err: ErrZeroPort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname too long",
|
||||||
|
hostname: strings.Repeat("a", 256),
|
||||||
|
port: 9735,
|
||||||
|
err: fmt.Errorf("%w: DNS hostname length 256",
|
||||||
|
ErrHostnameTooLong),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with invalid ASCII space",
|
||||||
|
hostname: "exa mple.com",
|
||||||
|
port: 9735,
|
||||||
|
err: fmt.Errorf("%w: hostname 'exa mple.com' contains "+
|
||||||
|
"invalid character ' ' at position 3",
|
||||||
|
ErrInvalidHostnameCharacter),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with invalid ASCII underscore",
|
||||||
|
hostname: "example_node.com",
|
||||||
|
port: 9735,
|
||||||
|
err: fmt.Errorf("%w: hostname 'example_node.com' "+
|
||||||
|
"contains invalid character '_' at position 7",
|
||||||
|
ErrInvalidHostnameCharacter),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname with non-ASCII character",
|
||||||
|
hostname: "example❄️.com",
|
||||||
|
port: 9735,
|
||||||
|
err: fmt.Errorf("%w: hostname 'example❄️.com' "+
|
||||||
|
"contains invalid character '❄' at position 7",
|
||||||
|
ErrInvalidHostnameCharacter),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid hostname",
|
||||||
|
hostname: "example.com",
|
||||||
|
port: 9735,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid hostname with numbers",
|
||||||
|
hostname: "node101.example.com",
|
||||||
|
port: 9735,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid hostname with hyphens",
|
||||||
|
hostname: "my-node.example-domain.com",
|
||||||
|
port: 9735,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := ValidateDNSAddr(tc.hostname, tc.port)
|
||||||
|
require.Equal(t, err, tc.err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package netann
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"net"
|
"net"
|
||||||
@@ -81,10 +82,45 @@ func SignNodeAnnouncement(signer lnwallet.MessageSigner,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNodeAnn validates the node announcement by ensuring that the
|
// ValidateNodeAnn validates the fields and signature of a node announcement.
|
||||||
|
func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error {
|
||||||
|
err := ValidateNodeAnnFields(a)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid node announcement fields: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateNodeAnnSignature(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNodeAnnFields validates the fields of a node announcement.
|
||||||
|
func ValidateNodeAnnFields(a *lnwire.NodeAnnouncement) error {
|
||||||
|
// Check that it only has at most one DNS address.
|
||||||
|
hasDNSAddr := false
|
||||||
|
for _, addr := range a.Addresses {
|
||||||
|
dnsAddr, ok := addr.(*lnwire.DNSAddress)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasDNSAddr {
|
||||||
|
return errors.New("node announcement contains " +
|
||||||
|
"multiple DNS addresses. Only one is allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDNSAddr = true
|
||||||
|
|
||||||
|
err := lnwire.ValidateDNSAddr(dnsAddr.Hostname, dnsAddr.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNodeAnnSignature validates the node announcement by ensuring that the
|
||||||
// attached signature is needed a signature of the node announcement under the
|
// attached signature is needed a signature of the node announcement under the
|
||||||
// specified node public key.
|
// specified node public key.
|
||||||
func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error {
|
func ValidateNodeAnnSignature(a *lnwire.NodeAnnouncement) error {
|
||||||
// Reconstruct the data of announcement which should be covered by the
|
// Reconstruct the data of announcement which should be covered by the
|
||||||
// signature so we can verify the signature shortly below
|
// signature so we can verify the signature shortly below
|
||||||
data, err := a.DataToSign()
|
data, err := a.DataToSign()
|
||||||
|
|||||||
Reference in new issue
Block a user