• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.78
/lncfg/address.go
1
package lncfg
2

3
import (
4
        "context"
5
        "crypto/tls"
6
        "encoding/hex"
7
        "fmt"
8
        "net"
9
        "strconv"
10
        "strings"
11

12
        "github.com/btcsuite/btcd/btcec/v2"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
        "github.com/lightningnetwork/lnd/tor"
15
)
16

17
// TCPResolver is a function signature that resolves an address on a given
18
// network.
19
type TCPResolver = func(network, addr string) (*net.TCPAddr, error)
20

21
// NormalizeAddresses returns a new slice with all the passed addresses
22
// normalized with the given default port and all duplicates removed.
23
func NormalizeAddresses(addrs []string, defaultPort string,
24
        tcpResolver TCPResolver) ([]net.Addr, error) {
3✔
25

3✔
26
        result := make([]net.Addr, 0, len(addrs))
3✔
27
        seen := map[string]struct{}{}
3✔
28

3✔
29
        for _, addr := range addrs {
6✔
30
                parsedAddr, err := ParseAddressString(
3✔
31
                        addr, defaultPort, tcpResolver,
3✔
32
                )
3✔
33
                if err != nil {
3✔
UNCOV
34
                        return nil, fmt.Errorf("parse address %s failed: %w",
×
UNCOV
35
                                addr, err)
×
UNCOV
36
                }
×
37

38
                if _, ok := seen[parsedAddr.String()]; !ok {
6✔
39
                        result = append(result, parsedAddr)
3✔
40
                        seen[parsedAddr.String()] = struct{}{}
3✔
41
                }
3✔
42
        }
43

44
        return result, nil
3✔
45
}
46

47
// EnforceSafeAuthentication enforces "safe" authentication taking into account
48
// the interfaces that the RPC servers are listening on, and if macaroons and
49
// TLS is activated or not. To protect users from using dangerous config
50
// combinations, we'll prevent disabling authentication if the server is
51
// listening on a public interface.
52
func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive,
53
        tlsActive bool) error {
3✔
54

3✔
55
        // We'll now examine all addresses that this RPC server is listening
3✔
56
        // on. If it's a localhost address or a private address, we'll skip it,
3✔
57
        // otherwise, we'll return an error if macaroons are inactive.
3✔
58
        for _, addr := range addrs {
6✔
59
                if IsLoopback(addr.String()) || IsUnix(addr) || IsPrivate(addr) {
6✔
60
                        continue
3✔
61
                }
62

63
                if !macaroonsActive {
×
64
                        return fmt.Errorf("detected RPC server listening on "+
×
65
                                "publicly reachable interface %v with "+
×
66
                                "authentication disabled! Refusing to start "+
×
67
                                "with --no-macaroons specified", addr)
×
68
                }
×
69

70
                if !tlsActive {
×
71
                        return fmt.Errorf("detected RPC server listening on "+
×
72
                                "publicly reachable interface %v with "+
×
73
                                "encryption disabled! Refusing to start "+
×
74
                                "with --no-rest-tls specified", addr)
×
75
                }
×
76
        }
77

78
        return nil
3✔
79
}
80

81
// parseNetwork parses the network type of the given address.
82
func parseNetwork(addr net.Addr) string {
3✔
83
        switch addr := addr.(type) {
3✔
84
        // TCP addresses resolved through net.ResolveTCPAddr give a default
85
        // network of "tcp", so we'll map back the correct network for the given
86
        // address. This ensures that we can listen on the correct interface
87
        // (IPv4 vs IPv6).
88
        case *net.TCPAddr:
3✔
89
                if addr.IP.To4() != nil {
6✔
90
                        return "tcp4"
3✔
91
                }
3✔
92
                return "tcp6"
×
93

94
        default:
×
95
                return addr.Network()
×
96
        }
97
}
98

99
// ListenOnAddress creates a listener that listens on the given address.
100
func ListenOnAddress(addr net.Addr) (net.Listener, error) {
3✔
101
        return net.Listen(parseNetwork(addr), addr.String())
3✔
102
}
3✔
103

104
// TLSListenOnAddress creates a TLS listener that listens on the given address.
105
func TLSListenOnAddress(addr net.Addr,
106
        config *tls.Config) (net.Listener, error) {
3✔
107
        return tls.Listen(parseNetwork(addr), addr.String(), config)
3✔
108
}
3✔
109

110
// IsLoopback returns true if an address describes a loopback interface.
111
func IsLoopback(host string) bool {
3✔
112
        if strings.Contains(host, "localhost") {
6✔
113
                return true
3✔
114
        }
3✔
115

116
        rawHost, _, _ := net.SplitHostPort(host)
3✔
117
        addr := net.ParseIP(rawHost)
3✔
118
        if addr == nil {
6✔
119
                return false
3✔
120
        }
3✔
121

122
        return addr.IsLoopback()
3✔
123
}
124

125
// isIPv6Host returns true if the host is IPV6 and false otherwise.
126
func isIPv6Host(host string) bool {
3✔
127
        v6Addr := net.ParseIP(host)
3✔
128
        if v6Addr == nil {
3✔
UNCOV
129
                return false
×
UNCOV
130
        }
×
131

132
        // The documentation states that if the IP address is an IPv6 address,
133
        // then To4() will return nil.
134
        return v6Addr.To4() == nil
3✔
135
}
136

137
// isUnspecifiedHost returns true if the host IP is considered unspecified.
138
func isUnspecifiedHost(host string) bool {
3✔
139
        addr := net.ParseIP(host)
3✔
140
        if addr == nil {
3✔
UNCOV
141
                return false
×
UNCOV
142
        }
×
143

144
        return addr.IsUnspecified()
3✔
145
}
146

147
// IsUnix returns true if an address describes an Unix socket address.
148
func IsUnix(addr net.Addr) bool {
3✔
149
        return strings.HasPrefix(addr.Network(), "unix")
3✔
150
}
3✔
151

152
// IsPrivate returns true if the address is private. The definitions are,
153
//
154
//        https://en.wikipedia.org/wiki/Link-local_address
155
//        https://en.wikipedia.org/wiki/Multicast_address
156
//        Local IPv4 addresses, https://tools.ietf.org/html/rfc1918
157
//        Local IPv6 addresses, https://tools.ietf.org/html/rfc4193
UNCOV
158
func IsPrivate(addr net.Addr) bool {
×
UNCOV
159
        switch addr := addr.(type) {
×
UNCOV
160
        case *net.TCPAddr:
×
UNCOV
161
                // Check 169.254.0.0/16 and fe80::/10.
×
UNCOV
162
                if addr.IP.IsLinkLocalUnicast() {
×
UNCOV
163
                        return true
×
UNCOV
164
                }
×
165

166
                // Check 224.0.0.0/4 and ff00::/8.
UNCOV
167
                if addr.IP.IsLinkLocalMulticast() {
×
UNCOV
168
                        return true
×
UNCOV
169
                }
×
170

171
                // Check 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16.
UNCOV
172
                if ip4 := addr.IP.To4(); ip4 != nil {
×
UNCOV
173
                        return ip4[0] == 10 ||
×
UNCOV
174
                                (ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
×
UNCOV
175
                                (ip4[0] == 192 && ip4[1] == 168)
×
UNCOV
176
                }
×
177

178
                // Check fc00::/7.
UNCOV
179
                return len(addr.IP) == net.IPv6len && addr.IP[0]&0xfe == 0xfc
×
180

181
        default:
×
182
                return false
×
183
        }
184
}
185

186
// ParseAddressString converts an address in string format to a net.Addr that is
187
// compatible with lnd. UDP is not supported because lnd needs reliable
188
// connections. We accept a custom function to resolve any TCP addresses so
189
// that caller is able control exactly how resolution is performed.
190
func ParseAddressString(strAddress string, defaultPort string,
191
        tcpResolver TCPResolver) (net.Addr, error) {
3✔
192

3✔
193
        var parsedNetwork, parsedAddr string
3✔
194

3✔
195
        // Addresses can either be in network://address:port format,
3✔
196
        // network:address:port, address:port, or just port. We want to support
3✔
197
        // all possible types.
3✔
198
        if strings.Contains(strAddress, "://") {
3✔
UNCOV
199
                parts := strings.Split(strAddress, "://")
×
UNCOV
200
                parsedNetwork, parsedAddr = parts[0], parts[1]
×
201
        } else if strings.Contains(strAddress, ":") {
6✔
202
                parts := strings.Split(strAddress, ":")
3✔
203
                parsedNetwork = parts[0]
3✔
204
                parsedAddr = strings.Join(parts[1:], ":")
3✔
205
        }
3✔
206

207
        // Only TCP and Unix socket addresses are valid. We can't use IP or
208
        // UDP only connections for anything we do in lnd.
209
        switch parsedNetwork {
3✔
UNCOV
210
        case "unix", "unixpacket":
×
UNCOV
211
                return net.ResolveUnixAddr(parsedNetwork, parsedAddr)
×
212

UNCOV
213
        case "tcp", "tcp4", "tcp6":
×
UNCOV
214
                return tcpResolver(
×
UNCOV
215
                        parsedNetwork, verifyPort(parsedAddr, defaultPort),
×
UNCOV
216
                )
×
217

218
        case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram":
×
219
                return nil, fmt.Errorf("only TCP or unix socket "+
×
220
                        "addresses are supported: %s", parsedAddr)
×
221

222
        default:
3✔
223
                // We'll now possibly apply the default port, use the local
3✔
224
                // host short circuit, or parse out an all interfaces listen.
3✔
225
                addrWithPort := verifyPort(strAddress, defaultPort)
3✔
226
                rawHost, rawPort, _ := net.SplitHostPort(addrWithPort)
3✔
227

3✔
228
                // If we reach this point, then we'll check to see if we have
3✔
229
                // an onion addresses, if so, we can directly pass the raw
3✔
230
                // address and port to create the proper address.
3✔
231
                if tor.IsOnionHost(rawHost) {
6✔
232
                        portNum, err := strconv.Atoi(rawPort)
3✔
233
                        if err != nil {
3✔
234
                                return nil, err
×
235
                        }
×
236

237
                        return &tor.OnionAddr{
3✔
238
                                OnionService: rawHost,
3✔
239
                                Port:         portNum,
3✔
240
                        }, nil
3✔
241
                }
242

243
                // Otherwise, we'll attempt the resolve the host. The Tor
244
                // resolver is unable to resolve local addresses,
245
                // IPv6 addresses, or the all-interfaces address, so we'll use
246
                // the system resolver instead for those.
247
                if rawHost == "" || IsLoopback(rawHost) ||
3✔
248
                        isIPv6Host(rawHost) || isUnspecifiedHost(rawHost) {
6✔
249

3✔
250
                        return net.ResolveTCPAddr("tcp", addrWithPort)
3✔
251
                }
3✔
252

253
                // If we've reached this point, then it's possible that this
254
                // resolve returns an error if it isn't able to resolve the
255
                // host. For example, local entries in /etc/hosts will fail to
256
                // be resolved by Tor. In order to handle this case, we'll fall
257
                // back to the normal system resolver if we fail with an
258
                // identifiable error.
259
                addr, err := tcpResolver("tcp", addrWithPort)
3✔
260
                if err != nil {
3✔
UNCOV
261
                        torErrStr := "tor host is unreachable"
×
UNCOV
262
                        if strings.Contains(err.Error(), torErrStr) {
×
263
                                return net.ResolveTCPAddr("tcp", addrWithPort)
×
264
                        }
×
265

UNCOV
266
                        return nil, err
×
267
                }
268

269
                return addr, nil
3✔
270
        }
271
}
272

273
// ParseLNAddressString converts a string of the form <pubkey>@<addr> into an
274
// lnwire.NetAddress. The <pubkey> must be presented in hex, and result in a
275
// 33-byte, compressed public key that lies on the secp256k1 curve. The <addr>
276
// may be any address supported by ParseAddressString. If no port is specified,
277
// the defaultPort will be used. Any tcp addresses that need resolving will be
278
// resolved using the custom TCPResolver.
279
func ParseLNAddressString(strAddress string, defaultPort string,
UNCOV
280
        tcpResolver TCPResolver) (*lnwire.NetAddress, error) {
×
UNCOV
281

×
UNCOV
282
        pubKey, parsedAddr, err := ParseLNAddressPubkey(strAddress)
×
UNCOV
283
        if err != nil {
×
UNCOV
284
                return nil, err
×
UNCOV
285
        }
×
286

287
        // Finally, parse the address string using our generic address parser.
UNCOV
288
        addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver)
×
UNCOV
289
        if err != nil {
×
UNCOV
290
                return nil, fmt.Errorf("invalid lightning address address: %w",
×
UNCOV
291
                        err)
×
UNCOV
292
        }
×
293

UNCOV
294
        return &lnwire.NetAddress{
×
UNCOV
295
                IdentityKey: pubKey,
×
UNCOV
296
                Address:     addr,
×
UNCOV
297
        }, nil
×
298
}
299

300
// ParseLNAddressPubkey converts a string of the form <pubkey>@<addr> into two
301
// pieces: the pubkey bytes and an addr string. It validates that the pubkey
302
// is of a valid form.
303
func ParseLNAddressPubkey(strAddress string) (*btcec.PublicKey, string, error) {
3✔
304
        // Split the address string around the @ sign.
3✔
305
        parts := strings.Split(strAddress, "@")
3✔
306

3✔
307
        // The string is malformed if there are not exactly two parts.
3✔
308
        if len(parts) != 2 {
3✔
UNCOV
309
                return nil, "", fmt.Errorf("invalid lightning address %s: "+
×
UNCOV
310
                        "must be of the form <pubkey-hex>@<addr>", strAddress)
×
UNCOV
311
        }
×
312

313
        // Now, take the first portion as the hex pubkey, and the latter as the
314
        // address string.
315
        parsedPubKey, parsedAddr := parts[0], parts[1]
3✔
316

3✔
317
        // Decode the hex pubkey to get the raw compressed pubkey bytes.
3✔
318
        pubKeyBytes, err := hex.DecodeString(parsedPubKey)
3✔
319
        if err != nil {
3✔
UNCOV
320
                return nil, "", fmt.Errorf("invalid lightning address "+
×
UNCOV
321
                        "pubkey: %w", err)
×
UNCOV
322
        }
×
323

324
        // The compressed pubkey should have a length of exactly 33 bytes.
325
        if len(pubKeyBytes) != 33 {
3✔
UNCOV
326
                return nil, "", fmt.Errorf("invalid lightning address pubkey: "+
×
UNCOV
327
                        "length must be 33 bytes, found %d", len(pubKeyBytes))
×
UNCOV
328
        }
×
329

330
        // Parse the pubkey bytes to verify that it corresponds to valid public
331
        // key on the secp256k1 curve.
332
        pubKey, err := btcec.ParsePubKey(pubKeyBytes)
3✔
333
        if err != nil {
3✔
UNCOV
334
                return nil, "", fmt.Errorf("invalid lightning address "+
×
UNCOV
335
                        "pubkey: %w", err)
×
UNCOV
336
        }
×
337

338
        return pubKey, parsedAddr, nil
3✔
339
}
340

341
// verifyPort makes sure that an address string has both a host and a port. If
342
// there is no port found, the default port is appended. If the address is just
343
// a port, then we'll assume that the user is using the short cut to specify a
344
// localhost:port address.
345
func verifyPort(address string, defaultPort string) string {
3✔
346
        host, port, err := net.SplitHostPort(address)
3✔
347
        if err != nil {
6✔
348
                // If the address itself is just an integer, then we'll assume
3✔
349
                // that we're mapping this directly to a localhost:port pair.
3✔
350
                // This ensures we maintain the legacy behavior.
3✔
351
                if _, err := strconv.Atoi(address); err == nil {
3✔
UNCOV
352
                        return net.JoinHostPort("localhost", address)
×
UNCOV
353
                }
×
354

355
                // Otherwise, we'll assume that the address just failed to
356
                // attach its own port, so we'll use the default port. In the
357
                // case of IPv6 addresses, if the host is already surrounded by
358
                // brackets, then we'll avoid using the JoinHostPort function,
359
                // since it will always add a pair of brackets.
360
                if strings.HasPrefix(address, "[") {
3✔
UNCOV
361
                        return address + ":" + defaultPort
×
UNCOV
362
                }
×
363
                return net.JoinHostPort(address, defaultPort)
3✔
364
        }
365

366
        // In the case that both the host and port are empty, we'll use the
367
        // default port.
368
        if host == "" && port == "" {
3✔
UNCOV
369
                return ":" + defaultPort
×
UNCOV
370
        }
×
371

372
        return address
3✔
373
}
374

375
// ClientAddressDialer creates a gRPC dialer that can also dial unix socket
376
// addresses instead of just TCP addresses.
377
func ClientAddressDialer(defaultPort string) func(context.Context,
378
        string) (net.Conn, error) {
×
379

×
380
        return func(ctx context.Context, addr string) (net.Conn, error) {
×
381
                parsedAddr, err := ParseAddressString(
×
382
                        addr, defaultPort, net.ResolveTCPAddr,
×
383
                )
×
384
                if err != nil {
×
385
                        return nil, err
×
386
                }
×
387

388
                d := net.Dialer{}
×
389
                return d.DialContext(
×
390
                        ctx, parsedAddr.Network(), parsedAddr.String(),
×
391
                )
×
392
        }
393
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc