mirror of
https://github.com/fatedier/frp.git
synced 2026-04-27 11:29:09 +08:00
223 lines
5.1 KiB
Go
223 lines
5.1 KiB
Go
// Copyright 2026 The frp Authors
|
|
//
|
|
// 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
|
|
//
|
|
// http://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 wire
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"slices"
|
|
|
|
libnet "github.com/fatedier/golib/net"
|
|
)
|
|
|
|
const (
|
|
ProtocolV1 = "v1"
|
|
ProtocolV2 = "v2"
|
|
|
|
WireVersionV2 = 2
|
|
|
|
FrameTypeClientHello uint16 = 1
|
|
FrameTypeServerHello uint16 = 2
|
|
FrameTypeMessage uint16 = 16
|
|
|
|
MessageCodecJSON = "json"
|
|
DefaultMaxFramePayloadSize = 64 * 1024
|
|
|
|
MagicV2 = "FRP\x00\x02\r\n"
|
|
)
|
|
|
|
type Frame struct {
|
|
Type uint16
|
|
Flags uint16
|
|
Payload []byte
|
|
}
|
|
|
|
type Conn struct {
|
|
rw io.ReadWriter
|
|
maxFramePayloadSize uint32
|
|
}
|
|
|
|
func NewConn(rw io.ReadWriter) *Conn {
|
|
return &Conn{
|
|
rw: rw,
|
|
maxFramePayloadSize: DefaultMaxFramePayloadSize,
|
|
}
|
|
}
|
|
|
|
func (c *Conn) ReadFrame() (*Frame, error) {
|
|
header := make([]byte, 8)
|
|
if _, err := io.ReadFull(c.rw, header); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
frameType := binary.BigEndian.Uint16(header[0:2])
|
|
flags := binary.BigEndian.Uint16(header[2:4])
|
|
length := binary.BigEndian.Uint32(header[4:8])
|
|
if flags != 0 {
|
|
return nil, fmt.Errorf("unsupported frame flags: %d", flags)
|
|
}
|
|
if length > c.maxFramePayloadSize {
|
|
return nil, fmt.Errorf("frame payload length %d exceeds limit %d", length, c.maxFramePayloadSize)
|
|
}
|
|
|
|
payload := make([]byte, length)
|
|
if _, err := io.ReadFull(c.rw, payload); err != nil {
|
|
return nil, err
|
|
}
|
|
return &Frame{
|
|
Type: frameType,
|
|
Flags: flags,
|
|
Payload: payload,
|
|
}, nil
|
|
}
|
|
|
|
func (c *Conn) WriteFrame(f *Frame) error {
|
|
if f.Flags != 0 {
|
|
return fmt.Errorf("unsupported frame flags: %d", f.Flags)
|
|
}
|
|
if len(f.Payload) > int(c.maxFramePayloadSize) {
|
|
return fmt.Errorf("frame payload length %d exceeds limit %d", len(f.Payload), c.maxFramePayloadSize)
|
|
}
|
|
|
|
header := make([]byte, 8)
|
|
binary.BigEndian.PutUint16(header[0:2], f.Type)
|
|
binary.BigEndian.PutUint16(header[2:4], f.Flags)
|
|
binary.BigEndian.PutUint32(header[4:8], uint32(len(f.Payload)))
|
|
if _, err := c.rw.Write(header); err != nil {
|
|
return err
|
|
}
|
|
_, err := c.rw.Write(f.Payload)
|
|
return err
|
|
}
|
|
|
|
func (c *Conn) ReadJSONFrame(frameType uint16, out any) error {
|
|
f, err := c.ReadFrame()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if f.Type != frameType {
|
|
return fmt.Errorf("unexpected frame type %d, want %d", f.Type, frameType)
|
|
}
|
|
return c.UnmarshalFrame(f, out)
|
|
}
|
|
|
|
func (c *Conn) UnmarshalFrame(f *Frame, out any) error {
|
|
return json.Unmarshal(f.Payload, out)
|
|
}
|
|
|
|
func (c *Conn) WriteJSONFrame(frameType uint16, in any) error {
|
|
payload, err := json.Marshal(in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.WriteFrame(&Frame{
|
|
Type: frameType,
|
|
Payload: payload,
|
|
})
|
|
}
|
|
|
|
func WriteMagic(w io.Writer) error {
|
|
_, err := io.WriteString(w, MagicV2)
|
|
return err
|
|
}
|
|
|
|
func WriteMagicIfV2(w io.Writer, wireProtocol string) error {
|
|
if wireProtocol != ProtocolV2 {
|
|
return nil
|
|
}
|
|
return WriteMagic(w)
|
|
}
|
|
|
|
func CheckMagic(conn net.Conn) (out net.Conn, isV2 bool, err error) {
|
|
sharedConn, r := libnet.NewSharedConnSize(conn, len(MagicV2))
|
|
buf := make([]byte, len(MagicV2))
|
|
if _, err = io.ReadFull(r, buf); err != nil {
|
|
return nil, false, err
|
|
}
|
|
for i := range MagicV2 {
|
|
if buf[i] != MagicV2[i] {
|
|
return sharedConn, false, nil
|
|
}
|
|
}
|
|
return conn, true, nil
|
|
}
|
|
|
|
type BootstrapInfo struct {
|
|
Transport string `json:"transport,omitempty"`
|
|
TLS bool `json:"tls,omitempty"`
|
|
TCPMux bool `json:"tcpMux,omitempty"`
|
|
}
|
|
|
|
type ClientHello struct {
|
|
Bootstrap BootstrapInfo `json:"bootstrap,omitempty"`
|
|
Capabilities ClientCapabilities `json:"capabilities,omitempty"`
|
|
}
|
|
|
|
type ClientCapabilities struct {
|
|
Message MessageCapabilities `json:"message,omitempty"`
|
|
}
|
|
|
|
type MessageCapabilities struct {
|
|
Codecs []string `json:"codecs,omitempty"`
|
|
}
|
|
|
|
type ServerHello struct {
|
|
Selected ServerSelection `json:"selected,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type ServerSelection struct {
|
|
Message MessageSelection `json:"message,omitempty"`
|
|
}
|
|
|
|
type MessageSelection struct {
|
|
Codec string `json:"codec,omitempty"`
|
|
}
|
|
|
|
func DefaultClientHello(bootstrap BootstrapInfo) ClientHello {
|
|
return ClientHello{
|
|
Bootstrap: bootstrap,
|
|
Capabilities: ClientCapabilities{
|
|
Message: MessageCapabilities{
|
|
Codecs: []string{MessageCodecJSON},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func DefaultServerHello() ServerHello {
|
|
return ServerHello{
|
|
Selected: ServerSelection{
|
|
Message: MessageSelection{
|
|
Codec: MessageCodecJSON,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func Supports(list []string, value string) bool {
|
|
return slices.Contains(list, value)
|
|
}
|
|
|
|
func ValidateClientHello(h ClientHello) error {
|
|
if !Supports(h.Capabilities.Message.Codecs, MessageCodecJSON) {
|
|
return fmt.Errorf("unsupported message codec")
|
|
}
|
|
return nil
|
|
}
|