흐름을 따라가보자

func (srv *Server) listenLoop() {
	srv.log.Debug("TCP listener up", "addr", srv.listener.Addr())
	// ...
	// 무한 루프로 들어오는 연결을 goroutine으로 처리  
	for {
		// ...
		for {
			fd, err = srv.listener.Accept()
            //...
			break
		}

		remoteIP := netutil.AddrIP(fd.RemoteAddr())
        // ...
		go func() {
			srv.SetupConn(fd, inboundConn, nil)
			slots <- struct{}{}
		}()
	}
}
// SetupConn runs the handshakes and attempts to add the connection
// as a peer. It returns when the connection has been added as a peer
// or the handshakes have failed.
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
	c := &conn{fd: fd, flags: flags, cont: make(chan error)}
    // ...    
	err := srv.setupConn(c, flags, dialDest)
	// ... 
	return err
}
  • SetupConn에서 srv.setupConn을 호출
func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) error {
	// Prevent leftover pending conns from entering the handshake.
	// ...
	
	if dialDest != nil {
        // 우리는 dest의 publickey를 알 수 있어야만 메시지를 보낼 수 있다.
		dialPubkey := new(ecdsa.PublicKey)
		if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
            // 그래서 secp256k1 의 publickey가 dialDest에 없다면 error가 발생한다.
			err = errors.New("dial destination doesn't have a secp256k1 public key")
			srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
			return err
		}
	}
	// ... 

    // 밑에서 doEncHandshake가 어떻게 Handshake를 진행하는지 살펴보자
	// 이 과정의 수행 결과로 c.session에 두 노드 사이의 Secrets이 설정된다.
	remotePubkey, err := c.doEncHandshake(srv.PrivateKey)
    // ...
		c.node = dialDest
	// ...
	// 서로가 가진 프로토콜 정보를 확인한다.
	// 만약 일치하지 않는다면 Handshake는 실패한다.
	phs, err := c.doProtoHandshake(srv.ourHandshake)
	
	// 여기서 checkpointAddPeer채널에 conn을 넣어준다.
	// func (srv *Server) run() 는 conn을 받아서 
    // p := srv.launchPeer(c)
	// peers[c.node.ID()] = p 를 수행한다. 이렇게 p2p peer가 저장된다.
    err = srv.checkpoint(c, srv.checkpointAddPeer)
    //...
	return nil
}

잠시 ECIES를 알아보자

doEncHandshake에서 실제 handshke 까지 살펴보자

func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
	t.conn.SetDeadline(time.Now().Add(handshakeTimeout))
	return t.conn.Handshake(prv)
}

// Handshake performs the handshake. This must be called before any data is written
// or read from the connection.
func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
	var (
		sec Secrets
		err error
		h   handshakeState
	)
    // ...
	    // 두 노드가 handshake를 하기 위해서는 둘 만이 알수 있는 암호화가 필요하다.
        // 이 과정에 사용되는 정보를 geth에서는 Secrets이라는 struct로 만들었다.
		// type Secrets struct {
		//    AES, MAC              []byte
		//    EgressMAC, IngressMAC hash.Hash
        //    remote                *ecdsa.PublicKey
        // }
	    // runInitiator Secrets을 return한다.
		sec, err = h.runInitiator(c.conn, prv, c.dialDest)
    // ...
	// runInitiator에서 secrets를 받아오면 c.session를 설정한다.
	c.InitWithSecrets(sec)
	c.session.rbuf = h.rbuf
	c.session.wbuf = h.wbuf
	return sec.remote, err
}
// prv is the local client's private key.
func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) {
	h.initiator = true
	// 상대측의 public key를 가져온다
	//PublicKey{
	//  X:      remote.X,
	//  Y:      remote.Y,
	//  Curve:  remote.Curve,
	//  Params: ParamsFromCurve(remote.Curve),
	//}
	h.remote = ecies.ImportECDSAPublic(remote)
    //  EIP-8에 맞는 authMsg를 생성한다.
	authMsg, err := h.makeAuthMsg(prv)
	// 생성된 authMsg를 EIP-8에 맞게 Encoding & Encrypt를 진행한다.
	authPacket, err := h.sealEIP8(authMsg)
    // ...
	if _, err = conn.Write(authPacket); err != nil {
		return s, err
	}

	authRespMsg := new(authRespV4)
	// auth 응답을 decoding한다.
	authRespPacket, err := h.readMsg(authRespMsg, prv, conn)
	if err != nil {
		return s, err
	}
	// respd에서  ecdhe-random-pubk,respNonce를 추출한다.
	if err := h.handleAuthResp(authRespMsg); err != nil {
		return s, err
	}
    // Hanshake가 정상적으로 종료되었다면, 이 과정에서 생긴 정보들을 모아서 Secrets을 생성한다.
	return h.secrets(authPacket, authRespPacket)
}
  • 위의 과정을 거치면 Adding p2p peer

0개의 댓글

Powered by GraphCDN, the GraphQL CDN