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 }