github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/lndc/listener.go (about)

     1  package lndc
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"net"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/mit-dci/lit/crypto/koblitz"
    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 *koblitz.PrivateKey
    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 lndc scheme
    36  // during both initial connection establishment and data transfer.
    37  func NewListener(localStatic *koblitz.PrivateKey, port int) (*Listener,
    38  	error) {
    39  	// since this is a listener, it is sufficient that we just pass the
    40  	// port and then add the later stuff here
    41  	str := ":" + strconv.Itoa(port) // colonize!
    42  	addr, err := net.ResolveTCPAddr("tcp", str)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	l, err := net.ListenTCP("tcp", addr)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	lndcListener := &Listener{
    53  		localStatic:   localStatic,
    54  		tcp:           l,
    55  		handshakeSema: make(chan struct{}, defaultHandshakes),
    56  		conns:         make(chan maybeConn),
    57  		quit:          make(chan struct{}),
    58  	}
    59  
    60  	for i := 0; i < defaultHandshakes; i++ {
    61  		lndcListener.handshakeSema <- struct{}{}
    62  	}
    63  
    64  	go lndcListener.listen()
    65  
    66  	return lndcListener, nil
    67  }
    68  
    69  // listen accepts connection from the underlying tcp conn, then performs
    70  // the brontinde handshake procedure asynchronously. A maximum of
    71  // defaultHandshakes will be active at any given time.
    72  //
    73  // NOTE: This method must be run as a goroutine.
    74  func (l *Listener) listen() {
    75  	for {
    76  		select {
    77  		case <-l.handshakeSema:
    78  		case <-l.quit:
    79  			return
    80  		}
    81  
    82  		conn, err := l.tcp.Accept()
    83  		if err != nil {
    84  			l.rejectConn(err)
    85  			l.handshakeSema <- struct{}{}
    86  			continue
    87  		}
    88  
    89  		go l.doHandshake(conn)
    90  	}
    91  }
    92  
    93  // doHandshake asynchronously performs the lndc handshake, so that it does
    94  // not block the main accept loop. This prevents peers that delay writing to the
    95  // connection from block other connection attempts.
    96  func (l *Listener) doHandshake(conn net.Conn) {
    97  	defer func() { l.handshakeSema <- struct{}{} }()
    98  
    99  	select {
   100  	case <-l.quit:
   101  		return
   102  	default:
   103  	}
   104  
   105  	lndcConn := &Conn{
   106  		conn:  conn,
   107  		noise: NewNoiseMachine(false, l.localStatic),
   108  	}
   109  
   110  	// We'll ensure that we get ActOne from the remote peer in a timely
   111  	// manner. If they don't respond within 1s, then we'll kill the
   112  	// connection.
   113  	conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
   114  
   115  	// Attempt to carry out the first act of the handshake protocol. If the
   116  	// connecting node doesn't know our long-term static public key, then
   117  	// this portion will fail with a non-nil error.
   118  	var actOne [ActOneSize]byte
   119  	if _, err := io.ReadFull(conn, actOne[:]); err != nil {
   120  		lndcConn.conn.Close()
   121  		l.rejectConn(err)
   122  		return
   123  	}
   124  	if err := lndcConn.noise.RecvActOne(actOne); err != nil {
   125  		lndcConn.conn.Close()
   126  		l.rejectConn(err)
   127  		return
   128  	}
   129  	// Next, progress the handshake processes by sending over our ephemeral
   130  	// key for the session along with an authenticating tag.
   131  	actTwo, err := lndcConn.noise.GenActTwo()
   132  	if err != nil {
   133  		lndcConn.conn.Close()
   134  		l.rejectConn(err)
   135  		return
   136  	}
   137  	if _, err := conn.Write(actTwo[:]); err != nil {
   138  		lndcConn.conn.Close()
   139  		l.rejectConn(err)
   140  		return
   141  	}
   142  
   143  	select {
   144  	case <-l.quit:
   145  		return
   146  	default:
   147  	}
   148  
   149  	// We'll ensure that we get ActTwo from the remote peer in a timely
   150  	// manner. If they don't respond within 1 second, then we'll kill the
   151  	// connection.
   152  	conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
   153  
   154  	// Finally, finish the handshake processes by reading and decrypting
   155  	// the connection peer's static public key. If this succeeds then both
   156  	// sides have mutually authenticated each other.
   157  	var actThree [ActThreeSize]byte
   158  	if _, err := io.ReadFull(conn, actThree[:]); err != nil {
   159  		lndcConn.conn.Close()
   160  		l.rejectConn(err)
   161  		return
   162  	}
   163  	if err := lndcConn.noise.RecvActThree(actThree); err != nil {
   164  		lndcConn.conn.Close()
   165  		l.rejectConn(err)
   166  		return
   167  	}
   168  
   169  	// We'll reset the deadline as it's no longer critical beyond the
   170  	// initial handshake.
   171  	conn.SetReadDeadline(time.Time{})
   172  
   173  	l.acceptConn(lndcConn)
   174  }
   175  
   176  // maybeConn holds either a lndc connection or an error returned from the
   177  // handshake.
   178  type maybeConn struct {
   179  	conn *Conn
   180  	err  error
   181  }
   182  
   183  // acceptConn returns a connection that successfully performed a handshake.
   184  func (l *Listener) acceptConn(conn *Conn) {
   185  	select {
   186  	case l.conns <- maybeConn{conn: conn}:
   187  	case <-l.quit:
   188  	}
   189  }
   190  
   191  // rejectConn returns any errors encountered during connection or handshake.
   192  func (l *Listener) rejectConn(err error) {
   193  	select {
   194  	case l.conns <- maybeConn{err: err}:
   195  	case <-l.quit:
   196  	}
   197  }
   198  
   199  // Accept waits for and returns the next connection to the listener. All
   200  // incoming connections are authenticated via the three act lndc
   201  // key-exchange scheme. This function will fail with a non-nil error in the
   202  // case that either the handshake breaks down, or the remote peer doesn't know
   203  // our static public key.
   204  //
   205  // Part of the net.Listener interface.
   206  func (l *Listener) Accept() (net.Conn, error) {
   207  	select {
   208  	case result := <-l.conns:
   209  		return result.conn, result.err
   210  	case <-l.quit:
   211  		return nil, errors.New("lndc connection closed")
   212  	}
   213  }
   214  
   215  // Close closes the listener.  Any blocked Accept operations will be unblocked
   216  // and return errors.
   217  //
   218  // Part of the net.Listener interface.
   219  func (l *Listener) Close() error {
   220  	select {
   221  	case <-l.quit:
   222  	default:
   223  		close(l.quit)
   224  	}
   225  
   226  	return l.tcp.Close()
   227  }
   228  
   229  // Addr returns the listener's network address.
   230  //
   231  // Part of the net.Listener interface.
   232  func (l *Listener) Addr() net.Addr {
   233  	return l.tcp.Addr()
   234  }