437 lines
16 KiB
Go
437 lines
16 KiB
Go
/*
|
|
Copyright 2019 NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package edge
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"github.com/openziti/channel/v3"
|
|
"github.com/openziti/foundation/v2/uuidz"
|
|
"github.com/openziti/sdk-golang/pb/edge_client_pb"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
ContentTypeConnect = int32(edge_client_pb.ContentType_ConnectType)
|
|
ContentTypeStateConnected = int32(edge_client_pb.ContentType_StateConnectedType)
|
|
ContentTypeStateClosed = int32(edge_client_pb.ContentType_StateClosedType)
|
|
ContentTypeData = int32(edge_client_pb.ContentType_DataType)
|
|
ContentTypeDial = int32(edge_client_pb.ContentType_DialType)
|
|
ContentTypeDialSuccess = int32(edge_client_pb.ContentType_DialSuccessType)
|
|
ContentTypeDialFailed = int32(edge_client_pb.ContentType_DialFailedType)
|
|
ContentTypeBind = int32(edge_client_pb.ContentType_BindType)
|
|
ContentTypeUnbind = int32(edge_client_pb.ContentType_UnbindType)
|
|
ContentTypeStateSessionEnded = int32(edge_client_pb.ContentType_StateSessionEndedType)
|
|
ContentTypeProbe = int32(edge_client_pb.ContentType_ProbeType)
|
|
ContentTypeUpdateBind = int32(edge_client_pb.ContentType_UpdateBindType)
|
|
ContentTypeHealthEvent = int32(edge_client_pb.ContentType_HealthEventType)
|
|
ContentTypeTraceRoute = int32(edge_client_pb.ContentType_TraceRouteType)
|
|
ContentTypeTraceRouteResponse = int32(edge_client_pb.ContentType_TraceRouteResponseType)
|
|
|
|
ContentTypeConnInspectRequest = int32(edge_client_pb.ContentType_ConnInspectRequest)
|
|
ContentTypeConnInspectResponse = int32(edge_client_pb.ContentType_ConnInspectResponse)
|
|
ContentTypeBindSuccess = int32(edge_client_pb.ContentType_BindSuccess)
|
|
|
|
ContentTypeUpdateToken = int32(edge_client_pb.ContentType_UpdateTokenType)
|
|
ContentTypeUpdateTokenSuccess = int32(edge_client_pb.ContentType_UpdateTokenSuccessType)
|
|
ContentTypeUpdateTokenFailure = int32(edge_client_pb.ContentType_UpdateTokenFailureType)
|
|
|
|
ContentTypePostureResponse = int32(edge_client_pb.ContentType_PostureResponseType)
|
|
)
|
|
|
|
const (
|
|
// UUIDHeader is put in the reflected range so replies will share the same UUID
|
|
UUIDHeader = int32(edge_client_pb.HeaderId_UUID)
|
|
|
|
ConnIdHeader = int32(edge_client_pb.HeaderId_ConnId)
|
|
SeqHeader = int32(edge_client_pb.HeaderId_Seq)
|
|
SessionTokenHeader = int32(edge_client_pb.HeaderId_SessionToken)
|
|
PublicKeyHeader = int32(edge_client_pb.HeaderId_PublicKey)
|
|
CostHeader = int32(edge_client_pb.HeaderId_Cost)
|
|
PrecedenceHeader = int32(edge_client_pb.HeaderId_Precedence)
|
|
TerminatorIdentityHeader = int32(edge_client_pb.HeaderId_TerminatorIdentity)
|
|
TerminatorIdentitySecretHeader = int32(edge_client_pb.HeaderId_TerminatorIdentitySecret)
|
|
CallerIdHeader = int32(edge_client_pb.HeaderId_CallerId)
|
|
CryptoMethodHeader = int32(edge_client_pb.HeaderId_CryptoMethod)
|
|
FlagsHeader = int32(edge_client_pb.HeaderId_Flags)
|
|
AppDataHeader = int32(edge_client_pb.HeaderId_AppData)
|
|
RouterProvidedConnId = int32(edge_client_pb.HeaderId_RouterProvidedConnId)
|
|
HealthStatusHeader = int32(edge_client_pb.HeaderId_HealthStatus)
|
|
ErrorCodeHeader = int32(edge_client_pb.HeaderId_ErrorCode)
|
|
TimestampHeader = int32(edge_client_pb.HeaderId_Timestamp)
|
|
TraceHopCountHeader = int32(edge_client_pb.HeaderId_TraceHopCount)
|
|
TraceHopTypeHeader = int32(edge_client_pb.HeaderId_TraceHopType)
|
|
TraceHopIdHeader = int32(edge_client_pb.HeaderId_TraceHopId)
|
|
TraceSourceRequestIdHeader = int32(edge_client_pb.HeaderId_TraceSourceRequestId)
|
|
TraceError = int32(edge_client_pb.HeaderId_TraceError)
|
|
ListenerId = int32(edge_client_pb.HeaderId_ListenerId)
|
|
ConnTypeHeader = int32(edge_client_pb.HeaderId_ConnType)
|
|
SupportsInspectHeader = int32(edge_client_pb.HeaderId_SupportsInspect)
|
|
SupportsBindSuccessHeader = int32(edge_client_pb.HeaderId_SupportsBindSuccess)
|
|
ConnectionMarkerHeader = int32(edge_client_pb.HeaderId_ConnectionMarker)
|
|
CircuitIdHeader = int32(edge_client_pb.HeaderId_CircuitId)
|
|
StickinessTokenHeader = int32(edge_client_pb.HeaderId_StickinessToken)
|
|
)
|
|
|
|
const (
|
|
ErrorCodeInternal = int32(edge_client_pb.Error_Internal)
|
|
ErrorCodeInvalidApiSession = int32(edge_client_pb.Error_InvalidApiSession)
|
|
ErrorCodeInvalidSession = int32(edge_client_pb.Error_InvalidSession)
|
|
ErrorCodeWrongSessionType = int32(edge_client_pb.Error_WrongSessionType)
|
|
ErrorCodeInvalidEdgeRouterForSession = int32(edge_client_pb.Error_InvalidEdgeRouterForSession)
|
|
ErrorCodeInvalidService = int32(edge_client_pb.Error_InvalidService)
|
|
ErrorCodeTunnelingNotEnabled = int32(edge_client_pb.Error_TunnelingNotEnabled)
|
|
ErrorCodeInvalidTerminator = int32(edge_client_pb.Error_InvalidTerminator)
|
|
ErrorCodeInvalidPrecedence = int32(edge_client_pb.Error_InvalidPrecedence)
|
|
ErrorCodeInvalidCost = int32(edge_client_pb.Error_InvalidCost)
|
|
ErrorCodeEncryptionDataMissing = int32(edge_client_pb.Error_EncryptionDataMissing)
|
|
)
|
|
|
|
const (
|
|
PrecedenceDefault = Precedence(edge_client_pb.PrecedenceValue_Default)
|
|
PrecedenceRequired = Precedence(edge_client_pb.PrecedenceValue_Required)
|
|
PrecedenceFailed = Precedence(edge_client_pb.PrecedenceValue_Failed)
|
|
)
|
|
|
|
const (
|
|
// CryptoMethodLibsodium are used to indicate the crypto engine in use
|
|
CryptoMethodLibsodium CryptoMethod = 0 // default: crypto_kx_*, crypto_secretstream_*
|
|
CryptoMethodSSL CryptoMethod = 1 // OpenSSL(possibly with FIPS): ECDH, AES256-GCM
|
|
)
|
|
|
|
const (
|
|
// FIN is an edge payload flag used to signal communication ends
|
|
FIN = uint32(edge_client_pb.Flag_FIN)
|
|
// TRACE_UUID indicates that peer will send data messages with specially constructed UUID headers
|
|
TRACE_UUID = uint32(edge_client_pb.Flag_TRACE_UUID)
|
|
// MULTIPART indicates that peer can accept multipart data messages
|
|
MULTIPART = uint32(edge_client_pb.Flag_MULTIPART)
|
|
// STREAM indicates connection with stream semantics
|
|
// this allows consolidation of payloads to lower overhead
|
|
STREAM = uint32(edge_client_pb.Flag_STREAM)
|
|
// MULTIPART_MSG set on data message with multiple payloads
|
|
MULTIPART_MSG = uint32(edge_client_pb.Flag_MULTIPART_MSG)
|
|
)
|
|
|
|
type CryptoMethod byte
|
|
|
|
type Precedence byte
|
|
|
|
var ContentTypeValue = map[string]int32{
|
|
"EdgeConnectType": ContentTypeConnect,
|
|
"EdgeStateConnectedType": ContentTypeStateConnected,
|
|
"EdgeStateClosedType": ContentTypeStateClosed,
|
|
"EdgeDataType": ContentTypeData,
|
|
"EdgeDialType": ContentTypeDial,
|
|
"EdgeDialSuccessType": ContentTypeDialSuccess,
|
|
"EdgeDialFailedType": ContentTypeDialFailed,
|
|
"EdgeBindType": ContentTypeBind,
|
|
"EdgeUnbindType": ContentTypeUnbind,
|
|
"EdgeProbeType": ContentTypeProbe,
|
|
"EdgeUpdateTokenType": ContentTypeUpdateToken,
|
|
"EdgeUpdateTokenSuccessType": ContentTypeUpdateTokenSuccess,
|
|
"EdgeUpdateTokenFailureType": ContentTypeUpdateTokenFailure,
|
|
}
|
|
|
|
var ContentTypeNames = map[int32]string{
|
|
ContentTypeConnect: "EdgeConnectType",
|
|
ContentTypeStateConnected: "EdgeStateConnectedType",
|
|
ContentTypeStateClosed: "EdgeStateClosedType",
|
|
ContentTypeData: "EdgeDataType",
|
|
ContentTypeDial: "EdgeDialType",
|
|
ContentTypeDialSuccess: "EdgeDialSuccessType",
|
|
ContentTypeDialFailed: "EdgeDialFailedType",
|
|
ContentTypeBind: "EdgeBindType",
|
|
ContentTypeUnbind: "EdgeUnbindType",
|
|
ContentTypeProbe: "EdgeProbeType",
|
|
ContentTypeUpdateToken: "EdgeUpdateTokenType",
|
|
ContentTypeUpdateTokenSuccess: "EdgeUpdateTokenSuccessType",
|
|
ContentTypeUpdateTokenFailure: "EdgeUpdateTokenFailureType",
|
|
}
|
|
|
|
type MsgEvent struct {
|
|
ConnId uint32
|
|
Seq uint32
|
|
MsgUUID []byte
|
|
Msg *channel.Message
|
|
}
|
|
|
|
func newMsg(contentType int32, connId uint32, seq uint32, data []byte) *channel.Message {
|
|
msg := channel.NewMessage(contentType, data)
|
|
msg.PutUint32Header(ConnIdHeader, connId)
|
|
msg.PutUint32Header(SeqHeader, seq)
|
|
return msg
|
|
}
|
|
|
|
func NewDataMsg(connId uint32, seq uint32, data []byte) *channel.Message {
|
|
return newMsg(ContentTypeData, connId, seq, data)
|
|
}
|
|
|
|
func NewProbeMsg() *channel.Message {
|
|
return channel.NewMessage(ContentTypeProbe, nil)
|
|
}
|
|
|
|
func NewTraceRouteMsg(connId uint32, hops uint32, timestamp uint64) *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeTraceRoute, nil)
|
|
msg.PutUint32Header(ConnIdHeader, connId)
|
|
msg.PutUint32Header(TraceHopCountHeader, hops)
|
|
msg.PutUint64Header(TimestampHeader, timestamp)
|
|
return msg
|
|
}
|
|
|
|
func NewTraceRouteResponseMsg(connId uint32, hops uint32, timestamp uint64, hopType, hopId string) *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeTraceRouteResponse, nil)
|
|
msg.PutUint32Header(ConnIdHeader, connId)
|
|
msg.PutUint32Header(TraceHopCountHeader, hops)
|
|
msg.PutUint64Header(TimestampHeader, timestamp)
|
|
msg.Headers[TraceHopTypeHeader] = []byte(hopType)
|
|
msg.Headers[TraceHopIdHeader] = []byte(hopId)
|
|
|
|
return msg
|
|
}
|
|
|
|
func NewConnInspectResponse(connId uint32, connType ConnType, state string) *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeConnInspectResponse, []byte(state))
|
|
msg.PutUint32Header(ConnIdHeader, connId)
|
|
msg.PutByteHeader(ConnTypeHeader, byte(connType))
|
|
return msg
|
|
}
|
|
|
|
func NewConnectMsg(connId uint32, token string, pubKey []byte, options *DialOptions) *channel.Message {
|
|
msg := newMsg(ContentTypeConnect, connId, 0, []byte(token))
|
|
if pubKey != nil {
|
|
msg.Headers[PublicKeyHeader] = pubKey
|
|
msg.PutByteHeader(CryptoMethodHeader, byte(CryptoMethodLibsodium))
|
|
}
|
|
|
|
if options.Identity != "" {
|
|
msg.Headers[TerminatorIdentityHeader] = []byte(options.Identity)
|
|
}
|
|
if options.CallerId != "" {
|
|
msg.Headers[CallerIdHeader] = []byte(options.CallerId)
|
|
}
|
|
if options.AppData != nil {
|
|
msg.Headers[AppDataHeader] = options.AppData
|
|
}
|
|
if options.StickinessToken != nil {
|
|
msg.Headers[StickinessTokenHeader] = options.StickinessToken
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func NewStateConnectedMsg(connId uint32) *channel.Message {
|
|
return newMsg(ContentTypeStateConnected, connId, 0, nil)
|
|
}
|
|
|
|
func NewStateClosedMsg(connId uint32, message string) *channel.Message {
|
|
return newMsg(ContentTypeStateClosed, connId, 0, []byte(message))
|
|
}
|
|
|
|
func NewDialMsg(connId uint32, token string, callerId string) *channel.Message {
|
|
msg := newMsg(ContentTypeDial, connId, 0, []byte(token))
|
|
msg.Headers[CallerIdHeader] = []byte(callerId)
|
|
return msg
|
|
}
|
|
|
|
func NewBindMsg(connId uint32, token string, pubKey []byte, options *ListenOptions) *channel.Message {
|
|
msg := newMsg(ContentTypeBind, connId, 0, []byte(token))
|
|
msg.PutBoolHeader(SupportsInspectHeader, true)
|
|
msg.PutBoolHeader(SupportsBindSuccessHeader, true)
|
|
|
|
if pubKey != nil {
|
|
msg.Headers[PublicKeyHeader] = pubKey
|
|
msg.PutByteHeader(CryptoMethodHeader, byte(CryptoMethodLibsodium))
|
|
}
|
|
|
|
if options.Cost > 0 {
|
|
costBytes := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(costBytes, options.Cost)
|
|
msg.Headers[CostHeader] = costBytes
|
|
}
|
|
if options.Precedence != PrecedenceDefault {
|
|
msg.PutByteHeader(PrecedenceHeader, byte(options.Precedence))
|
|
}
|
|
|
|
if options.Identity != "" {
|
|
msg.PutStringHeader(TerminatorIdentityHeader, options.Identity)
|
|
|
|
if options.IdentitySecret != "" {
|
|
msg.PutStringHeader(TerminatorIdentitySecretHeader, options.IdentitySecret)
|
|
}
|
|
}
|
|
|
|
if options.ListenerId != "" {
|
|
msg.PutStringHeader(ListenerId, options.ListenerId)
|
|
}
|
|
|
|
msg.PutBoolHeader(RouterProvidedConnId, true)
|
|
return msg
|
|
}
|
|
|
|
func NewUnbindMsg(connId uint32, token string) *channel.Message {
|
|
return newMsg(ContentTypeUnbind, connId, 0, []byte(token))
|
|
}
|
|
|
|
func NewUpdateBindMsg(connId uint32, token string, cost *uint16, precedence *Precedence) *channel.Message {
|
|
msg := newMsg(ContentTypeUpdateBind, connId, 0, []byte(token))
|
|
if cost != nil {
|
|
msg.PutUint16Header(CostHeader, *cost)
|
|
}
|
|
if precedence != nil {
|
|
msg.Headers[PrecedenceHeader] = []byte{byte(*precedence)}
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func NewHealthEventMsg(connId uint32, token string, pass bool) *channel.Message {
|
|
msg := newMsg(ContentTypeHealthEvent, connId, 0, []byte(token))
|
|
msg.PutBoolHeader(HealthStatusHeader, pass)
|
|
return msg
|
|
}
|
|
|
|
func NewDialSuccessMsg(connId uint32, newConnId uint32) *channel.Message {
|
|
newConnIdBytes := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(newConnIdBytes, newConnId)
|
|
msg := newMsg(ContentTypeDialSuccess, connId, 0, newConnIdBytes)
|
|
return msg
|
|
}
|
|
|
|
func NewDialFailedMsg(connId uint32, message string) *channel.Message {
|
|
return newMsg(ContentTypeDialFailed, connId, 0, []byte(message))
|
|
}
|
|
|
|
func NewStateSessionEndedMsg(reason string) *channel.Message {
|
|
return newMsg(ContentTypeStateSessionEnded, 0, 0, []byte(reason))
|
|
}
|
|
|
|
// NewUpdateTokenMsg creates a message sent to edge routers to update the token that
|
|
// allows the client to stay connection. If the token is not update before the current
|
|
// one expires, the connection and all service connections through it will be terminated.
|
|
func NewUpdateTokenMsg(token []byte) *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeUpdateToken, token)
|
|
return msg
|
|
}
|
|
|
|
// NewUpdateTokenFailedMsg is returned in response to a token update where the token failed
|
|
// validation.
|
|
func NewUpdateTokenFailedMsg(err error) *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeUpdateTokenFailure, []byte(err.Error()))
|
|
return msg
|
|
}
|
|
|
|
// NewUpdateTokenSuccessMsg is returned in response to a toke update where the token
|
|
// was accepted.
|
|
func NewUpdateTokenSuccessMsg() *channel.Message {
|
|
msg := channel.NewMessage(ContentTypeUpdateTokenSuccess, nil)
|
|
return msg
|
|
}
|
|
|
|
type DialResult struct {
|
|
ConnId uint32
|
|
NewConnId uint32
|
|
Success bool
|
|
Message string
|
|
}
|
|
|
|
func UnmarshalDialResult(msg *channel.Message) (*DialResult, error) {
|
|
connId, found := msg.GetUint32Header(ConnIdHeader)
|
|
if !found {
|
|
return nil, errors.Errorf("received edge message with no connection id header")
|
|
}
|
|
|
|
if msg.ContentType == ContentTypeDialSuccess {
|
|
if len(msg.Body) != 4 {
|
|
return nil, errors.Errorf("dial success msg improperly formatted. body len: %v", len(msg.Body))
|
|
}
|
|
newConnId := binary.LittleEndian.Uint32(msg.Body)
|
|
return &DialResult{
|
|
ConnId: connId,
|
|
NewConnId: newConnId,
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
if msg.ContentType == ContentTypeDialFailed {
|
|
return &DialResult{
|
|
ConnId: connId,
|
|
Success: false,
|
|
Message: string(msg.Body),
|
|
}, nil
|
|
}
|
|
|
|
return nil, errors.Errorf("unexpected response. received %v instead of dial result message", msg.ContentType)
|
|
}
|
|
|
|
func GetLoggerFields(msg *channel.Message) logrus.Fields {
|
|
var msgUUID string
|
|
if id, found := msg.Headers[UUIDHeader]; found {
|
|
msgUUID = uuidz.ToString(id)
|
|
}
|
|
|
|
connId, _ := msg.GetUint32Header(ConnIdHeader)
|
|
seq, _ := msg.GetUint32Header(SeqHeader)
|
|
|
|
fields := logrus.Fields{
|
|
"connId": connId,
|
|
"type": ContentTypeNames[msg.ContentType],
|
|
"chSeq": msg.Sequence(),
|
|
"edgeSeq": seq,
|
|
}
|
|
|
|
if msgUUID != "" {
|
|
fields["uuid"] = msgUUID
|
|
}
|
|
|
|
if circuitId, found := msg.GetStringHeader(CircuitIdHeader); found {
|
|
fields["circuitId"] = circuitId
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
type ConnType byte
|
|
|
|
const (
|
|
ConnTypeInvalid ConnType = 0
|
|
ConnTypeDial ConnType = 1
|
|
ConnTypeBind ConnType = 2
|
|
ConnTypeUnknown ConnType = 3
|
|
)
|
|
|
|
type InspectResult struct {
|
|
ConnId uint32
|
|
Type ConnType
|
|
Detail string
|
|
}
|
|
|
|
func UnmarshalInspectResult(msg *channel.Message) (*InspectResult, error) {
|
|
if msg.ContentType == ContentTypeConnInspectResponse {
|
|
connId, _ := msg.GetUint32Header(ConnIdHeader)
|
|
connType, found := msg.GetByteHeader(ConnTypeHeader)
|
|
if !found {
|
|
connType = byte(ConnTypeUnknown)
|
|
}
|
|
return &InspectResult{
|
|
ConnId: connId,
|
|
Type: ConnType(connType),
|
|
Detail: string(msg.Body),
|
|
}, nil
|
|
}
|
|
|
|
return nil, errors.Errorf("unexpected response. received %v instead of inspect result message", msg.ContentType)
|
|
}
|