Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

noise: use Noise Extension to negotiate the muxer during the handshake #1813

Merged
merged 5 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion p2p/security/noise/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func makeTransport(b *testing.B) *Transport {
if err != nil {
b.Fatal(err)
}
tpt, err := New(priv)
tpt, err := New(priv, nil)
if err != nil {
b.Fatalf("error constructing transport: %v", err)
}
Expand Down
79 changes: 68 additions & 11 deletions p2p/security/noise/pb/payload.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions p2p/security/noise/pb/payload.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pb;

message NoiseExtensions {
repeated bytes webtransport_certhashes = 1;
repeated string stream_muxers = 2;
}

message NoiseHandshakePayload {
Expand Down
12 changes: 11 additions & 1 deletion p2p/security/noise/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type secureSession struct {
prologue []byte

initiatorEarlyDataHandler, responderEarlyDataHandler EarlyDataHandler

// ConnectionState holds state information releated to the secureSession entity.
connectionState network.ConnectionState
}

// newSecureSession creates a Noise session over the given insecureConn Conn, using
Expand Down Expand Up @@ -110,7 +113,7 @@ func (s *secureSession) RemotePublicKey() crypto.PubKey {
}

func (s *secureSession) ConnState() network.ConnectionState {
return network.ConnectionState{}
return s.connectionState
}

func (s *secureSession) SetDeadline(t time.Time) error {
Expand All @@ -128,3 +131,10 @@ func (s *secureSession) SetWriteDeadline(t time.Time) error {
func (s *secureSession) Close() error {
return s.insecureConn.Close()
}

func SessionWithConnState(s *secureSession, muxer string) *secureSession {
if s != nil {
s.connectionState.NextProto = muxer
}
return s
}
71 changes: 66 additions & 5 deletions p2p/security/noise/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import (
"github.com/libp2p/go-libp2p/core/canonicallog"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/libp2p/go-libp2p/p2p/security/noise/pb"

manet "github.com/multiformats/go-multiaddr/net"
)

// ID is the protocol ID for noise
const ID = "/noise"
const (
ID = "/noise"
maxProtoNum = 100
)

var _ sec.SecureTransport = &Transport{}

Expand All @@ -22,38 +27,51 @@ var _ sec.SecureTransport = &Transport{}
type Transport struct {
localID peer.ID
privateKey crypto.PrivKey
muxers []string
}

// New creates a new Noise transport using the given private key as its
// libp2p identity key.
func New(privkey crypto.PrivKey) (*Transport, error) {
func New(privkey crypto.PrivKey, muxers []protocol.ID) (*Transport, error) {
julian88110 marked this conversation as resolved.
Show resolved Hide resolved
localID, err := peer.IDFromPrivateKey(privkey)
if err != nil {
return nil, err
}

smuxers := make([]string, 0, len(muxers))
for _, muxer := range muxers {
smuxers = append(smuxers, string(muxer))
}

return &Transport{
localID: localID,
privateKey: privkey,
muxers: smuxers,
}, nil
}

// SecureInbound runs the Noise handshake as the responder.
// If p is empty, connections from any peer are accepted.
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
c, err := newSecureSession(t, ctx, insecure, p, nil, nil, nil, false)
responderEDH := newTransportEDH(t)
c, err := newSecureSession(t, ctx, insecure, p, nil, nil, responderEDH, false)
if err != nil {
addr, maErr := manet.FromNetAddr(insecure.RemoteAddr())
if maErr == nil {
canonicallog.LogPeerStatus(100, p, addr, "handshake_failure", "noise", "err", err.Error())
}
}
return c, err
return SessionWithConnState(c, responderEDH.MatchMuxers(false)), err
}

// SecureOutbound runs the Noise handshake as the initiator.
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
return newSecureSession(t, ctx, insecure, p, nil, nil, nil, true)
initiatorEDH := newTransportEDH(t)
c, err := newSecureSession(t, ctx, insecure, p, nil, initiatorEDH, nil, true)
if err != nil {
return c, err
}
return SessionWithConnState(c, initiatorEDH.MatchMuxers(true)), err
}

func (t *Transport) WithSessionOptions(opts ...SessionOption) (sec.SecureTransport, error) {
Expand All @@ -65,3 +83,46 @@ func (t *Transport) WithSessionOptions(opts ...SessionOption) (sec.SecureTranspo
}
return st, nil
}

func matchMuxers(initiatorMuxers, responderMuxers []string) string {
for _, muxer := range responderMuxers {
for _, initMuxer := range initiatorMuxers {
if initMuxer == muxer {
return muxer
}
}
}
return ""
}

type transportEarlyDataHandler struct {
julian88110 marked this conversation as resolved.
Show resolved Hide resolved
transport *Transport
receivedMuxers []string
}

var _ EarlyDataHandler = &transportEarlyDataHandler{}

func newTransportEDH(t *Transport) *transportEarlyDataHandler {
return &transportEarlyDataHandler{transport: t}
}

func (i *transportEarlyDataHandler) Send(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {
return &pb.NoiseExtensions{
StreamMuxers: i.transport.muxers,
}
}

func (i *transportEarlyDataHandler) Received(_ context.Context, _ net.Conn, extension *pb.NoiseExtensions) error {
// Discard messages with size or the number of protocols exceeding extension limit for security.
if extension != nil && len(extension.StreamMuxers) <= maxProtoNum {
i.receivedMuxers = extension.GetStreamMuxers()
}
return nil
}

func (i *transportEarlyDataHandler) MatchMuxers(isInitiator bool) string {
if isInitiator {
return matchMuxers(i.transport.muxers, i.receivedMuxers)
}
return matchMuxers(i.receivedMuxers, i.transport.muxers)
}
59 changes: 59 additions & 0 deletions p2p/security/noise/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ func newTestTransport(t *testing.T, typ, bits int) *Transport {
}
}

func newTestTransportWithMuxers(t *testing.T, typ, bits int, muxers []string) *Transport {
transport := newTestTransport(t, typ, bits)
transport.muxers = muxers
return transport
}

// Create a new pair of connected TCP sockets.
func newConnPair(t *testing.T) (net.Conn, net.Conn) {
lstnr, err := net.Listen("tcp", "localhost:0")
Expand Down Expand Up @@ -586,3 +592,56 @@ func TestEarlyfffDataAcceptedWithNoHandler(t *testing.T) {
require.NoError(t, err)
}
}

type noiseEarlyDataTestCase struct {
initProtos []string
respProtos []string
expectedResult string
}

func TestHandshakeWithTransportEarlyData(t *testing.T) {
tests := []noiseEarlyDataTestCase{
{initProtos: nil, respProtos: nil, expectedResult: ""},
{[]string{"muxer1"}, []string{"muxer1"}, "muxer1"},
{[]string{"muxer1"}, []string{}, ""},
{[]string{}, []string{"muxer2"}, ""},
{[]string{"muxer2"}, []string{"muxer1"}, ""},
{[]string{"muxer1/1.0.0", "muxer2/1.0.1"}, []string{"muxer2/1.0.1", "muxer1/1.0.0"}, "muxer2/1.0.1"},
{[]string{"muxer1/1.0.0", "muxer2/1.0.1", "muxer3/1.0.0"}, []string{"muxer2/1.0.1", "muxer1/1.0.1", "muxer3/1.0.0"}, "muxer2/1.0.1"},
{[]string{"muxer1/1.0.0", "muxer2/1.0.0"}, []string{"muxer3/1.0.0"}, ""},
}

noiseHandshake := func(t *testing.T, initProtos, respProtos []string, expectedProto string) {
initTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, initProtos)
respTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, respProtos)

initConn, respConn := connect(t, initTransport, respTransport)
defer initConn.Close()
defer respConn.Close()

require.Equal(t, expectedProto, initConn.connectionState.NextProto)
require.Equal(t, expectedProto, respConn.connectionState.NextProto)

initData := []byte("Test data for noise transport")
_, err := initConn.Write(initData)
if err != nil {
t.Fatal(err)
}

respData := make([]byte, len(initData))
_, err = respConn.Read(respData)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(initData, respData) {
t.Errorf("Data transmitted mismatch over noise session. %v != %v", initData, respData)
}
}

for _, test := range tests {
t.Run("Transport EarlyData Test", func(t *testing.T) {
noiseHandshake(t, test.initProtos, test.respProtos, test.expectedResult)
})
}
}
2 changes: 1 addition & 1 deletion p2p/transport/websocket/websocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
t.Fatal(err)
}
var secMuxer csms.SSMuxer
noiseTpt, err := noise.New(priv)
noiseTpt, err := noise.New(priv, nil)
require.NoError(t, err)
secMuxer.AddTransport(noise.ID, noiseTpt)
return id, &secMuxer
Expand Down
2 changes: 1 addition & 1 deletion p2p/transport/webtransport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func New(key ic.PrivKey, gater connmgr.ConnectionGater, rcmgr network.ResourceMa
return nil, err
}
}
n, err := noise.New(key)
n, err := noise.New(key, nil)
if err != nil {
return nil, err
}
Expand Down