brontide/listener: allow parallel handshakes
This commit is contained in:
parent
9b729654f6
commit
782a8088eb
@ -1,6 +1,7 @@
|
|||||||
package brontide
|
package brontide
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@ -8,6 +9,10 @@ import (
|
|||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defaultHandshakes is the maximum number of handshakes that can be done in
|
||||||
|
// parallel.
|
||||||
|
const defaultHandshakes = 1000
|
||||||
|
|
||||||
// Listener is an implementation of a net.Conn which executes an authenticated
|
// Listener is an implementation of a net.Conn which executes an authenticated
|
||||||
// key exchange and message encryption protocol dubbed "Machine" after
|
// key exchange and message encryption protocol dubbed "Machine" after
|
||||||
// initial connection acceptance. See the Machine struct for additional
|
// initial connection acceptance. See the Machine struct for additional
|
||||||
@ -17,6 +22,10 @@ type Listener struct {
|
|||||||
localStatic *btcec.PrivateKey
|
localStatic *btcec.PrivateKey
|
||||||
|
|
||||||
tcp *net.TCPListener
|
tcp *net.TCPListener
|
||||||
|
|
||||||
|
handshakeSema chan struct{}
|
||||||
|
conns chan maybeConn
|
||||||
|
quit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile-time assertion to ensure that Conn meets the net.Listener interface.
|
// A compile-time assertion to ensure that Conn meets the net.Listener interface.
|
||||||
@ -36,23 +45,57 @@ func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Listener{
|
brontideListener := &Listener{
|
||||||
localStatic: localStatic,
|
localStatic: localStatic,
|
||||||
tcp: l,
|
tcp: l,
|
||||||
}, nil
|
handshakeSema: make(chan struct{}, defaultHandshakes),
|
||||||
|
conns: make(chan maybeConn),
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < defaultHandshakes; i++ {
|
||||||
|
brontideListener.handshakeSema <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
go brontideListener.listen()
|
||||||
|
|
||||||
|
return brontideListener, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept waits for and returns the next connection to the listener. All
|
// listen accepts connection from the underlying tcp conn, then performs
|
||||||
// incoming connections are authenticated via the three act Brontide
|
// the brontinde handshake procedure asynchronously. A maximum of
|
||||||
// key-exchange scheme. This function will fail with a non-nil error in the
|
// defaultHandshakes will be active at any given time.
|
||||||
// case that either the handshake breaks down, or the remote peer doesn't know
|
|
||||||
// our static public key.
|
|
||||||
//
|
//
|
||||||
// Part of the net.Listener interface.
|
// NOTE: This method must be run as a goroutine.
|
||||||
func (l *Listener) Accept() (net.Conn, error) {
|
func (l *Listener) listen() {
|
||||||
conn, err := l.tcp.Accept()
|
for {
|
||||||
if err != nil {
|
select {
|
||||||
return nil, err
|
case <-l.handshakeSema:
|
||||||
|
case <-l.quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := l.tcp.Accept()
|
||||||
|
if err != nil {
|
||||||
|
l.rejectConn(err)
|
||||||
|
l.handshakeSema <- struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.doHandshake(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// doHandshake asynchronously performs the brontide handshake, so that it does
|
||||||
|
// not block the main accept loop. This prevents peers that delay writing to the
|
||||||
|
// connection from block other connection attempts.
|
||||||
|
func (l *Listener) doHandshake(conn net.Conn) {
|
||||||
|
defer func() { l.handshakeSema <- struct{}{} }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-l.quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
brontideConn := &Conn{
|
brontideConn := &Conn{
|
||||||
@ -71,11 +114,13 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
var actOne [ActOneSize]byte
|
var actOne [ActOneSize]byte
|
||||||
if _, err := io.ReadFull(conn, actOne[:]); err != nil {
|
if _, err := io.ReadFull(conn, actOne[:]); err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err := brontideConn.noise.RecvActOne(actOne); err != nil {
|
if err := brontideConn.noise.RecvActOne(actOne); err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, progress the handshake processes by sending over our ephemeral
|
// Next, progress the handshake processes by sending over our ephemeral
|
||||||
@ -83,11 +128,19 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
actTwo, err := brontideConn.noise.GenActTwo()
|
actTwo, err := brontideConn.noise.GenActTwo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if _, err := conn.Write(actTwo[:]); err != nil {
|
if _, err := conn.Write(actTwo[:]); err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-l.quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll ensure that we get ActTwo from the remote peer in a timely
|
// We'll ensure that we get ActTwo from the remote peer in a timely
|
||||||
@ -101,18 +154,59 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
var actThree [ActThreeSize]byte
|
var actThree [ActThreeSize]byte
|
||||||
if _, err := io.ReadFull(conn, actThree[:]); err != nil {
|
if _, err := io.ReadFull(conn, actThree[:]); err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err := brontideConn.noise.RecvActThree(actThree); err != nil {
|
if err := brontideConn.noise.RecvActThree(actThree); err != nil {
|
||||||
brontideConn.conn.Close()
|
brontideConn.conn.Close()
|
||||||
return nil, err
|
l.rejectConn(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll reset the deadline as it's no longer critical beyond the
|
// We'll reset the deadline as it's no longer critical beyond the
|
||||||
// initial handshake.
|
// initial handshake.
|
||||||
conn.SetReadDeadline(time.Time{})
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
return brontideConn, nil
|
l.acceptConn(brontideConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeConn holds either a brontide connection or an error returned from the
|
||||||
|
// handshake.
|
||||||
|
type maybeConn struct {
|
||||||
|
conn *Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptConn returns a connection that successfully performed a handshake.
|
||||||
|
func (l *Listener) acceptConn(conn *Conn) {
|
||||||
|
select {
|
||||||
|
case l.conns <- maybeConn{conn: conn}:
|
||||||
|
case <-l.quit:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rejectConn returns any errors encountered during connection or handshake.
|
||||||
|
func (l *Listener) rejectConn(err error) {
|
||||||
|
select {
|
||||||
|
case l.conns <- maybeConn{err: err}:
|
||||||
|
case <-l.quit:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept waits for and returns the next connection to the listener. All
|
||||||
|
// incoming connections are authenticated via the three act Brontide
|
||||||
|
// key-exchange scheme. This function will fail with a non-nil error in the
|
||||||
|
// case that either the handshake breaks down, or the remote peer doesn't know
|
||||||
|
// our static public key.
|
||||||
|
//
|
||||||
|
// Part of the net.Listener interface.
|
||||||
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case result := <-l.conns:
|
||||||
|
return result.conn, result.err
|
||||||
|
case <-l.quit:
|
||||||
|
return nil, errors.New("brontide connection closed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the listener. Any blocked Accept operations will be unblocked
|
// Close closes the listener. Any blocked Accept operations will be unblocked
|
||||||
@ -120,6 +214,12 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
//
|
//
|
||||||
// Part of the net.Listener interface.
|
// Part of the net.Listener interface.
|
||||||
func (l *Listener) Close() error {
|
func (l *Listener) Close() error {
|
||||||
|
select {
|
||||||
|
case <-l.quit:
|
||||||
|
default:
|
||||||
|
close(l.quit)
|
||||||
|
}
|
||||||
|
|
||||||
return l.tcp.Close()
|
return l.tcp.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user