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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

74.22
/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) {
25✔
25

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

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

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

44
        return result, nil
22✔
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 {
×
54

×
55
        // We'll now examine all addresses that this RPC server is listening
×
56
        // on. If it's a localhost address or a private address, we'll skip it,
×
57
        // otherwise, we'll return an error if macaroons are inactive.
×
58
        for _, addr := range addrs {
×
59
                if IsLoopback(addr.String()) || IsUnix(addr) || IsPrivate(addr) {
×
60
                        continue
×
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
×
79
}
80

81
// parseNetwork parses the network type of the given address.
82
func parseNetwork(addr net.Addr) string {
×
83
        switch addr := addr.(type) {
×
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:
×
89
                if addr.IP.To4() != nil {
×
90
                        return "tcp4"
×
91
                }
×
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) {
×
101
        return net.Listen(parseNetwork(addr), addr.String())
×
102
}
×
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) {
×
107
        return tls.Listen(parseNetwork(addr), addr.String(), config)
×
108
}
×
109

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

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

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

125
// isIPv6Host returns true if the host is IPV6 and false otherwise.
126
func isIPv6Host(host string) bool {
12✔
127
        v6Addr := net.ParseIP(host)
12✔
128
        if v6Addr == nil {
16✔
129
                return false
4✔
130
        }
4✔
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
8✔
135
}
136

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

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

147
// IsUnix returns true if an address describes an Unix socket address.
148
func IsUnix(addr net.Addr) bool {
44✔
149
        return strings.HasPrefix(addr.Network(), "unix")
44✔
150
}
44✔
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
158
func IsPrivate(addr net.Addr) bool {
19✔
159
        switch addr := addr.(type) {
19✔
160
        case *net.TCPAddr:
19✔
161
                // Check 169.254.0.0/16 and fe80::/10.
19✔
162
                if addr.IP.IsLinkLocalUnicast() {
21✔
163
                        return true
2✔
164
                }
2✔
165

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

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

178
                // Check fc00::/7.
179
                return len(addr.IP) == net.IPv6len && addr.IP[0]&0xfe == 0xfc
5✔
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) {
50✔
192

50✔
193
        var parsedNetwork, parsedAddr string
50✔
194

50✔
195
        // Addresses can either be in network://address:port format,
50✔
196
        // network:address:port, address:port, or just port. We want to support
50✔
197
        // all possible types.
50✔
198
        if strings.Contains(strAddress, "://") {
60✔
199
                parts := strings.Split(strAddress, "://")
10✔
200
                parsedNetwork, parsedAddr = parts[0], parts[1]
10✔
201
        } else if strings.Contains(strAddress, ":") {
74✔
202
                parts := strings.Split(strAddress, ":")
24✔
203
                parsedNetwork = parts[0]
24✔
204
                parsedAddr = strings.Join(parts[1:], ":")
24✔
205
        }
24✔
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 {
50✔
210
        case "unix", "unixpacket":
4✔
211
                return net.ResolveUnixAddr(parsedNetwork, parsedAddr)
4✔
212

213
        case "tcp", "tcp4", "tcp6":
12✔
214
                return tcpResolver(
12✔
215
                        parsedNetwork, verifyPort(parsedAddr, defaultPort),
12✔
216
                )
12✔
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:
34✔
223
                // We'll now possibly apply the default port, use the local
34✔
224
                // host short circuit, or parse out an all interfaces listen.
34✔
225
                addrWithPort := verifyPort(strAddress, defaultPort)
34✔
226
                rawHost, rawPort, _ := net.SplitHostPort(addrWithPort)
34✔
227

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

237
                        return &tor.OnionAddr{
8✔
238
                                OnionService: rawHost,
8✔
239
                                Port:         portNum,
8✔
240
                        }, nil
8✔
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) ||
26✔
248
                        isIPv6Host(rawHost) || isUnspecifiedHost(rawHost) {
44✔
249

18✔
250
                        return net.ResolveTCPAddr("tcp", addrWithPort)
18✔
251
                }
18✔
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)
8✔
260
                if err != nil {
12✔
261
                        torErrStr := "tor host is unreachable"
4✔
262
                        if strings.Contains(err.Error(), torErrStr) {
4✔
263
                                return net.ResolveTCPAddr("tcp", addrWithPort)
×
264
                        }
×
265

266
                        return nil, err
4✔
267
                }
268

269
                return addr, nil
4✔
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,
280
        tcpResolver TCPResolver) (*lnwire.NetAddress, error) {
31✔
281

31✔
282
        pubKey, parsedAddr, err := ParseLNAddressPubkey(strAddress)
31✔
283
        if err != nil {
37✔
284
                return nil, err
6✔
285
        }
6✔
286

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

294
        return &lnwire.NetAddress{
22✔
295
                IdentityKey: pubKey,
22✔
296
                Address:     addr,
22✔
297
        }, nil
22✔
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) {
31✔
304
        // Split the address string around the @ sign.
31✔
305
        parts := strings.Split(strAddress, "@")
31✔
306

31✔
307
        // The string is malformed if there are not exactly two parts.
31✔
308
        if len(parts) != 2 {
32✔
309
                return nil, "", fmt.Errorf("invalid lightning address %s: "+
1✔
310
                        "must be of the form <pubkey-hex>@<addr>", strAddress)
1✔
311
        }
1✔
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]
30✔
316

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

324
        // The compressed pubkey should have a length of exactly 33 bytes.
325
        if len(pubKeyBytes) != 33 {
32✔
326
                return nil, "", fmt.Errorf("invalid lightning address pubkey: "+
3✔
327
                        "length must be 33 bytes, found %d", len(pubKeyBytes))
3✔
328
        }
3✔
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)
26✔
333
        if err != nil {
27✔
334
                return nil, "", fmt.Errorf("invalid lightning address "+
1✔
335
                        "pubkey: %w", err)
1✔
336
        }
1✔
337

338
        return pubKey, parsedAddr, nil
25✔
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 {
46✔
346
        host, port, err := net.SplitHostPort(address)
46✔
347
        if err != nil {
70✔
348
                // If the address itself is just an integer, then we'll assume
24✔
349
                // that we're mapping this directly to a localhost:port pair.
24✔
350
                // This ensures we maintain the legacy behavior.
24✔
351
                if _, err := strconv.Atoi(address); err == nil {
26✔
352
                        return net.JoinHostPort("localhost", address)
2✔
353
                }
2✔
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, "[") {
24✔
361
                        return address + ":" + defaultPort
2✔
362
                }
2✔
363
                return net.JoinHostPort(address, defaultPort)
20✔
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 == "" {
24✔
369
                return ":" + defaultPort
2✔
370
        }
2✔
371

372
        return address
20✔
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