diff --git a/client/internal/engine.go b/client/internal/engine.go index 9d278ef16..bc90244bf 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -353,6 +353,10 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp return nil } +func sendSignal(message *sProto.Message, s signal.Client) error { + return s.Send(message) +} + // SignalOfferAnswer signals either an offer or an answer to remote peer func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error { var t sProto.Body_Type @@ -369,6 +373,10 @@ func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKe if err != nil { return err } + + // indicates message support in gRPC + msg.Body.FeaturesSupported = []uint32{signal.DirectCheck} + err = s.Send(msg) if err != nil { return err @@ -827,6 +835,9 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er peerConn.SetSignalCandidate(signalCandidate) peerConn.SetSignalOffer(signalOffer) peerConn.SetSignalAnswer(signalAnswer) + peerConn.SetSendSignalMessage(func(message *sProto.Message) error { + return sendSignal(message, e.signal) + }) return peerConn, nil } @@ -850,6 +861,9 @@ func (e *Engine) receiveSignalEvents() { if err != nil { return err } + + conn.RegisterProtoSupportMeta(msg.Body.GetFeaturesSupported()) + conn.OnRemoteOffer(peer.OfferAnswer{ IceCredentials: peer.IceCredentials{ UFrag: remoteCred.UFrag, @@ -863,6 +877,9 @@ func (e *Engine) receiveSignalEvents() { if err != nil { return err } + + conn.RegisterProtoSupportMeta(msg.Body.GetFeaturesSupported()) + conn.OnRemoteAnswer(peer.OfferAnswer{ IceCredentials: peer.IceCredentials{ UFrag: remoteCred.UFrag, @@ -878,6 +895,19 @@ func (e *Engine) receiveSignalEvents() { return err } conn.OnRemoteCandidate(candidate) + case sProto.Body_MODE: + protoMode := msg.GetBody().GetMode() + if protoMode == nil { + return fmt.Errorf("received an empty mode message") + } + + err := conn.OnModeMessage(peer.ModeMessage{ + Direct: protoMode.GetDirect(), + }) + if err != nil { + log.Errorf("failed processing a mode message -> %s", err) + return err + } } return nil diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 658b5dd28..c5d43b34c 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -2,6 +2,7 @@ package peer import ( "context" + "fmt" "net" "strings" "sync" @@ -14,6 +15,8 @@ import ( "github.com/netbirdio/netbird/client/internal/proxy" "github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/version" + signal "github.com/netbirdio/netbird/signal/client" + sProto "github.com/netbirdio/netbird/signal/proto" ) // ConnConfig is a peer Connection configuration @@ -69,8 +72,9 @@ type Conn struct { // signalCandidate is a handler function to signal remote peer about local connection candidate signalCandidate func(candidate ice.Candidate) error // signalOffer is a handler function to signal remote peer our connection offer (credentials) - signalOffer func(OfferAnswer) error - signalAnswer func(OfferAnswer) error + signalOffer func(OfferAnswer) error + signalAnswer func(OfferAnswer) error + sendSignalMessage func(message *sProto.Message) error // remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection remoteOffersCh chan OfferAnswer @@ -85,7 +89,20 @@ type Conn struct { statusRecorder *Status - proxy proxy.Proxy + proxy proxy.Proxy + remoteModeCh chan ModeMessage + meta meta +} + +// meta holds meta information about a connection +type meta struct { + protoSupport signal.FeaturesSupport +} + +// ModeMessage represents a connection mode chosen by the peer +type ModeMessage struct { + // Direct indicates that it decided to use a direct connection + Direct bool } // GetConf returns the connection config @@ -109,6 +126,7 @@ func NewConn(config ConnConfig, statusRecorder *Status) (*Conn, error) { remoteOffersCh: make(chan OfferAnswer), remoteAnswerCh: make(chan OfferAnswer), statusRecorder: statusRecorder, + remoteModeCh: make(chan ModeMessage, 1), }, nil } @@ -366,15 +384,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error { } peerState := State{PubKey: conn.config.Key} - useProxy := shouldUseProxy(pair) - var p proxy.Proxy - if useProxy { - p = proxy.NewWireguardProxy(conn.config.ProxyConfig) - peerState.Direct = false - } else { - p = proxy.NewNoProxy(conn.config.ProxyConfig, remoteWgPort) - peerState.Direct = true - } + p := conn.getProxyWithMessageExchange(pair, remoteWgPort) conn.proxy = p err = p.Start(remoteConn) if err != nil { @@ -390,6 +400,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error { if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay { peerState.Relayed = true } + peerState.Direct = p.Type() == proxy.TypeNoProxy err = conn.statusRecorder.UpdatePeerState(peerState) if err != nil { @@ -399,6 +410,63 @@ func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error { return nil } +func (conn *Conn) getProxyWithMessageExchange(pair *ice.CandidatePair, remoteWgPort int) proxy.Proxy { + + useProxy := shouldUseProxy(pair) + localDirectMode := !useProxy + remoteDirectMode := localDirectMode + + if conn.meta.protoSupport.DirectCheck { + go conn.sendLocalDirectMode(localDirectMode) + // will block until message received or timeout + remoteDirectMode = conn.receiveRemoteDirectMode() + } + + if localDirectMode && remoteDirectMode { + log.Debugf("using WireGuard direct mode with peer %s", conn.config.Key) + return proxy.NewNoProxy(conn.config.ProxyConfig, remoteWgPort) + } + + log.Debugf("falling back to local proxy mode with peer %s", conn.config.Key) + return proxy.NewWireguardProxy(conn.config.ProxyConfig) +} + +func (conn *Conn) sendLocalDirectMode(localMode bool) { + // todo what happens when we couldn't deliver this message? + // we could retry, etc but there is no guarantee + + err := conn.sendSignalMessage(&sProto.Message{ + Key: conn.config.LocalKey, + RemoteKey: conn.config.Key, + Body: &sProto.Body{ + Type: sProto.Body_MODE, + Mode: &sProto.Mode{ + Direct: &localMode, + }, + NetBirdVersion: version.NetbirdVersion(), + }, + }) + if err != nil { + log.Errorf("failed to send local proxy mode to remote peer %s, error: %s", conn.config.Key, err) + } +} + +func (conn *Conn) receiveRemoteDirectMode() bool { + timeout := time.Second + timer := time.NewTimer(timeout) + defer timer.Stop() + + select { + case receivedMSG := <-conn.remoteModeCh: + return receivedMSG.Direct + case <-timer.C: + // we didn't receive a message from remote so we assume that it supports the direct mode to keep the old behaviour + log.Debugf("timeout after %s while waiting for remote direct mode message from remote peer %s", + timeout, conn.config.Key) + return true + } +} + // cleanup closes all open resources and sets status to StatusDisconnected func (conn *Conn) cleanup() error { log.Debugf("trying to cleanup %s", conn.config.Key) @@ -459,6 +527,11 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error conn.signalCandidate = handler } +// SetSendSignalMessage sets a handler function to be triggered by Conn when there is new message to send via signal +func (conn *Conn) SetSendSignalMessage(handler func(message *sProto.Message) error) { + conn.sendSignalMessage = handler +} + // onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates // and then signals them to the remote peer func (conn *Conn) onICECandidate(candidate ice.Candidate) { @@ -613,3 +686,19 @@ func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate) { func (conn *Conn) GetKey() string { return conn.config.Key } + +// OnModeMessage unmarshall the payload message and send it to the mode message channel +func (conn *Conn) OnModeMessage(message ModeMessage) error { + select { + case conn.remoteModeCh <- message: + return nil + default: + return fmt.Errorf("unable to process mode message: channel busy") + } +} + +// RegisterProtoSupportMeta register supported proto message in the connection metadata +func (conn *Conn) RegisterProtoSupportMeta(support []uint32) { + protoSupport := signal.ParseFeaturesSupported(support) + conn.meta.protoSupport = protoSupport +} diff --git a/client/internal/peer/conn_test.go b/client/internal/peer/conn_test.go index 452915112..ac4447956 100644 --- a/client/internal/peer/conn_test.go +++ b/client/internal/peer/conn_test.go @@ -7,9 +7,11 @@ import ( "github.com/magiconair/properties/assert" "github.com/pion/ice/v2" + "golang.org/x/sync/errgroup" "github.com/netbirdio/netbird/client/internal/proxy" "github.com/netbirdio/netbird/iface" + sproto "github.com/netbirdio/netbird/signal/proto" ) var connConf = ConnConfig{ @@ -329,3 +331,110 @@ func TestConn_ShouldUseProxy(t *testing.T) { }) } } + +func TestGetProxyWithMessageExchange(t *testing.T) { + publicHostCandidate := &mockICECandidate{ + AddressFunc: func() string { + return "8.8.8.8" + }, + TypeFunc: func() ice.CandidateType { + return ice.CandidateTypeHost + }, + } + relayCandidate := &mockICECandidate{ + AddressFunc: func() string { + return "1.1.1.1" + }, + TypeFunc: func() ice.CandidateType { + return ice.CandidateTypeRelay + }, + } + + testCases := []struct { + name string + candatePair *ice.CandidatePair + inputDirectModeSupport bool + inputRemoteModeMessage bool + expected proxy.Type + }{ + { + name: "Should Result In Using Wireguard Proxy When Local Eval Is Use Proxy", + candatePair: &ice.CandidatePair{ + Local: relayCandidate, + Remote: publicHostCandidate, + }, + inputDirectModeSupport: true, + inputRemoteModeMessage: true, + expected: proxy.TypeWireguard, + }, + { + name: "Should Result In Using Wireguard Proxy When Remote Eval Is Use Proxy", + candatePair: &ice.CandidatePair{ + Local: publicHostCandidate, + Remote: publicHostCandidate, + }, + inputDirectModeSupport: true, + inputRemoteModeMessage: false, + expected: proxy.TypeWireguard, + }, + { + name: "Should Result In Using Wireguard Proxy When Remote Direct Mode Support Is False And Local Eval Is Use Proxy", + candatePair: &ice.CandidatePair{ + Local: relayCandidate, + Remote: publicHostCandidate, + }, + inputDirectModeSupport: false, + inputRemoteModeMessage: false, + expected: proxy.TypeWireguard, + }, + { + name: "Should Result In Using Direct When Remote Direct Mode Support Is False And Local Eval Is No Use Proxy", + candatePair: &ice.CandidatePair{ + Local: publicHostCandidate, + Remote: publicHostCandidate, + }, + inputDirectModeSupport: false, + inputRemoteModeMessage: false, + expected: proxy.TypeNoProxy, + }, + { + name: "Should Result In Using Direct When Local And Remote Eval Is No Proxy", + candatePair: &ice.CandidatePair{ + Local: publicHostCandidate, + Remote: publicHostCandidate, + }, + inputDirectModeSupport: true, + inputRemoteModeMessage: true, + expected: proxy.TypeNoProxy, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + g := errgroup.Group{} + conn, err := NewConn(connConf, nil) + if err != nil { + t.Fatal(err) + } + conn.meta.protoSupport.DirectCheck = testCase.inputDirectModeSupport + conn.SetSendSignalMessage(func(message *sproto.Message) error { + return nil + }) + + g.Go(func() error { + return conn.OnModeMessage(ModeMessage{ + Direct: testCase.inputRemoteModeMessage, + }) + }) + + resultProxy := conn.getProxyWithMessageExchange(testCase.candatePair, 1000) + + err = g.Wait() + if err != nil { + t.Error(err) + } + if resultProxy.Type() != testCase.expected { + t.Errorf("result didn't match expected value: Expected: %s, Got: %s", testCase.expected, resultProxy.Type()) + } + }) + } +} diff --git a/go.mod b/go.mod index c5594dc8c..e65b4e302 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( go.opentelemetry.io/otel/metric v0.33.0 go.opentelemetry.io/otel/sdk/metric v0.33.0 golang.org/x/net v0.8.0 + golang.org/x/sync v0.1.0 golang.org/x/term v0.6.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -128,7 +129,6 @@ require ( golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d // indirect diff --git a/signal/client/client.go b/signal/client/client.go index 6aa99e4fe..4af758732 100644 --- a/signal/client/client.go +++ b/signal/client/client.go @@ -19,6 +19,16 @@ type Status string const StreamConnected Status = "Connected" const StreamDisconnected Status = "Disconnected" +const ( + // DirectCheck indicates support to direct mode checks + DirectCheck uint32 = 1 +) + +// FeaturesSupport register protocol supported features +type FeaturesSupport struct { + DirectCheck bool +} + type Client interface { io.Closer StreamConnected() bool @@ -62,3 +72,15 @@ type Credential struct { UFrag string Pwd string } + +// ParseFeaturesSupported parses a slice of supported features into FeaturesSupport +func ParseFeaturesSupported(featuresMessage []uint32) FeaturesSupport { + var protoSupport FeaturesSupport + for _, feature := range featuresMessage { + if feature == DirectCheck { + protoSupport.DirectCheck = true + return protoSupport + } + } + return protoSupport +} diff --git a/signal/client/client_test.go b/signal/client/client_test.go index db07e8e34..c168783e7 100644 --- a/signal/client/client_test.go +++ b/signal/client/client_test.go @@ -2,8 +2,11 @@ package client import ( "context" - sigProto "github.com/netbirdio/netbird/signal/proto" - "github.com/netbirdio/netbird/signal/server" + "net" + "sync" + "testing" + "time" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -12,9 +15,9 @@ import ( "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" - "net" - "sync" - "time" + + sigProto "github.com/netbirdio/netbird/signal/proto" + "github.com/netbirdio/netbird/signal/server" ) var _ = Describe("GrpcClient", func() { @@ -43,15 +46,18 @@ var _ = Describe("GrpcClient", func() { var msgReceived sync.WaitGroup msgReceived.Add(2) - var receivedOnA string - var receivedOnB string + var payloadReceivedOnA string + var payloadReceivedOnB string + var featuresSupportedReceivedOnA []uint32 + var featuresSupportedReceivedOnB []uint32 // connect PeerA to Signal keyA, _ := wgtypes.GenerateKey() clientA := createSignalClient(addr, keyA) go func() { err := clientA.Receive(func(msg *sigProto.Message) error { - receivedOnA = msg.GetBody().GetPayload() + payloadReceivedOnA = msg.GetBody().GetPayload() + featuresSupportedReceivedOnA = msg.GetBody().GetFeaturesSupported() msgReceived.Done() return nil }) @@ -67,7 +73,8 @@ var _ = Describe("GrpcClient", func() { go func() { err := clientB.Receive(func(msg *sigProto.Message) error { - receivedOnB = msg.GetBody().GetPayload() + payloadReceivedOnB = msg.GetBody().GetPayload() + featuresSupportedReceivedOnB = msg.GetBody().GetFeaturesSupported() err := clientB.Send(&sigProto.Message{ Key: keyB.PublicKey().String(), RemoteKey: keyA.PublicKey().String(), @@ -90,7 +97,7 @@ var _ = Describe("GrpcClient", func() { err := clientA.Send(&sigProto.Message{ Key: keyA.PublicKey().String(), RemoteKey: keyB.PublicKey().String(), - Body: &sigProto.Body{Payload: "ping"}, + Body: &sigProto.Body{Payload: "ping", FeaturesSupported: []uint32{DirectCheck}}, }) if err != nil { Fail("failed sending a message to PeerB") @@ -100,9 +107,10 @@ var _ = Describe("GrpcClient", func() { Fail("test timed out on waiting for peers to exchange messages") } - Expect(receivedOnA).To(BeEquivalentTo("pong")) - Expect(receivedOnB).To(BeEquivalentTo("ping")) - + Expect(payloadReceivedOnA).To(BeEquivalentTo("pong")) + Expect(payloadReceivedOnB).To(BeEquivalentTo("ping")) + Expect(featuresSupportedReceivedOnA).To(BeNil()) + Expect(featuresSupportedReceivedOnB).To(ContainElements([]uint32{DirectCheck})) }) }) }) @@ -160,6 +168,41 @@ var _ = Describe("GrpcClient", func() { }) +func TestParseFeaturesSupported(t *testing.T) { + expectedOnEmptyOrUnsupported := FeaturesSupport{DirectCheck: false} + expectedWithDirectCheck := FeaturesSupport{DirectCheck: true} + testCases := []struct { + name string + input []uint32 + expected FeaturesSupport + }{ + { + name: "Should Return DirectCheck Supported", + input: []uint32{DirectCheck}, + expected: expectedWithDirectCheck, + }, + { + name: "Should Return DirectCheck Unsupported When Nil", + input: nil, + expected: expectedOnEmptyOrUnsupported, + }, + { + name: "Should Return DirectCheck Unsupported When Not Known Feature", + input: []uint32{9999}, + expected: expectedOnEmptyOrUnsupported, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result := ParseFeaturesSupported(testCase.input) + if result.DirectCheck != testCase.expected.DirectCheck { + t.Errorf("Direct check feature should match: Expected: %t, Got: %t", testCase.expected.DirectCheck, result.DirectCheck) + } + }) + } +} + func createSignalClient(addr string, key wgtypes.Key) *GrpcClient { var sigTLSEnabled = false client, err := NewClient(context.Background(), addr, key, sigTLSEnabled) diff --git a/signal/proto/generate.sh b/signal/proto/generate.sh index 73f3d9bf3..720a5ff66 100755 --- a/signal/proto/generate.sh +++ b/signal/proto/generate.sh @@ -1,4 +1,17 @@ #!/bin/bash +set -e + +if ! which realpath > /dev/null 2>&1 +then + echo realpath is not installed + echo run: brew install coreutils + exit 1 +fi + +old_pwd=$(pwd) +script_path=$(dirname $(realpath "$0")) +cd "$script_path" go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 -protoc -I proto/ proto/signalexchange.proto --go_out=. --go-grpc_out=. \ No newline at end of file +protoc -I ./ ./signalexchange.proto --go_out=../ --go-grpc_out=../ +cd "$old_pwd" \ No newline at end of file diff --git a/signal/proto/signalexchange.pb.go b/signal/proto/signalexchange.pb.go index a0b62578d..fcf2c93de 100644 --- a/signal/proto/signalexchange.pb.go +++ b/signal/proto/signalexchange.pb.go @@ -1,15 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.12.4 +// protoc v3.21.9 // source: signalexchange.proto package proto import ( - _ "github.com/golang/protobuf/protoc-gen-go/descriptor" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/descriptorpb" reflect "reflect" sync "sync" ) @@ -28,6 +28,7 @@ const ( Body_OFFER Body_Type = 0 Body_ANSWER Body_Type = 1 Body_CANDIDATE Body_Type = 2 + Body_MODE Body_Type = 4 ) // Enum value maps for Body_Type. @@ -36,11 +37,13 @@ var ( 0: "OFFER", 1: "ANSWER", 2: "CANDIDATE", + 4: "MODE", } Body_Type_value = map[string]int32{ "OFFER": 0, "ANSWER": 1, "CANDIDATE": 2, + "MODE": 4, } ) @@ -217,6 +220,9 @@ type Body struct { // wgListenPort is an actual WireGuard listen port WgListenPort uint32 `protobuf:"varint,3,opt,name=wgListenPort,proto3" json:"wgListenPort,omitempty"` NetBirdVersion string `protobuf:"bytes,4,opt,name=netBirdVersion,proto3" json:"netBirdVersion,omitempty"` + Mode *Mode `protobuf:"bytes,5,opt,name=mode,proto3" json:"mode,omitempty"` + // featuresSupported list of supported features by the client of this protocol + FeaturesSupported []uint32 `protobuf:"varint,6,rep,packed,name=featuresSupported,proto3" json:"featuresSupported,omitempty"` } func (x *Body) Reset() { @@ -279,6 +285,68 @@ func (x *Body) GetNetBirdVersion() string { return "" } +func (x *Body) GetMode() *Mode { + if x != nil { + return x.Mode + } + return nil +} + +func (x *Body) GetFeaturesSupported() []uint32 { + if x != nil { + return x.FeaturesSupported + } + return nil +} + +// Mode indicates a connection mode +type Mode struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Direct *bool `protobuf:"varint,1,opt,name=direct,proto3,oneof" json:"direct,omitempty"` +} + +func (x *Mode) Reset() { + *x = Mode{} + if protoimpl.UnsafeEnabled { + mi := &file_signalexchange_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Mode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mode) ProtoMessage() {} + +func (x *Mode) ProtoReflect() protoreflect.Message { + mi := &file_signalexchange_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Mode.ProtoReflect.Descriptor instead. +func (*Mode) Descriptor() ([]byte, []int) { + return file_signalexchange_proto_rawDescGZIP(), []int{3} +} + +func (x *Mode) GetDirect() bool { + if x != nil && x.Direct != nil { + return *x.Direct + } + return false +} + var File_signalexchange_proto protoreflect.FileDescriptor var file_signalexchange_proto_rawDesc = []byte{ @@ -298,7 +366,7 @@ var file_signalexchange_proto_rawDesc = []byte{ 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, - 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xc9, 0x01, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2d, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xab, 0x02, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, @@ -308,22 +376,32 @@ var file_signalexchange_proto_rawDesc = []byte{ 0x67, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x65, 0x74, 0x42, 0x69, 0x72, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x42, 0x69, 0x72, 0x64, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4f, - 0x46, 0x46, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x53, 0x57, 0x45, 0x52, - 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x44, 0x49, 0x44, 0x41, 0x54, 0x45, 0x10, - 0x02, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, + 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, + 0x11, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x11, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x36, 0x0a, 0x04, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0a, + 0x0a, 0x06, 0x41, 0x4e, 0x53, 0x57, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, + 0x4e, 0x44, 0x49, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, 0x44, + 0x45, 0x10, 0x04, 0x22, 0x2e, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, + 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, - 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, + 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -339,25 +417,27 @@ func file_signalexchange_proto_rawDescGZIP() []byte { } var file_signalexchange_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_signalexchange_proto_goTypes = []interface{}{ (Body_Type)(0), // 0: signalexchange.Body.Type (*EncryptedMessage)(nil), // 1: signalexchange.EncryptedMessage (*Message)(nil), // 2: signalexchange.Message (*Body)(nil), // 3: signalexchange.Body + (*Mode)(nil), // 4: signalexchange.Mode } var file_signalexchange_proto_depIdxs = []int32{ 3, // 0: signalexchange.Message.body:type_name -> signalexchange.Body 0, // 1: signalexchange.Body.type:type_name -> signalexchange.Body.Type - 1, // 2: signalexchange.SignalExchange.Send:input_type -> signalexchange.EncryptedMessage - 1, // 3: signalexchange.SignalExchange.ConnectStream:input_type -> signalexchange.EncryptedMessage - 1, // 4: signalexchange.SignalExchange.Send:output_type -> signalexchange.EncryptedMessage - 1, // 5: signalexchange.SignalExchange.ConnectStream:output_type -> signalexchange.EncryptedMessage - 4, // [4:6] is the sub-list for method output_type - 2, // [2:4] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 4, // 2: signalexchange.Body.mode:type_name -> signalexchange.Mode + 1, // 3: signalexchange.SignalExchange.Send:input_type -> signalexchange.EncryptedMessage + 1, // 4: signalexchange.SignalExchange.ConnectStream:input_type -> signalexchange.EncryptedMessage + 1, // 5: signalexchange.SignalExchange.Send:output_type -> signalexchange.EncryptedMessage + 1, // 6: signalexchange.SignalExchange.ConnectStream:output_type -> signalexchange.EncryptedMessage + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_signalexchange_proto_init() } @@ -402,14 +482,27 @@ func file_signalexchange_proto_init() { return nil } } + file_signalexchange_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Mode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } + file_signalexchange_proto_msgTypes[3].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_signalexchange_proto_rawDesc, NumEnums: 1, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/signal/proto/signalexchange.proto b/signal/proto/signalexchange.proto index 4ba214d5f..18c918d97 100644 --- a/signal/proto/signalexchange.proto +++ b/signal/proto/signalexchange.proto @@ -46,10 +46,20 @@ message Body { OFFER = 0; ANSWER = 1; CANDIDATE = 2; + MODE = 4; } Type type = 1; string payload = 2; // wgListenPort is an actual WireGuard listen port uint32 wgListenPort = 3; string netBirdVersion = 4; + Mode mode = 5; + + // featuresSupported list of supported features by the client of this protocol + repeated uint32 featuresSupported = 6; +} + +// Mode indicates a connection mode +message Mode { + optional bool direct = 1; } \ No newline at end of file