• 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

62.22
/brontide/listener.go
1
package brontide
2

3
import (
4
        "errors"
5
        "fmt"
6
        "io"
7
        "net"
8
        "time"
9

10
        "github.com/lightningnetwork/lnd/keychain"
11
)
12

13
// defaultHandshakes is the maximum number of handshakes that can be done in
14
// parallel.
15
const defaultHandshakes = 1000
16

17
// Listener is an implementation of a net.Conn which executes an authenticated
18
// key exchange and message encryption protocol dubbed "Machine" after
19
// initial connection acceptance. See the Machine struct for additional
20
// details w.r.t the handshake and encryption scheme used within the
21
// connection.
22
type Listener struct {
23
        localStatic keychain.SingleKeyECDH
24

25
        tcp *net.TCPListener
26

27
        handshakeSema chan struct{}
28
        conns         chan maybeConn
29
        quit          chan struct{}
30
}
31

32
// A compile-time assertion to ensure that Conn meets the net.Listener interface.
33
var _ net.Listener = (*Listener)(nil)
34

35
// NewListener returns a new net.Listener which enforces the Brontide scheme
36
// during both initial connection establishment and data transfer.
37
func NewListener(localStatic keychain.SingleKeyECDH,
38
        listenAddr string) (*Listener, error) {
3✔
39

3✔
40
        addr, err := net.ResolveTCPAddr("tcp", listenAddr)
3✔
41
        if err != nil {
3✔
42
                return nil, err
×
43
        }
×
44

45
        l, err := net.ListenTCP("tcp", addr)
3✔
46
        if err != nil {
3✔
47
                return nil, err
×
48
        }
×
49

50
        brontideListener := &Listener{
3✔
51
                localStatic:   localStatic,
3✔
52
                tcp:           l,
3✔
53
                handshakeSema: make(chan struct{}, defaultHandshakes),
3✔
54
                conns:         make(chan maybeConn),
3✔
55
                quit:          make(chan struct{}),
3✔
56
        }
3✔
57

3✔
58
        for i := 0; i < defaultHandshakes; i++ {
6✔
59
                brontideListener.handshakeSema <- struct{}{}
3✔
60
        }
3✔
61

62
        go brontideListener.listen()
3✔
63

3✔
64
        return brontideListener, nil
3✔
65
}
66

67
// listen accepts connection from the underlying tcp conn, then performs
68
// the brontinde handshake procedure asynchronously. A maximum of
69
// defaultHandshakes will be active at any given time.
70
//
71
// NOTE: This method must be run as a goroutine.
72
func (l *Listener) listen() {
3✔
73
        for {
6✔
74
                select {
3✔
75
                case <-l.handshakeSema:
3✔
76
                case <-l.quit:
3✔
77
                        return
3✔
78
                }
79

80
                conn, err := l.tcp.Accept()
3✔
81
                if err != nil {
6✔
82
                        l.rejectConn(err)
3✔
83
                        l.handshakeSema <- struct{}{}
3✔
84
                        continue
3✔
85
                }
86

87
                go l.doHandshake(conn)
3✔
88
        }
89
}
90

91
// rejectedConnErr is a helper function that prepends the remote address of the
92
// failed connection attempt to the original error message.
UNCOV
93
func rejectedConnErr(err error, remoteAddr string) error {
×
UNCOV
94
        return fmt.Errorf("unable to accept connection from %v: %w", remoteAddr,
×
UNCOV
95
                err)
×
UNCOV
96
}
×
97

98
// doHandshake asynchronously performs the brontide handshake, so that it does
99
// not block the main accept loop. This prevents peers that delay writing to the
100
// connection from block other connection attempts.
101
func (l *Listener) doHandshake(conn net.Conn) {
3✔
102
        defer func() { l.handshakeSema <- struct{}{} }()
6✔
103

104
        select {
3✔
105
        case <-l.quit:
×
106
                return
×
107
        default:
3✔
108
        }
109

110
        remoteAddr := conn.RemoteAddr().String()
3✔
111

3✔
112
        brontideConn := &Conn{
3✔
113
                conn:  conn,
3✔
114
                noise: NewBrontideMachine(false, l.localStatic, nil),
3✔
115
        }
3✔
116

3✔
117
        // We'll ensure that we get ActOne from the remote peer in a timely
3✔
118
        // manner. If they don't respond within handshakeReadTimeout, then
3✔
119
        // we'll kill the connection.
3✔
120
        err := conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
3✔
121
        if err != nil {
3✔
122
                brontideConn.conn.Close()
×
123
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
124
                return
×
125
        }
×
126

127
        // Attempt to carry out the first act of the handshake protocol. If the
128
        // connecting node doesn't know our long-term static public key, then
129
        // this portion will fail with a non-nil error.
130
        var actOne [ActOneSize]byte
3✔
131
        if _, err := io.ReadFull(conn, actOne[:]); err != nil {
3✔
UNCOV
132
                brontideConn.conn.Close()
×
UNCOV
133
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
UNCOV
134
                return
×
UNCOV
135
        }
×
136
        if err := brontideConn.noise.RecvActOne(actOne); err != nil {
3✔
137
                brontideConn.conn.Close()
×
138
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
139
                return
×
140
        }
×
141

142
        // Next, progress the handshake processes by sending over our ephemeral
143
        // key for the session along with an authenticating tag.
144
        actTwo, err := brontideConn.noise.GenActTwo()
3✔
145
        if err != nil {
3✔
146
                brontideConn.conn.Close()
×
147
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
148
                return
×
149
        }
×
150
        if _, err := conn.Write(actTwo[:]); err != nil {
3✔
151
                brontideConn.conn.Close()
×
152
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
153
                return
×
154
        }
×
155

156
        select {
3✔
157
        case <-l.quit:
×
158
                return
×
159
        default:
3✔
160
        }
161

162
        // We'll ensure that we get ActTwo from the remote peer in a timely
163
        // manner. If they don't respond within handshakeReadTimeout, then
164
        // we'll kill the connection.
165
        err = conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
3✔
166
        if err != nil {
3✔
167
                brontideConn.conn.Close()
×
168
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
169
                return
×
170
        }
×
171

172
        // Finally, finish the handshake processes by reading and decrypting
173
        // the connection peer's static public key. If this succeeds then both
174
        // sides have mutually authenticated each other.
175
        var actThree [ActThreeSize]byte
3✔
176
        if _, err := io.ReadFull(conn, actThree[:]); err != nil {
3✔
177
                brontideConn.conn.Close()
×
178
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
179
                return
×
180
        }
×
181
        if err := brontideConn.noise.RecvActThree(actThree); err != nil {
3✔
182
                brontideConn.conn.Close()
×
183
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
184
                return
×
185
        }
×
186

187
        // We'll reset the deadline as it's no longer critical beyond the
188
        // initial handshake.
189
        err = conn.SetReadDeadline(time.Time{})
3✔
190
        if err != nil {
3✔
191
                brontideConn.conn.Close()
×
192
                l.rejectConn(rejectedConnErr(err, remoteAddr))
×
193
                return
×
194
        }
×
195

196
        l.acceptConn(brontideConn)
3✔
197
}
198

199
// maybeConn holds either a brontide connection or an error returned from the
200
// handshake.
201
type maybeConn struct {
202
        conn *Conn
203
        err  error
204
}
205

206
// acceptConn returns a connection that successfully performed a handshake.
207
func (l *Listener) acceptConn(conn *Conn) {
3✔
208
        select {
3✔
209
        case l.conns <- maybeConn{conn: conn}:
3✔
210
        case <-l.quit:
×
211
        }
212
}
213

214
// rejectConn returns any errors encountered during connection or handshake.
215
func (l *Listener) rejectConn(err error) {
3✔
216
        select {
3✔
217
        case l.conns <- maybeConn{err: err}:
×
218
        case <-l.quit:
3✔
219
        }
220
}
221

222
// Accept waits for and returns the next connection to the listener. All
223
// incoming connections are authenticated via the three act Brontide
224
// key-exchange scheme. This function will fail with a non-nil error in the
225
// case that either the handshake breaks down, or the remote peer doesn't know
226
// our static public key.
227
//
228
// Part of the net.Listener interface.
229
func (l *Listener) Accept() (net.Conn, error) {
3✔
230
        select {
3✔
231
        case result := <-l.conns:
3✔
232
                return result.conn, result.err
3✔
233
        case <-l.quit:
3✔
234
                return nil, errors.New("brontide connection closed")
3✔
235
        }
236
}
237

238
// Close closes the listener.  Any blocked Accept operations will be unblocked
239
// and return errors.
240
//
241
// Part of the net.Listener interface.
242
func (l *Listener) Close() error {
3✔
243
        select {
3✔
244
        case <-l.quit:
×
245
        default:
3✔
246
                close(l.quit)
3✔
247
        }
248

249
        return l.tcp.Close()
3✔
250
}
251

252
// Addr returns the listener's network address.
253
//
254
// Part of the net.Listener interface.
255
func (l *Listener) Addr() net.Addr {
3✔
256
        return l.tcp.Addr()
3✔
257
}
3✔
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