Skip to content

Commit

Permalink
multi: add dns_hostname_address field
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamedawnallah committed Feb 5, 2025
1 parent fcdd9bc commit fb721bc
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 43 deletions.
60 changes: 40 additions & 20 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,26 +325,28 @@ type Config struct {
// loadConfig function. We need to expose the 'raw' strings so the
// command line library can access them.
// Only the parsed net.Addrs should be used!
RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
RawExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"`
ExternalHosts []string `long:"externalhosts" description:"Add a hostname:port that should be periodically resolved to announce IPs for. If a port is not specified, the default (9735) will be used."`
RPCListeners []net.Addr
RESTListeners []net.Addr
RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
Listeners []net.Addr
ExternalIPs []net.Addr
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
DisableRest bool `long:"norest" description:"Disable REST API"`
DisableRestTLS bool `long:"no-rest-tls" description:"Disable TLS for REST connections"`
WSPingInterval time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"`
WSPongWait time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
AddPeers []string `long:"addpeer" description:"Specify peers to connect to first"`
MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."`
RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
RawExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"`
RawExternalDNSHostnameAddress string `long:"external-dns-hostname" description:"Specify a DNS hostname for the node's external address. If no port is provided, the default (9735) is used."`
ExternalHosts []string `long:"externalhosts" description:"Add a hostname:port that should be periodically resolved to announce IPs for. If a port is not specified, the default (9735) will be used."`
RPCListeners []net.Addr
RESTListeners []net.Addr
RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
Listeners []net.Addr
ExternalIPs []net.Addr
ExternalDNSHostnameAddress *lnwire.DNSHostnameAddress
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
DisableRest bool `long:"norest" description:"Disable REST API"`
DisableRestTLS bool `long:"no-rest-tls" description:"Disable TLS for REST connections"`
WSPingInterval time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"`
WSPongWait time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
AddPeers []string `long:"addpeer" description:"Specify peers to connect to first"`
MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."`

DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <global-level>,<subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`

Expand Down Expand Up @@ -1555,6 +1557,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
ltndLog.Infof("Listening on the p2p interface is disabled!")
cfg.Listeners = nil
cfg.ExternalIPs = nil
cfg.ExternalDNSHostnameAddress = nil
} else {

// Add default port to all listener addresses if needed and remove
Expand All @@ -1578,6 +1581,23 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
return nil, err
}

// Add default port to external dnsh hostname address if needed.
if cfg.RawExternalDNSHostnameAddress != "" {
addr, err := parseAddr(
cfg.RawExternalDNSHostnameAddress, cfg.net,
)
if err != nil {
return nil, err
}
dnsAddr, ok := addr.(*lnwire.DNSHostnameAddress)
if !ok {
return nil, fmt.Errorf("failed to cast "+
"address to lnwire.DNSHostnameAddr: "+
"got %T", addr)
}
cfg.ExternalDNSHostnameAddress = dnsAddr
}

// For the p2p port it makes no sense to listen to an Unix socket.
// Also, we would need to refactor the brontide listener to support
// that.
Expand Down
1 change: 1 addition & 0 deletions discovery/gossiper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1988,6 +1988,7 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement,
HaveNodeAnnouncement: true,
LastUpdate: timestamp,
Addresses: msg.Addresses,
DNSHostnameAddress: msg.DNSHostnameAddress,
PubKeyBytes: msg.NodeID,
Alias: msg.Alias.String(),
AuthSigBytes: msg.Signature.ToSignatureBytes(),
Expand Down
70 changes: 70 additions & 0 deletions graph/db/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net"

"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)

Expand All @@ -26,6 +27,9 @@ const (

// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 3

// dnsHostnameAddr denotes a DNS hostname address.
dnsHostnameAddr addressType = 4
)

// encodeTCPAddr serializes a TCP address into its compact raw bytes
Expand Down Expand Up @@ -121,6 +125,47 @@ func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error {
return nil
}

// encodeDNSHostnameAddr serializes a DNS hostname address into its compact raw
// bytes representation. It writes the address type, hostname length, hostname,
// and port (in big-endian order) to the writer. The function validates that the
// hostname is non-empty and does not exceed 255 characters. Returns an error if
// any part of the serialization fails.
func encodeDNSHostnameAddr(w io.Writer, addr *lnwire.DNSHostnameAddress) error {
// Validate the hostname.
if len(addr.Hostname) == 0 {
return errors.New("hostname cannot be empty")
}
if len(addr.Hostname) > 255 {
return errors.New("hostname exceeds maximum length of 255 " +
"characters")
}

// Write the address type.
if _, err := w.Write([]byte{byte(dnsHostnameAddr)}); err != nil {
return err
}

// Write the length of the hostname.
hostnameLen := byte(len(addr.Hostname))
if _, err := w.Write([]byte{hostnameLen}); err != nil {
return err
}

// Write the hostname bytes.
if _, err := w.Write([]byte(addr.Hostname)); err != nil {
return err
}

// Write the port in big-endian order.
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}

return nil
}

// DeserializeAddr reads the serialized raw representation of an address and
// deserializes it into the actual address. This allows us to avoid address
// resolution within the channeldb package.
Expand Down Expand Up @@ -200,6 +245,29 @@ func DeserializeAddr(r io.Reader) (net.Addr, error) {
OnionService: onionService,
Port: port,
}

case dnsHostnameAddr:
var hostnameLen byte
err := binary.Read(r, binary.BigEndian, &hostnameLen)
if err != nil {
return nil, err
}

hostname := make([]byte, hostnameLen)
if _, err := r.Read(hostname); err != nil {
return nil, err
}

var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}

address = &lnwire.DNSHostnameAddress{
Hostname: string(hostname),
Port: int(binary.BigEndian.Uint16(port[:])),
}

default:
return nil, ErrUnknownAddressType
}
Expand All @@ -215,6 +283,8 @@ func SerializeAddr(w io.Writer, address net.Addr) error {
return encodeTCPAddr(w, addr)
case *tor.OnionAddr:
return encodeOnionAddr(w, addr)
case *lnwire.DNSHostnameAddress:
return encodeDNSHostnameAddr(w, addr)
default:
return ErrUnknownAddressType
}
Expand Down
19 changes: 16 additions & 3 deletions graph/db/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -3972,13 +3972,20 @@ func putLightningNode(nodeBucket kvdb.RwBucket, aliasBucket kvdb.RwBucket, // no
return err
}

numAddresses := uint16(len(node.Addresses))
var allAddresses []net.Addr
var numAddresses uint16
if node.DNSHostnameAddress != nil {
allAddresses = append(allAddresses, node.DNSHostnameAddress)
numAddresses += 1
}
allAddresses = append(allAddresses, node.Addresses...)
numAddresses += uint16(len(node.Addresses))
byteOrder.PutUint16(scratch[:2], numAddresses)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}

for _, address := range node.Addresses {
for _, address := range allAddresses {
if err := SerializeAddr(&b, address); err != nil {
return err
}
Expand Down Expand Up @@ -4171,14 +4178,20 @@ func deserializeLightningNode(r io.Reader) (models.LightningNode, error) {
numAddresses := int(byteOrder.Uint16(scratch[:2]))

var addresses []net.Addr
var dnsHostnameAddress *lnwire.DNSHostnameAddress
for i := 0; i < numAddresses; i++ {
address, err := DeserializeAddr(r)
if err != nil {
return models.LightningNode{}, err
}
addresses = append(addresses, address)
if dnsAddr, ok := address.(*lnwire.DNSHostnameAddress); ok {
dnsHostnameAddress = dnsAddr
} else {
addresses = append(addresses, address)
}
}
node.Addresses = addresses
node.DNSHostnameAddress = dnsHostnameAddress

node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil {
Expand Down
18 changes: 11 additions & 7 deletions graph/db/models/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type LightningNode struct {
// Address is the TCP address this node is reachable over.
Addresses []net.Addr

// DNSHostnameAddress is the DNS hostname address for this node.
DNSHostnameAddress *lnwire.DNSHostnameAddress

// Color is the selected color for the node.
Color color.RGBA

Expand Down Expand Up @@ -109,13 +112,14 @@ func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement,
}

nodeAnn := &lnwire.NodeAnnouncement{
Features: l.Features.RawFeatureVector,
NodeID: l.PubKeyBytes,
RGBColor: l.Color,
Alias: alias,
Addresses: l.Addresses,
Timestamp: uint32(l.LastUpdate.Unix()),
ExtraOpaqueData: l.ExtraOpaqueData,
Features: l.Features.RawFeatureVector,
NodeID: l.PubKeyBytes,
RGBColor: l.Color,
Alias: alias,
Addresses: l.Addresses,
DNSHostnameAddress: l.DNSHostnameAddress,
Timestamp: uint32(l.LastUpdate.Unix()),
ExtraOpaqueData: l.ExtraOpaqueData,
}

if !signed {
Expand Down
26 changes: 26 additions & 0 deletions lnrpc/peersrpc/peers_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,32 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context,
}
}

if req.DnsHostnameAddress != "" {
addr, err := s.cfg.ParseAddr(req.DnsHostnameAddress)
if err != nil {
return nil, fmt.Errorf("invalid dns hostname address "+
"value: %w", err)
}
dnsAddr, ok := addr.(*lnwire.DNSHostnameAddress)
if !ok {
return nil, fmt.Errorf("failed to cast address to "+
"lnwire.DNSHostnameAddr: got %T", addr)
}

if dnsAddr != currentNodeAnn.DNSHostnameAddress {
resp.Ops = append(resp.Ops, &lnrpc.Op{
Entity: "dns_hostname_address",
Actions: []string{
fmt.Sprintf("changed to %v", dnsAddr),
},
})
nodeModifiers = append(
nodeModifiers,
netann.NodeAnnSetDNSHostnameAddress(dnsAddr),
)
}
}

if len(req.AddressUpdates) > 0 {
newAddrs, ops, err := s.updateAddresses(
currentNodeAnn.Addresses,
Expand Down
26 changes: 26 additions & 0 deletions lnwire/dns_hostname_addr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lnwire

import (
"fmt"
"net"
)

// DNSHostnameAddress is a custom implementation of the net.Addr interface.
type DNSHostnameAddress struct {
Hostname string
Port int
}

// A compile-time check to ensure that DNSHostnameAddr implements
// the net.Addr interface.
var _ net.Addr = (*DNSHostnameAddress)(nil)

// Network returns the network type, e.g., "tcp".
func (d *DNSHostnameAddress) Network() string {
return "tcp"
}

// String returns the address in the form "hostname:port".
func (d *DNSHostnameAddress) String() string {
return fmt.Sprintf("%s:%d", d.Hostname, d.Port)
}
Loading

0 comments on commit fb721bc

Please sign in to comment.