In the second half of 2018, the industry of block chain is gradually fading away from the impetuosity and rationality at the beginning of its development. On the surface, it seems that the demand and status of relevant talents are declining. But in fact, it is the gradual decline of the initial bubble that gives people more attention to the real technology of the block chain.
nat means network address translation. This part of the source code is relatively independent and single, so it is not analyzed here for the time being. You just need to understand the basic functions.
nat has two network protocols, upnp and pmp.
UPnP application scenario (pmp is a protocol similar to upnp)
If users access the Internet through NAT, and need to use such software as BC, eMule and other P2P, then UPnP function will bring great convenience. UPnP can automatically map the port numbers of BC and eMule to the public network, so that users on the public network can also initiate connections on the private side of NAT.
The main function is to provide an interface to map IP + ports of the intranet to IP + ports of routers. This means that the program on the Intranet has an IP address on the extranet, so that users of the public network can access you directly. Otherwise, you need to drill holes in UDP for access.
UDP Protocol in p2p
Nowadays most users run in an intranet environment. The ports monitored in the Intranet environment can not be accessed directly by other public network programs. It needs to go through a process of drilling holes. Only then can the two sides connect. This is called UDP holing.
In the p2p code. peer represents a well-established network link. There may be multiple protocols running on one link. For example, the ETH agreement. Swarm's agreement. Or Whisper's agreement.
Structure of peer
type protoRW struct { Protocol in chan Msg // receices read messages closed <-chan struct{} // receives when peer is shutting down wstart <-chan struct{} // receives when write may start werr chan<- error // for write results offset uint64 w MsgWriter } // Protocol represents a P2P subprotocol implementation. type Protocol struct { // Name should contain the official protocol name, // often a three-letter word. Name string // Version should contain the version number of the protocol. Version uint // Length should contain the number of message codes used // by the protocol. Length uint64 // Run is called in a new groutine when the protocol has been // negotiated with a peer. It should read and write messages from // rw. The Payload for each message must be fully consumed. // // The peer connection is closed when Start returns. It should return // any protocol-level error (such as an I/O error) that is // encountered. Run func(peer *Peer, rw MsgReadWriter) error // NodeInfo is an optional helper method to retrieve protocol specific metadata // about the host node. NodeInfo func() interface{} // PeerInfo is an optional helper method to retrieve protocol specific metadata // about a certain peer in the network. If an info retrieval function is set, // but returns nil, it is assumed that the protocol handshake is still running. PeerInfo func(id discover.NodeID) interface{} } // Peer represents a connected remote node. type Peer struct { rw *conn running map[string]*protoRW //Operating protocols log log.Logger created mclock.AbsTime wg sync.WaitGroup protoErr chan error closed chan struct{} disc chan DiscReason // events receives message send / receive events if set events *event.Feed }
Creation of peer to find protomap supported by current peer according to matching
func newPeer(conn *conn, protocols []Protocol) *Peer { protomap := matchProtocols(protocols, conn.caps, conn) p := &Peer{ rw: conn, running: protomap, created: mclock.Now(), disc: make(chan DiscReason), protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop closed: make(chan struct{}), log: log.New("id", conn.id, "conn", conn.flags), } return p }
The peer startup starts two goroutine threads. One is reading. One is to perform ping operations.
func (p *Peer) run() (remoteRequested bool, err error) { var ( writeStart = make(chan struct{}, 1) //A pipe used to control when it can be written. writeErr = make(chan error, 1) readErr = make(chan error, 1) reason DiscReason // sent to the peer ) p.wg.Add(2) go p.readLoop(readErr) go p.pingLoop() // Start all protocol handlers. writeStart <- struct{}{} //Start all protocols. p.startProtocols(writeStart, writeErr) // Wait for an error or disconnect. loop: for { select { case err = <-writeErr: // A write finished. Allow the next write to start if // there was no error. if err != nil { reason = DiscNetworkError break loop } writeStart <- struct{}{} case err = <-readErr: if r, ok := err.(DiscReason); ok { remoteRequested = true reason = r } else { reason = DiscNetworkError } break loop case err = <-p.protoErr: reason = discReasonForError(err) break loop case err = <-p.disc: break loop } } close(p.closed) p.rw.close(reason) p.wg.Wait() return remoteRequested, err }
startProtocols method, which traverses all protocols.
func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) { p.wg.Add(len(p.running)) for _, proto := range p.running { proto := proto proto.closed = p.closed proto.wstart = writeStart proto.werr = writeErr var rw MsgReadWriter = proto if p.events != nil { rw = newMsgEventer(rw, p.events, p.ID(), proto.Name) } p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version)) // This is equivalent to opening a goroutine for each protocol. Call its Run method. go func() { // The proto.Run(p, rw) method should be a dead cycle. If returned, an error was encountered. err := proto.Run(p, rw) if err == nil { p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version)) err = errProtocolReturned } else if err != io.EOF { p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err) } p.protoErr <- err p.wg.Done() }() } }
Look back at the readLoop method. This method is also a dead cycle. Call p.rw to read an Msg (the RW is actually the object of the frameRLPx mentioned earlier, that is, the object after the subframe. Then the corresponding processing is carried out according to the type of Msg, if the type of Msg is the type of protocol running internally. Then send it to the proto.in queue of the corresponding protocol.
func (p *Peer) readLoop(errc chan<- error) { defer p.wg.Done() for { msg, err := p.rw.ReadMsg() if err != nil { errc <- err return } msg.ReceivedAt = time.Now() if err = p.handle(msg); err != nil { errc <- err return } } } func (p *Peer) handle(msg Msg) error { switch { case msg.Code == pingMsg: msg.Discard() go SendItems(p.rw, pongMsg) case msg.Code == discMsg: var reason [1]DiscReason // This is the last message. We don't need to discard or // check errors because, the connection will be closed after it. rlp.Decode(msg.Payload, &reason) return reason[0] case msg.Code < baseProtocolLength: // ignore other base protocol messages return msg.Discard() default: // it's a subprotocol message proto, err := p.getProto(msg.Code) if err != nil { return fmt.Errorf("msg code out of range: %v", msg.Code) } select { case proto.in <- msg: return nil case <-p.closed: return io.EOF } } return nil }
Look at Ping Loop. This method is simple. It's sending Ping Msg messages to the opposite end on a regular basis.
Finally, look at protoRW's read and write methods. You can see that both read and write are blocking