tailscale/control/controlbase/messages.go
David Anderson 02ad987e24 control/controlbase: make the protocol version number selectable.
This is so that we can plumb our client capability version through
the protocol as the Noise version. The capability version increments
more frequently than strictly required (the Noise version only needs
to change when cryptographically-significant changes are made to
the protocol, whereas the capability version also indicates changes
in non-cryptographically-significant parts of the protocol), but this
gives us a safe pre-auth way to determine if the client supports
future protocol features, while still relying on Noise's strong
assurance that the client and server have agreed on the same version.

Currently, the server executes the same protocol regardless of the
version number, and just presents the version to the caller so they
can do capability-based things in the upper RPC protocol. In future,
we may add a ratchet to disallow obsolete protocols, or vary the
Noise handshake behavior based on requested version.

Updates #3488

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-04-07 13:25:28 -07:00

89 lines
3.2 KiB
Go

// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlbase
import "encoding/binary"
const (
// msgTypeInitiation frames carry a Noise IK handshake initiation message.
msgTypeInitiation = 1
// msgTypeResponse frames carry a Noise IK handshake response message.
msgTypeResponse = 2
// msgTypeError frames carry an unauthenticated human-readable
// error message.
//
// Errors reported in this message type must be treated as public
// hints only. They are not encrypted or authenticated, and so can
// be seen and tampered with on the wire.
msgTypeError = 3
// msgTypeRecord frames carry session data bytes.
msgTypeRecord = 4
// headerLen is the size of the header on all messages except msgTypeInitiation.
headerLen = 3
// initiationHeaderLen is the size of the header on all msgTypeInitiation messages.
initiationHeaderLen = 5
)
// initiationMessage is the protocol message sent from a client
// machine to a control server.
//
// 2b: protocol version
// 1b: message type (0x01)
// 2b: payload length (96)
// 5b: header (see headerLen for fields)
// 32b: client ephemeral public key (cleartext)
// 48b: client machine public key (encrypted)
// 16b: message tag (authenticates the whole message)
type initiationMessage [101]byte
func mkInitiationMessage(protocolVersion uint16) initiationMessage {
var ret initiationMessage
binary.BigEndian.PutUint16(ret[:2], protocolVersion)
ret[2] = msgTypeInitiation
binary.BigEndian.PutUint16(ret[3:5], uint16(len(ret.Payload())))
return ret
}
func (m *initiationMessage) Header() []byte { return m[:initiationHeaderLen] }
func (m *initiationMessage) Payload() []byte { return m[initiationHeaderLen:] }
func (m *initiationMessage) Version() uint16 { return binary.BigEndian.Uint16(m[:2]) }
func (m *initiationMessage) Type() byte { return m[2] }
func (m *initiationMessage) Length() int { return int(binary.BigEndian.Uint16(m[3:5])) }
func (m *initiationMessage) EphemeralPub() []byte {
return m[initiationHeaderLen : initiationHeaderLen+32]
}
func (m *initiationMessage) MachinePub() []byte {
return m[initiationHeaderLen+32 : initiationHeaderLen+32+48]
}
func (m *initiationMessage) Tag() []byte { return m[initiationHeaderLen+32+48:] }
// responseMessage is the protocol message sent from a control server
// to a client machine.
//
// 1b: message type (0x02)
// 2b: payload length (48)
// 32b: control ephemeral public key (cleartext)
// 16b: message tag (authenticates the whole message)
type responseMessage [51]byte
func mkResponseMessage() responseMessage {
var ret responseMessage
ret[0] = msgTypeResponse
binary.BigEndian.PutUint16(ret[1:], uint16(len(ret.Payload())))
return ret
}
func (m *responseMessage) Header() []byte { return m[:headerLen] }
func (m *responseMessage) Payload() []byte { return m[headerLen:] }
func (m *responseMessage) Type() byte { return m[0] }
func (m *responseMessage) Length() int { return int(binary.BigEndian.Uint16(m[1:3])) }
func (m *responseMessage) EphemeralPub() []byte { return m[headerLen : headerLen+32] }
func (m *responseMessage) Tag() []byte { return m[headerLen+32:] }