Implement custom handshake

Hello.

I am building a blockchain prototype (github.com/0xpolygon/polygon-sdk) with libp2p as the network stack. I use libp2p to establish the connection and then I run grpc on top of the stream as the communication protocol.

However, I need to do some custom handshake after the connection is established to exchange important metadata and do some validations (i.e. both clients are on the same chain). This is a pre requisite before any other grpc streams can be started.

Is there any support for this kind of behaviour or are there any best practices you would recommend to implement this?

So far this are the two options that I am considering:

  1. After each Notifieer.Connected message do a handshake stream. If the handshake fails remove the peer from the host. However, at any time during this period the peers could connect to any of the other streams. Besides, the auth lifecycle would be all over the place. Ideally, I would like something that succeeds or fails in the Connect call.
  2. Use a custom security transport that embeds Noise as a pre-process and after that performs the handshake with the raw net.Conn interface.
1 Like

Hey @ferranbt,

Welcome to the libp2p community!

There are several solutions I can think of, depending on the security tradeoff you want:

  1. If you don’t mind libp2p protocols running before the connection is authenticated (e.g. libp2p identify, pubsub, DHT, etc.), you can implement a custom protocol that subscribes to PeerConnectednessChanged events on the eventbus, initiates the challenge by opening a stream, and kills the connection if the challenge fails.

    • All other application-specific protocols would wait until this protocol has authenticated the peer.
    • You would need to implement a “session manager” component, that the authentication protocol reports to, and that other protocols gate on to know when they are authorised to service a peer.
    • You could send custom events on the eventbus to signal the state changes.
  2. If you don’t mind the handshake running and the peer ID of the responder being revealed to the other party, prior to your app-specific authentication, you could use a ConnectionGater and implement a hook for InterceptUpgraded.

    • The problem is that you get a network.Conn and not a mux.MuxedConn, so I’m not sure how the other peer would accept the stream.
    • Note that these are raw streams, not managed by the libp2p host. I haven’t seen anybody using libp2p in this manner, so not sure if it would work.
  3. The earliest point at which you can challenge the peer is by implementing a custom secure channel and injecting it into the Host: test-plans/main.go at master · libp2p/test-plans · GitHub

  4. You could use private networks if a PSK arrangement would work for you.

If none of these work for you, you could open an issue in go-libp2p and propose a feature.

Hope that guides you a little!
RaĂşl.

1 Like

Thank you for your reply.

I followed the first approach and everything seems to be working fine. However, I have another issue now.

I have a custom “dial manager” to limit the number of connected peers. It dials new peers from a queue when there are less than MaxPeers. The dial queue is filled with values from PeersAdded callback from the dht module. The aforementioned handshake protocol is called every time a new peer is connected.

However, it seems like the Dht module also makes Connect calls to other nodes (I would guess this connections are done to exchange kademlia peers). These calls are also piped into the handshake protocol. Thus, peers not dialed with the dial manager/queue are included as valid peers.

Sorry for the delay. Not sure what the question is.

Are you seeing that the DHT is connecting to peers but not calling the PeerAdded event handler?

Looping in @adin to see if he can help here.

Are you referring to https://github.com/libp2p/go-libp2p-kbucket/blob/b90e3fed3255e131058ac337a19beb2ad85da43f/table.go#L52 or something else? This function is called when peers are added to the routing table, however as part of standard operation we may connect to peers or receive connections from peers who are not suitable for membership in our routing table (e.g. the peer is behind a relay, the bucket they would go into is full, they are DHT clients and not servers, etc.)

go-libp2p has an eventbus where you can listen on for events like when peers have connected and finished with the identify protocol (e.g. https://github.com/libp2p/go-libp2p-core/blob/master/event/identify.go). Other common eventbus types can be seen in the event package.

@raul
is connectionGater workable ? for implementing custom handshake methods …

i tried

func (cg *MyConnectionGater) InterceptUpgraded(conn network.Conn) (allow bool, reason control.DisconnectReason) {
	//TODO implement me
	//panic("implement me")
	fmt.Println("Status : ", conn.ConnState().Transport)

	if conn.Stat().Direction == 1 {
		challengeStream, err := conn.NewStream(context.Background())
		if err != nil {
			fmt.Println("Failed to start stream")
			return false, 0
		}
		_, err = challengeStream.Write([]byte("hello"))
		if err != nil {
			fmt.Println("Failed to send challenge")
			return false, 0
		}
	} else {
		streams := conn.GetStreams()
		if len(streams) == 0 {
			return false, 0
		}
		challengeStream := streams[0]
		var buf []byte
		_, err := challengeStream.Read(buf)
		if err != nil {
			fmt.Println("Failed to receive challenge")
			return false, 0
		}
	}

	return true, 0
}

but it fails to open a stream

is there any ways to authorise a peer with CA certs