mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-11 19:56:37 +08:00
init
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
v interface{}
|
||||
expiration int64
|
||||
}
|
||||
|
||||
func NewItem(v interface{}, d time.Duration) *Item {
|
||||
var expiration int64
|
||||
if d > 0 {
|
||||
expiration = time.Now().Add(d).UnixNano()
|
||||
}
|
||||
|
||||
return &Item{
|
||||
v: v,
|
||||
expiration: expiration,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Item) Expired() bool {
|
||||
if p == nil {
|
||||
return true
|
||||
}
|
||||
return p.expiration > 0 && time.Now().UnixNano() > p.expiration
|
||||
}
|
||||
|
||||
func (p *Item) Value() interface{} {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.v
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
items map[string]*Item
|
||||
cleanupInterval time.Duration
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCache(cleanupInterval time.Duration) *Cache {
|
||||
return &Cache{
|
||||
cleanupInterval: cleanupInterval,
|
||||
items: make(map[string]*Item),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, item *Item) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.items[key] = item
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string) *Item {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
return c.items[key]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package dtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
)
|
||||
|
||||
type dtlsConn struct {
|
||||
net.Conn
|
||||
rbuf bytes.Buffer
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func Conn(c net.Conn, bufferSize int) net.Conn {
|
||||
return &dtlsConn{
|
||||
Conn: c,
|
||||
bufferSize: bufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dtlsConn) Read(p []byte) (n int, err error) {
|
||||
/*
|
||||
defer func() {
|
||||
logger.Default().Debugf("dtls: read data %d/%d, %v", n, len(p), err)
|
||||
}()
|
||||
*/
|
||||
|
||||
if c.rbuf.Len() > 0 {
|
||||
return c.rbuf.Read(p)
|
||||
}
|
||||
|
||||
bufferSize := c.bufferSize
|
||||
if len(p) >= bufferSize {
|
||||
return c.Conn.Read(p)
|
||||
}
|
||||
|
||||
buf := bufpool.Get(bufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
nn, err := c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n = copy(p, buf[:nn])
|
||||
c.rbuf.Write(buf[n:nn])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *dtlsConn) Write(p []byte) (n int, err error) {
|
||||
/*
|
||||
defer func() {
|
||||
logger.Default().Debugf("dtls: write data %d, %v", n, err)
|
||||
}()
|
||||
*/
|
||||
|
||||
for len(p) > 0 {
|
||||
nn := c.bufferSize
|
||||
if nn > len(p) {
|
||||
nn = len(p)
|
||||
}
|
||||
nn, err = c.Conn.Write(p[:nn])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p = p[nn:]
|
||||
}
|
||||
return
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.12.4
|
||||
// source: gost.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Chunk struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Chunk) Reset() {
|
||||
*x = Chunk{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_gost_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Chunk) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Chunk) ProtoMessage() {}
|
||||
|
||||
func (x *Chunk) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gost_proto_msgTypes[0]
|
||||
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 Chunk.ProtoReflect.Descriptor instead.
|
||||
func (*Chunk) Descriptor() ([]byte, []int) {
|
||||
return file_gost_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Chunk) GetData() []byte {
|
||||
if x != nil {
|
||||
return x.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_gost_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_gost_proto_rawDesc = []byte{
|
||||
0x0a, 0x0a, 0x67, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x05,
|
||||
0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x29, 0x0a, 0x09, 0x47, 0x6f, 0x73,
|
||||
0x74, 0x54, 0x75, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c,
|
||||
0x12, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b,
|
||||
0x28, 0x01, 0x30, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x67, 0x6f, 0x73, 0x74, 0x2f, 0x67, 0x6f, 0x73, 0x74, 0x2f,
|
||||
0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
|
||||
0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_gost_proto_rawDescOnce sync.Once
|
||||
file_gost_proto_rawDescData = file_gost_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_gost_proto_rawDescGZIP() []byte {
|
||||
file_gost_proto_rawDescOnce.Do(func() {
|
||||
file_gost_proto_rawDescData = protoimpl.X.CompressGZIP(file_gost_proto_rawDescData)
|
||||
})
|
||||
return file_gost_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_gost_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_gost_proto_goTypes = []any{
|
||||
(*Chunk)(nil), // 0: Chunk
|
||||
}
|
||||
var file_gost_proto_depIdxs = []int32{
|
||||
0, // 0: GostTunel.Tunnel:input_type -> Chunk
|
||||
0, // 1: GostTunel.Tunnel:output_type -> Chunk
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_gost_proto_init() }
|
||||
func file_gost_proto_init() {
|
||||
if File_gost_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_gost_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Chunk); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_gost_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_gost_proto_goTypes,
|
||||
DependencyIndexes: file_gost_proto_depIdxs,
|
||||
MessageInfos: file_gost_proto_msgTypes,
|
||||
}.Build()
|
||||
File_gost_proto = out.File
|
||||
file_gost_proto_rawDesc = nil
|
||||
file_gost_proto_goTypes = nil
|
||||
file_gost_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/go-gost/core/common/util/grpc/proto";
|
||||
|
||||
message Chunk {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
service GostTunel {
|
||||
rpc Tunnel (stream Chunk) returns (stream Chunk);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// GostTunelClient is the client API for GostTunel service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type GostTunelClient interface {
|
||||
Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error)
|
||||
}
|
||||
|
||||
type gostTunelClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewGostTunelClient(cc grpc.ClientConnInterface) GostTunelClient {
|
||||
return &gostTunelClient{cc}
|
||||
}
|
||||
|
||||
func (c *gostTunelClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &GostTunel_ServiceDesc.Streams[0], "/GostTunel/Tunnel", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &gostTunelTunnelClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type GostTunel_TunnelClient interface {
|
||||
Send(*Chunk) error
|
||||
Recv() (*Chunk, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type gostTunelTunnelClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *gostTunelTunnelClient) Send(m *Chunk) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *gostTunelTunnelClient) Recv() (*Chunk, error) {
|
||||
m := new(Chunk)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GostTunelServer is the server API for GostTunel service.
|
||||
// All implementations must embed UnimplementedGostTunelServer
|
||||
// for forward compatibility
|
||||
type GostTunelServer interface {
|
||||
Tunnel(GostTunel_TunnelServer) error
|
||||
mustEmbedUnimplementedGostTunelServer()
|
||||
}
|
||||
|
||||
// UnimplementedGostTunelServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedGostTunelServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedGostTunelServer) Tunnel(GostTunel_TunnelServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Tunnel not implemented")
|
||||
}
|
||||
func (UnimplementedGostTunelServer) mustEmbedUnimplementedGostTunelServer() {}
|
||||
|
||||
// UnsafeGostTunelServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to GostTunelServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeGostTunelServer interface {
|
||||
mustEmbedUnimplementedGostTunelServer()
|
||||
}
|
||||
|
||||
func RegisterGostTunelServer(s grpc.ServiceRegistrar, srv GostTunelServer) {
|
||||
s.RegisterService(&GostTunel_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _GostTunel_Tunnel_Handler(srv any, stream grpc.ServerStream) error {
|
||||
return srv.(GostTunelServer).Tunnel(&gostTunelTunnelServer{stream})
|
||||
}
|
||||
|
||||
type GostTunel_TunnelServer interface {
|
||||
Send(*Chunk) error
|
||||
Recv() (*Chunk, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type gostTunelTunnelServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *gostTunelTunnelServer) Send(m *Chunk) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *gostTunelTunnelServer) Recv() (*Chunk, error) {
|
||||
m := new(Chunk)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GostTunel_ServiceDesc is the grpc.ServiceDesc for GostTunel service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var GostTunel_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "GostTunel",
|
||||
HandlerType: (*GostTunelServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Tunnel",
|
||||
Handler: _GostTunel_Tunnel_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "gost.proto",
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"strings"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type GostTunelClientX interface {
|
||||
TunnelX(ctx context.Context, method string, opts ...grpc.CallOption) (GostTunel_TunnelClient, error)
|
||||
}
|
||||
type gostTunelClientX struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewGostTunelClientX(cc grpc.ClientConnInterface) GostTunelClientX {
|
||||
return &gostTunelClientX{
|
||||
cc: cc,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gostTunelClientX) TunnelX(ctx context.Context, method string, opts ...grpc.CallOption) (GostTunel_TunnelClient, error) {
|
||||
sd := ServerDesc(method)
|
||||
method = "/" + sd.ServiceName + "/" + sd.Streams[0].StreamName
|
||||
stream, err := c.cc.NewStream(ctx, &sd.Streams[0], method, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &gostTunelTunnelClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func RegisterGostTunelServerX(s grpc.ServiceRegistrar, srv GostTunelServer, method string) {
|
||||
sd := ServerDesc(method)
|
||||
s.RegisterService(&sd, srv)
|
||||
}
|
||||
|
||||
func ServerDesc(method string) grpc.ServiceDesc {
|
||||
serviceName, streamName := parsingMethod(method)
|
||||
|
||||
return grpc.ServiceDesc{
|
||||
ServiceName: serviceName,
|
||||
HandlerType: GostTunel_ServiceDesc.HandlerType,
|
||||
Methods: GostTunel_ServiceDesc.Methods,
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: streamName,
|
||||
Handler: GostTunel_ServiceDesc.Streams[0].Handler,
|
||||
ServerStreams: GostTunel_ServiceDesc.Streams[0].ServerStreams,
|
||||
ClientStreams: GostTunel_ServiceDesc.Streams[0].ClientStreams,
|
||||
},
|
||||
},
|
||||
Metadata: GostTunel_ServiceDesc.Metadata,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func parsingMethod(method string) (string, string) {
|
||||
serviceName := GostTunel_ServiceDesc.ServiceName
|
||||
streamName := GostTunel_ServiceDesc.Streams[0].StreamName
|
||||
v := strings.SplitN(strings.Trim(method, "/"), "/", 2)
|
||||
if len(v) == 1 && v[0] != "" {
|
||||
serviceName = v[0]
|
||||
}
|
||||
if len(v) == 2 {
|
||||
serviceName = v[0]
|
||||
streamName = strings.Replace(v[1], "/", "-", -1)
|
||||
}
|
||||
|
||||
return serviceName, streamName
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
protoc --go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||
gost.proto
|
||||
@@ -0,0 +1,313 @@
|
||||
package icmp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
"github.com/go-gost/core/logger"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
const (
|
||||
ICMPv4 = 1
|
||||
ICMPv6 = 58
|
||||
)
|
||||
|
||||
const (
|
||||
readBufferSize = 4096
|
||||
writeBufferSize = 4096
|
||||
magicNumber = 0x474F5354
|
||||
)
|
||||
|
||||
const (
|
||||
messageHeaderLen = 10
|
||||
)
|
||||
|
||||
const (
|
||||
FlagAck = 1
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidPacket = errors.New("icmp: invalid packet")
|
||||
ErrInvalidType = errors.New("icmp: invalid type")
|
||||
ErrShortBuffer = errors.New("icmp: short buffer")
|
||||
)
|
||||
|
||||
type message struct {
|
||||
// magic uint32 // magic number
|
||||
flags uint16 // flags
|
||||
// rsv uint16 // reserved field
|
||||
// len uint16 // length of data
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (m *message) Encode(b []byte) (n int, err error) {
|
||||
if len(b) < messageHeaderLen+len(m.data) {
|
||||
err = ErrShortBuffer
|
||||
return
|
||||
}
|
||||
binary.BigEndian.PutUint32(b[:4], magicNumber) // magic number
|
||||
binary.BigEndian.PutUint16(b[4:6], m.flags) // flags
|
||||
binary.BigEndian.PutUint16(b[6:8], 0) // reserved
|
||||
binary.BigEndian.PutUint16(b[8:10], uint16(len(m.data)))
|
||||
copy(b[messageHeaderLen:], m.data)
|
||||
|
||||
n = messageHeaderLen + len(m.data)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *message) Decode(b []byte) (n int, err error) {
|
||||
if len(b) < messageHeaderLen {
|
||||
err = ErrShortBuffer
|
||||
return
|
||||
}
|
||||
if binary.BigEndian.Uint32(b[:4]) != magicNumber {
|
||||
err = ErrInvalidPacket
|
||||
return
|
||||
}
|
||||
m.flags = binary.BigEndian.Uint16(b[4:6])
|
||||
length := binary.BigEndian.Uint16(b[8:10])
|
||||
if len(b[messageHeaderLen:]) < int(length) {
|
||||
err = ErrShortBuffer
|
||||
return
|
||||
}
|
||||
m.data = b[messageHeaderLen : messageHeaderLen+length]
|
||||
|
||||
n = messageHeaderLen + int(length)
|
||||
return
|
||||
}
|
||||
|
||||
type clientConn struct {
|
||||
ip6 bool
|
||||
net.PacketConn
|
||||
id int
|
||||
seq uint32
|
||||
}
|
||||
|
||||
func ClientConn(ip6 bool, conn net.PacketConn, id int) net.PacketConn {
|
||||
return &clientConn{
|
||||
ip6: ip6,
|
||||
PacketConn: conn,
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
buf := bufpool.Get(readBufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
for {
|
||||
n, addr, err = c.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
proto := ICMPv4
|
||||
if c.ip6 {
|
||||
proto = ICMPv6
|
||||
}
|
||||
m, err := icmp.ParseMessage(proto, buf[:n])
|
||||
if err != nil {
|
||||
// logger.Default().Error("icmp: parse message %v", err)
|
||||
return 0, addr, err
|
||||
}
|
||||
echo, ok := m.Body.(*icmp.Echo)
|
||||
if !ok || m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
|
||||
// logger.Default().Warnf("icmp: invalid type %s (discarded)", m.Type)
|
||||
continue // discard
|
||||
}
|
||||
|
||||
if echo.ID != c.id {
|
||||
// logger.Default().Warnf("icmp: id mismatch got %d, should be %d (discarded)", echo.ID, c.id)
|
||||
continue
|
||||
}
|
||||
|
||||
msg := message{}
|
||||
if _, err := msg.Decode(echo.Data); err != nil {
|
||||
logger.Default().Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.flags&FlagAck == 0 {
|
||||
// logger.Default().Warn("icmp: invalid message (discarded)")
|
||||
continue
|
||||
}
|
||||
n = copy(b, msg.data)
|
||||
break
|
||||
}
|
||||
|
||||
if v, ok := addr.(*net.IPAddr); ok {
|
||||
addr = &net.UDPAddr{
|
||||
IP: v.IP,
|
||||
Port: c.id,
|
||||
Zone: v.Zone,
|
||||
}
|
||||
}
|
||||
// logger.Default().Infof("icmp: read from: %v %d", addr, n)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *clientConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
// logger.Default().Infof("icmp: write to: %v %d", addr, len(b))
|
||||
switch v := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
addr = &net.IPAddr{IP: v.IP, Zone: v.Zone}
|
||||
}
|
||||
|
||||
buf := bufpool.Get(writeBufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
msg := message{
|
||||
data: b,
|
||||
}
|
||||
nn, err := msg.Encode(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
echo := icmp.Echo{
|
||||
ID: c.id,
|
||||
Seq: int(atomic.AddUint32(&c.seq, 1)),
|
||||
Data: buf[:nn],
|
||||
}
|
||||
m := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho,
|
||||
Code: 0,
|
||||
Body: &echo,
|
||||
}
|
||||
if c.ip6 {
|
||||
m.Type = ipv6.ICMPTypeEchoRequest
|
||||
}
|
||||
|
||||
wb, err := m.Marshal(nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(wb, addr)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
ip6 bool
|
||||
net.PacketConn
|
||||
seqs [65535]uint32
|
||||
}
|
||||
|
||||
func ServerConn(ip6 bool, conn net.PacketConn) net.PacketConn {
|
||||
return &serverConn{
|
||||
ip6: ip6,
|
||||
PacketConn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
buf := bufpool.Get(readBufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
for {
|
||||
n, addr, err = c.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
proto := ICMPv4
|
||||
if c.ip6 {
|
||||
proto = ICMPv6
|
||||
}
|
||||
m, err := icmp.ParseMessage(proto, buf[:n])
|
||||
if err != nil {
|
||||
// logger.Default().Error("icmp: parse message %v", err)
|
||||
return 0, addr, err
|
||||
}
|
||||
|
||||
echo, ok := m.Body.(*icmp.Echo)
|
||||
if !ok || echo.ID <= 0 || m.Type != ipv4.ICMPTypeEcho && m.Type != ipv6.ICMPTypeEchoRequest {
|
||||
// logger.Default().Warnf("icmp: invalid type %s (discarded)", m.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&c.seqs[uint16(echo.ID-1)], uint32(echo.Seq))
|
||||
|
||||
msg := message{}
|
||||
if _, err := msg.Decode(echo.Data); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.flags&FlagAck > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
n = copy(b, msg.data)
|
||||
|
||||
if v, ok := addr.(*net.IPAddr); ok {
|
||||
addr = &net.UDPAddr{
|
||||
IP: v.IP,
|
||||
Port: echo.ID,
|
||||
Zone: v.Zone,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// logger.Default().Infof("icmp: read from: %v %d", addr, n)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
// logger.Default().Infof("icmp: write to: %v %d", addr, len(b))
|
||||
var id int
|
||||
switch v := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
addr = &net.IPAddr{IP: v.IP, Zone: v.Zone}
|
||||
id = v.Port
|
||||
}
|
||||
|
||||
if id <= 0 || id > math.MaxUint16 {
|
||||
err = fmt.Errorf("icmp: invalid message id %v", addr)
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufpool.Get(writeBufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
msg := message{
|
||||
flags: FlagAck,
|
||||
data: b,
|
||||
}
|
||||
nn, err := msg.Encode(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
echo := icmp.Echo{
|
||||
ID: id,
|
||||
Seq: int(atomic.LoadUint32(&c.seqs[id-1])),
|
||||
Data: buf[:nn],
|
||||
}
|
||||
m := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEchoReply,
|
||||
Code: 0,
|
||||
Body: &echo,
|
||||
}
|
||||
if c.ip6 {
|
||||
m.Type = ipv6.ICMPTypeEchoReply
|
||||
}
|
||||
|
||||
wb, err := m.Marshal(nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(wb, addr)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/xtaci/kcp-go/v5"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultSalt is the default salt for KCP cipher.
|
||||
DefaultSalt = "kcp-go"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultKCPConfig is the default KCP config.
|
||||
DefaultConfig = &Config{
|
||||
Key: "it's a secrect",
|
||||
Crypt: "aes",
|
||||
Mode: "fast",
|
||||
MTU: 1350,
|
||||
SndWnd: 1024,
|
||||
RcvWnd: 1024,
|
||||
DataShard: 10,
|
||||
ParityShard: 3,
|
||||
DSCP: 0,
|
||||
NoComp: false,
|
||||
AckNodelay: false,
|
||||
NoDelay: 0,
|
||||
Interval: 50,
|
||||
Resend: 0,
|
||||
NoCongestion: 0,
|
||||
SockBuf: 4194304,
|
||||
SmuxVer: 1,
|
||||
SmuxBuf: 4194304,
|
||||
StreamBuf: 2097152,
|
||||
KeepAlive: 10,
|
||||
SnmpLog: "",
|
||||
SnmpPeriod: 60,
|
||||
Signal: false,
|
||||
TCP: false,
|
||||
}
|
||||
)
|
||||
|
||||
// KCPConfig describes the config for KCP.
|
||||
type Config struct {
|
||||
Key string `json:"key"`
|
||||
Crypt string `json:"crypt"`
|
||||
Mode string `json:"mode"`
|
||||
MTU int `json:"mtu"`
|
||||
SndWnd int `json:"sndwnd"`
|
||||
RcvWnd int `json:"rcvwnd"`
|
||||
DataShard int `json:"datashard"`
|
||||
ParityShard int `json:"parityshard"`
|
||||
DSCP int `json:"dscp"`
|
||||
NoComp bool `json:"nocomp"`
|
||||
AckNodelay bool `json:"acknodelay"`
|
||||
NoDelay int `json:"nodelay"`
|
||||
Interval int `json:"interval"`
|
||||
Resend int `json:"resend"`
|
||||
NoCongestion int `json:"nc"`
|
||||
SockBuf int `json:"sockbuf"`
|
||||
SmuxBuf int `json:"smuxbuf"`
|
||||
StreamBuf int `json:"streambuf"`
|
||||
SmuxVer int `json:"smuxver"`
|
||||
KeepAlive int `json:"keepalive"`
|
||||
SnmpLog string `json:"snmplog"`
|
||||
SnmpPeriod int `json:"snmpperiod"`
|
||||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
||||
TCP bool `json:"tcp"`
|
||||
}
|
||||
|
||||
func ParseFromFile(filename string) (*Config, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &Config{}
|
||||
if err = json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Init initializes the KCP config.
|
||||
func (c *Config) Init() {
|
||||
switch c.Mode {
|
||||
case "normal":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
|
||||
case "fast":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1
|
||||
case "fast2":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
|
||||
case "fast3":
|
||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
|
||||
}
|
||||
if c.SmuxVer <= 0 {
|
||||
c.SmuxVer = 1
|
||||
}
|
||||
if c.SmuxBuf <= 0 {
|
||||
c.SmuxBuf = c.SockBuf
|
||||
}
|
||||
if c.StreamBuf <= 0 {
|
||||
c.StreamBuf = c.SockBuf / 2
|
||||
}
|
||||
}
|
||||
|
||||
func BlockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
||||
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
|
||||
|
||||
switch crypt {
|
||||
case "sm4":
|
||||
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
||||
case "tea":
|
||||
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||
case "xor":
|
||||
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||
case "none":
|
||||
block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||
case "aes-128":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||
case "aes-192":
|
||||
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||
case "blowfish":
|
||||
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||
case "twofish":
|
||||
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||
case "cast5":
|
||||
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||
case "3des":
|
||||
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||
case "xtea":
|
||||
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||
case "salsa20":
|
||||
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||
case "aes":
|
||||
fallthrough
|
||||
default: // aes
|
||||
block, _ = kcp.NewAESBlockCrypt(pass)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
type kcpCompStreamConn struct {
|
||||
net.Conn
|
||||
w *snappy.Writer
|
||||
r *snappy.Reader
|
||||
}
|
||||
|
||||
func CompStreamConn(conn net.Conn) net.Conn {
|
||||
return &kcpCompStreamConn{
|
||||
Conn: conn,
|
||||
w: snappy.NewBufferedWriter(conn),
|
||||
r: snappy.NewReader(conn),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *kcpCompStreamConn) Read(b []byte) (n int, err error) {
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *kcpCompStreamConn) Write(b []byte) (n int, err error) {
|
||||
n, err = c.w.Write(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.w.Flush()
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
smux "github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultVersion = 1
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// SMUX Protocol version, support 1,2
|
||||
Version int
|
||||
|
||||
// Disabled keepalive
|
||||
KeepAliveDisabled bool
|
||||
|
||||
// KeepAliveInterval is how often to send a NOP command to the remote
|
||||
KeepAliveInterval time.Duration
|
||||
|
||||
// KeepAliveTimeout is how long the session
|
||||
// will be closed if no data has arrived
|
||||
KeepAliveTimeout time.Duration
|
||||
|
||||
// MaxFrameSize is used to control the maximum
|
||||
// frame size to sent to the remote
|
||||
MaxFrameSize int
|
||||
|
||||
// MaxReceiveBuffer is used to control the maximum
|
||||
// number of data in the buffer pool
|
||||
MaxReceiveBuffer int
|
||||
|
||||
// MaxStreamBuffer is used to control the maximum
|
||||
// number of data per stream
|
||||
MaxStreamBuffer int
|
||||
}
|
||||
|
||||
func convertConfig(cfg *Config) *smux.Config {
|
||||
smuxCfg := smux.DefaultConfig()
|
||||
smuxCfg.Version = defaultVersion
|
||||
|
||||
if cfg == nil {
|
||||
return smuxCfg
|
||||
}
|
||||
|
||||
if cfg.Version > 0 {
|
||||
smuxCfg.Version = cfg.Version
|
||||
}
|
||||
smuxCfg.KeepAliveDisabled = cfg.KeepAliveDisabled
|
||||
if cfg.KeepAliveInterval > 0 {
|
||||
smuxCfg.KeepAliveInterval = cfg.KeepAliveInterval
|
||||
}
|
||||
if cfg.KeepAliveTimeout > 0 {
|
||||
smuxCfg.KeepAliveTimeout = cfg.KeepAliveTimeout
|
||||
}
|
||||
if cfg.MaxFrameSize > 0 {
|
||||
smuxCfg.MaxFrameSize = cfg.MaxFrameSize
|
||||
}
|
||||
if cfg.MaxReceiveBuffer > 0 {
|
||||
smuxCfg.MaxReceiveBuffer = cfg.MaxReceiveBuffer
|
||||
}
|
||||
if cfg.MaxStreamBuffer > 0 {
|
||||
smuxCfg.MaxStreamBuffer = cfg.MaxStreamBuffer
|
||||
}
|
||||
|
||||
return smuxCfg
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
conn net.Conn
|
||||
session *smux.Session
|
||||
}
|
||||
|
||||
func ClientSession(conn net.Conn, cfg *Config) (*Session, error) {
|
||||
s, err := smux.Client(conn, convertConfig(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{
|
||||
conn: conn,
|
||||
session: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ServerSession(conn net.Conn, cfg *Config) (*Session, error) {
|
||||
s, err := smux.Server(conn, convertConfig(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{
|
||||
conn: conn,
|
||||
session: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (session *Session) GetConn() (net.Conn, error) {
|
||||
stream, err := session.session.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamConn{Conn: session.conn, stream: stream}, nil
|
||||
}
|
||||
|
||||
func (session *Session) Accept() (net.Conn, error) {
|
||||
stream, err := session.session.AcceptStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamConn{Conn: session.conn, stream: stream}, nil
|
||||
}
|
||||
|
||||
func (session *Session) Close() error {
|
||||
if session.session == nil {
|
||||
return nil
|
||||
}
|
||||
return session.session.Close()
|
||||
}
|
||||
|
||||
func (session *Session) IsClosed() bool {
|
||||
if session.session == nil {
|
||||
return true
|
||||
}
|
||||
return session.session.IsClosed()
|
||||
}
|
||||
|
||||
func (session *Session) NumStreams() int {
|
||||
return session.session.NumStreams()
|
||||
}
|
||||
|
||||
type streamConn struct {
|
||||
net.Conn
|
||||
stream *smux.Stream
|
||||
}
|
||||
|
||||
func (c *streamConn) Read(b []byte) (n int, err error) {
|
||||
return c.stream.Read(b)
|
||||
}
|
||||
|
||||
func (c *streamConn) Write(b []byte) (n int, err error) {
|
||||
return c.stream.Write(b)
|
||||
}
|
||||
|
||||
func (c *streamConn) Close() error {
|
||||
return c.stream.Close()
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Host string
|
||||
Client *http.Client
|
||||
AuthorizePath string
|
||||
PushPath string
|
||||
PullPath string
|
||||
TLSEnabled bool
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
||||
raddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
c.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Host != "" {
|
||||
addr = net.JoinHostPort(c.Host, strconv.Itoa(raddr.Port))
|
||||
}
|
||||
|
||||
token, err := c.authorize(ctx, addr)
|
||||
if err != nil {
|
||||
c.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cn := &clientConn{
|
||||
client: c.Client,
|
||||
rxc: make(chan []byte, 128),
|
||||
closed: make(chan struct{}),
|
||||
localAddr: &net.TCPAddr{},
|
||||
remoteAddr: raddr,
|
||||
logger: c.Logger,
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if c.TLSEnabled {
|
||||
scheme = "https"
|
||||
}
|
||||
cn.pushURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PushPath, token)
|
||||
cn.pullURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PullPath, token)
|
||||
|
||||
go cn.readLoop()
|
||||
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *Client) authorize(ctx context.Context, addr string) (token string, err error) {
|
||||
var url string
|
||||
if c.TLSEnabled {
|
||||
url = fmt.Sprintf("https://%s%s", addr, c.AuthorizePath)
|
||||
} else {
|
||||
url = fmt.Sprintf("http://%s%s", addr, c.AuthorizePath)
|
||||
}
|
||||
r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
c.Logger.Trace(string(dump))
|
||||
} else if c.Logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
c.Logger.Debugf("%s %s", r.Method, r.URL)
|
||||
}
|
||||
|
||||
resp, err := c.Client.Do(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.Logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.Logger.Trace(string(dump))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(string(data), "token=") {
|
||||
token = strings.TrimPrefix(string(data), "token=")
|
||||
}
|
||||
if token == "" {
|
||||
err = errors.New("authorize failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
)
|
||||
|
||||
type clientConn struct {
|
||||
client *http.Client
|
||||
pushURL string
|
||||
pullURL string
|
||||
buf []byte
|
||||
rxc chan []byte
|
||||
closed chan struct{}
|
||||
mu sync.Mutex
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (c *clientConn) Read(b []byte) (n int, err error) {
|
||||
if len(c.buf) == 0 {
|
||||
select {
|
||||
case c.buf = <-c.rxc:
|
||||
case <-c.closed:
|
||||
err = io.ErrClosedPipe
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n = copy(b, c.buf)
|
||||
c.buf = c.buf[n:]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *clientConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return
|
||||
}
|
||||
return c.write(b)
|
||||
}
|
||||
|
||||
func (c *clientConn) write(b []byte) (n int, err error) {
|
||||
if c.isClosed() {
|
||||
err = io.ErrClosedPipe
|
||||
return
|
||||
}
|
||||
|
||||
var r io.Reader
|
||||
if len(b) > 0 {
|
||||
buf := bytes.NewBufferString(base64.StdEncoding.EncodeToString(b))
|
||||
buf.WriteByte('\n')
|
||||
r = buf
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, c.pushURL, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
c.logger.Trace(string(dump))
|
||||
} else if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
c.logger.Debugf("%s %s", req.Method, req.URL)
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.logger.Trace(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = errors.New(resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *clientConn) readLoop() {
|
||||
for {
|
||||
if c.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
done := true
|
||||
err := func() error {
|
||||
r, err := http.NewRequest(http.MethodGet, c.pullURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
c.logger.Trace(string(dump))
|
||||
} else if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
c.logger.Debugf("%s %s", r.Method, r.URL)
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.logger.Trace(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
done = false
|
||||
if scanner.Text() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(scanner.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case c.rxc <- b:
|
||||
case <-c.closed:
|
||||
return net.ErrClosed
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if done { // server connection closed
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *clientConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *clientConn) Close() error {
|
||||
c.mu.Lock()
|
||||
|
||||
select {
|
||||
case <-c.closed:
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
default:
|
||||
close(c.closed)
|
||||
}
|
||||
|
||||
c.mu.Unlock()
|
||||
|
||||
_, err := c.write(nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *clientConn) isClosed() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
select {
|
||||
case <-c.closed:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *clientConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
net.Conn
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *serverConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *serverConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
"github.com/go-gost/core/logger"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBacklog = 128
|
||||
defaultReadBufferSize = 32 * 1024
|
||||
defaultReadTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type serverOptions struct {
|
||||
authorizePath string
|
||||
pushPath string
|
||||
pullPath string
|
||||
backlog int
|
||||
tlsEnabled bool
|
||||
tlsConfig *tls.Config
|
||||
readBufferSize int
|
||||
readTimeout time.Duration
|
||||
mptcp bool
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type ServerOption func(opts *serverOptions)
|
||||
|
||||
func PathServerOption(authorizePath, pushPath, pullPath string) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.authorizePath = authorizePath
|
||||
opts.pullPath = pullPath
|
||||
opts.pushPath = pushPath
|
||||
}
|
||||
}
|
||||
|
||||
func BacklogServerOption(backlog int) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.backlog = backlog
|
||||
}
|
||||
}
|
||||
|
||||
func TLSConfigServerOption(tlsConfig *tls.Config) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.tlsConfig = tlsConfig
|
||||
}
|
||||
}
|
||||
|
||||
func EnableTLSServerOption(enable bool) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.tlsEnabled = enable
|
||||
}
|
||||
}
|
||||
|
||||
func ReadBufferSizeServerOption(n int) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.readBufferSize = n
|
||||
}
|
||||
}
|
||||
|
||||
func ReadTimeoutServerOption(timeout time.Duration) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.readTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func MPTCPServerOption(mptcp bool) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.mptcp = mptcp
|
||||
}
|
||||
}
|
||||
|
||||
func LoggerServerOption(logger logger.Logger) ServerOption {
|
||||
return func(opts *serverOptions) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove stale clients from conns
|
||||
type Server struct {
|
||||
addr net.Addr
|
||||
httpServer *http.Server
|
||||
http3Server *http3.Server
|
||||
cqueue chan net.Conn
|
||||
conns sync.Map
|
||||
closed chan struct{}
|
||||
|
||||
options serverOptions
|
||||
}
|
||||
|
||||
func NewServer(addr string, opts ...ServerOption) *Server {
|
||||
var options serverOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
if options.backlog <= 0 {
|
||||
options.backlog = defaultBacklog
|
||||
}
|
||||
if options.readBufferSize <= 0 {
|
||||
options.readBufferSize = defaultReadBufferSize
|
||||
}
|
||||
if options.readTimeout <= 0 {
|
||||
options.readTimeout = defaultReadTimeout
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
httpServer: &http.Server{
|
||||
Addr: addr,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
},
|
||||
cqueue: make(chan net.Conn, options.backlog),
|
||||
closed: make(chan struct{}),
|
||||
options: options,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(options.authorizePath, s.handleAuthorize)
|
||||
mux.HandleFunc(options.pushPath, s.handlePush)
|
||||
mux.HandleFunc(options.pullPath, s.handlePull)
|
||||
s.httpServer.Handler = mux
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func NewHTTP3Server(addr string, quicConfig *quic.Config, opts ...ServerOption) *Server {
|
||||
var options serverOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
if options.backlog <= 0 {
|
||||
options.backlog = defaultBacklog
|
||||
}
|
||||
if options.readBufferSize <= 0 {
|
||||
options.readBufferSize = defaultReadBufferSize
|
||||
}
|
||||
if options.readTimeout <= 0 {
|
||||
options.readTimeout = defaultReadTimeout
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
http3Server: &http3.Server{
|
||||
Addr: addr,
|
||||
TLSConfig: options.tlsConfig,
|
||||
QUICConfig: quicConfig,
|
||||
},
|
||||
cqueue: make(chan net.Conn, options.backlog),
|
||||
closed: make(chan struct{}),
|
||||
options: options,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(options.authorizePath, s.handleAuthorize)
|
||||
mux.HandleFunc(options.pushPath, s.handlePush)
|
||||
mux.HandleFunc(options.pullPath, s.handlePull)
|
||||
s.http3Server.Handler = mux
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() error {
|
||||
if s.http3Server != nil {
|
||||
network := "udp"
|
||||
if xnet.IsIPv4(s.http3Server.Addr) {
|
||||
network = "udp4"
|
||||
}
|
||||
addr, err := net.ResolveUDPAddr(network, s.http3Server.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.addr = addr
|
||||
return s.http3Server.ListenAndServe()
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
if xnet.IsIPv4(s.httpServer.Addr) {
|
||||
network = "tcp4"
|
||||
}
|
||||
|
||||
lc := net.ListenConfig{}
|
||||
if s.options.mptcp {
|
||||
lc.SetMultipathTCP(true)
|
||||
s.options.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP())
|
||||
}
|
||||
ln, err := lc.Listen(context.Background(), network, s.httpServer.Addr)
|
||||
if err != nil {
|
||||
s.options.logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.addr = ln.Addr()
|
||||
if s.options.tlsEnabled {
|
||||
s.httpServer.TLSConfig = s.options.tlsConfig
|
||||
ln = tls.NewListener(ln, s.options.tlsConfig)
|
||||
}
|
||||
|
||||
return s.httpServer.Serve(ln)
|
||||
}
|
||||
|
||||
func (s *Server) Accept() (conn net.Conn, err error) {
|
||||
select {
|
||||
case conn = <-s.cqueue:
|
||||
case <-s.closed:
|
||||
err = http.ErrServerClosed
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
select {
|
||||
case <-s.closed:
|
||||
return http.ErrServerClosed
|
||||
default:
|
||||
close(s.closed)
|
||||
|
||||
if s.http3Server != nil {
|
||||
return s.http3Server.Close()
|
||||
}
|
||||
return s.httpServer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleAuthorize(w http.ResponseWriter, r *http.Request) {
|
||||
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Trace(string(dump))
|
||||
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
|
||||
}
|
||||
|
||||
raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
|
||||
if raddr == nil {
|
||||
raddr = &net.TCPAddr{}
|
||||
}
|
||||
|
||||
// connection id
|
||||
cid := xid.New().String()
|
||||
|
||||
c1, c2 := net.Pipe()
|
||||
c := &serverConn{
|
||||
Conn: c1,
|
||||
localAddr: s.addr,
|
||||
remoteAddr: raddr,
|
||||
}
|
||||
|
||||
select {
|
||||
case s.cqueue <- c:
|
||||
default:
|
||||
c.Close()
|
||||
s.options.logger.Warnf("connection queue is full, client %s discarded", r.RemoteAddr)
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("token=%s", cid)))
|
||||
s.conns.Store(cid, c2)
|
||||
}
|
||||
|
||||
func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) {
|
||||
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Trace(string(dump))
|
||||
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
cid := r.Form.Get("token")
|
||||
v, ok := s.conns.Load(cid)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
conn := v.(net.Conn)
|
||||
|
||||
br := bufio.NewReader(r.Body)
|
||||
data, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
s.options.logger.Error(err)
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
}
|
||||
conn.Close()
|
||||
s.conns.Delete(cid)
|
||||
return
|
||||
}
|
||||
|
||||
data = strings.TrimSuffix(data, "\n")
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
s.options.logger.Error(err)
|
||||
s.conns.Delete(cid)
|
||||
conn.Close()
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
|
||||
defer conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
if _, err := conn.Write(b); err != nil {
|
||||
s.options.logger.Error(err)
|
||||
s.conns.Delete(cid)
|
||||
conn.Close()
|
||||
w.WriteHeader(http.StatusGone)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handlePull(w http.ResponseWriter, r *http.Request) {
|
||||
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Trace(string(dump))
|
||||
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
|
||||
}
|
||||
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
cid := r.Form.Get("token")
|
||||
v, ok := s.conns.Load(cid)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
conn := v.(net.Conn)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if fw, ok := w.(http.Flusher); ok {
|
||||
fw.Flush()
|
||||
}
|
||||
|
||||
b := bufpool.Get(s.options.readBufferSize)
|
||||
defer bufpool.Put(b)
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(s.options.readTimeout))
|
||||
n, err := conn.Read(b)
|
||||
if n > 0 {
|
||||
bw := bufio.NewWriter(w)
|
||||
bw.WriteString(base64.StdEncoding.EncodeToString(b[:n]))
|
||||
bw.WriteString("\n")
|
||||
if err := bw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
if fw, ok := w.(http.Flusher); ok {
|
||||
fw.Flush()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
b[0] = '\n' // no data
|
||||
w.Write(b[:1])
|
||||
} else if errors.Is(err, io.EOF) {
|
||||
// server connection closed
|
||||
} else {
|
||||
if !errors.Is(err, io.ErrClosedPipe) {
|
||||
s.options.logger.Error(err)
|
||||
}
|
||||
s.conns.Delete(cid)
|
||||
conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type cipherConn struct {
|
||||
net.PacketConn
|
||||
key []byte
|
||||
}
|
||||
|
||||
func CipherPacketConn(conn net.PacketConn, key []byte) net.PacketConn {
|
||||
return &cipherConn{
|
||||
PacketConn: conn,
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *cipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) {
|
||||
n, addr, err = conn.PacketConn.ReadFrom(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b, err := conn.decrypt(data[:n])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
copy(data, b)
|
||||
|
||||
return len(b), addr, nil
|
||||
}
|
||||
|
||||
func (conn *cipherConn) WriteTo(data []byte, addr net.Addr) (n int, err error) {
|
||||
b, err := conn.encrypt(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.PacketConn.WriteTo(b, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (conn *cipherConn) encrypt(data []byte) ([]byte, error) {
|
||||
c, err := aes.NewCipher(conn.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Seal(nonce, nonce, data, nil), nil
|
||||
}
|
||||
|
||||
func (conn *cipherConn) decrypt(data []byte) ([]byte, error) {
|
||||
c, err := aes.NewCipher(conn.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
return gcm.Open(nil, nonce, ciphertext, nil)
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/relay"
|
||||
)
|
||||
|
||||
func StatusText(code uint8) string {
|
||||
switch code {
|
||||
case relay.StatusBadRequest:
|
||||
return "Bad Request"
|
||||
case relay.StatusForbidden:
|
||||
return "Forbidden"
|
||||
case relay.StatusHostUnreachable:
|
||||
return "Host Unreachable"
|
||||
case relay.StatusInternalServerError:
|
||||
return "Internal Server Error"
|
||||
case relay.StatusNetworkUnreachable:
|
||||
return "Network Unreachable"
|
||||
case relay.StatusServiceUnavailable:
|
||||
return "Service Unavailable"
|
||||
case relay.StatusTimeout:
|
||||
return "Timeout"
|
||||
case relay.StatusUnauthorized:
|
||||
return "Unauthorized"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type udpTunConn struct {
|
||||
net.Conn
|
||||
taddr net.Addr
|
||||
}
|
||||
|
||||
func UDPTunClientConn(c net.Conn, targetAddr net.Addr) net.Conn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
taddr: targetAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPTunClientPacketConn(c net.Conn) net.PacketConn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPTunServerConn(c net.Conn) net.PacketConn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpTunConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
socksAddr := gosocks5.Addr{}
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
_, err = dgram.ReadFrom(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = len(dgram.Data)
|
||||
if n > len(b) {
|
||||
n = copy(b, dgram.Data)
|
||||
}
|
||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
dgram.Header.Rsv = uint16(len(dgram.Data))
|
||||
dgram.Header.Frag = 0xff // UDP tun relay flag, used by shadowsocks
|
||||
_, err = dgram.WriteTo(c.Conn)
|
||||
n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultBufferSize = 4096
|
||||
)
|
||||
|
||||
type udpConn struct {
|
||||
net.PacketConn
|
||||
raddr net.Addr
|
||||
taddr net.Addr
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func UDPConn(c net.PacketConn, bufferSize int) net.PacketConn {
|
||||
return &udpConn{
|
||||
PacketConn: c,
|
||||
bufferSize: bufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads an UDP datagram.
|
||||
// NOTE: for server side,
|
||||
// the returned addr is the target address the client want to relay to.
|
||||
func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
rbuf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(rbuf)
|
||||
|
||||
n, c.raddr, err = c.PacketConn.ReadFrom(rbuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
hlen, err := header.ReadFrom(bytes.NewReader(rbuf[:n]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n = copy(b, rbuf[hlen:n])
|
||||
|
||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
wbuf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(wbuf)
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(wbuf[:0])
|
||||
_, err = dgram.WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
|
||||
n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
func (c *udpConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTTL = 60 * time.Second
|
||||
)
|
||||
|
||||
type CacheKey string
|
||||
|
||||
// NewCacheKey generates resolver cache key from question of dns query.
|
||||
func NewCacheKey(q *dns.Question) CacheKey {
|
||||
if q == nil {
|
||||
return ""
|
||||
}
|
||||
key := fmt.Sprintf("%s%s.%s", q.Name, dns.Class(q.Qclass).String(), dns.Type(q.Qtype).String())
|
||||
return CacheKey(key)
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
msg *dns.Msg
|
||||
ts time.Time
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
m sync.Map
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewCache() *Cache {
|
||||
return &Cache{}
|
||||
}
|
||||
|
||||
func (c *Cache) WithLogger(logger logger.Logger) *Cache {
|
||||
c.logger = logger
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Cache) Load(ctx context.Context, key CacheKey) (msg *dns.Msg, ttl time.Duration) {
|
||||
v, ok := c.m.Load(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
item, ok := v.(*cacheItem)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
msg = item.msg.Copy()
|
||||
for i := range msg.Answer {
|
||||
d := uint32(time.Since(item.ts).Seconds())
|
||||
if msg.Answer[i].Header().Ttl > d {
|
||||
msg.Answer[i].Header().Ttl -= d
|
||||
} else {
|
||||
msg.Answer[i].Header().Ttl = 1
|
||||
}
|
||||
}
|
||||
ttl = item.ttl - time.Since(item.ts)
|
||||
|
||||
if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
|
||||
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
|
||||
log = log.WithFields(map[string]any{
|
||||
"sid": sid,
|
||||
})
|
||||
}
|
||||
log.Debugf("resolver cache hit: %s, ttl: %v", key, ttl)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Cache) Store(ctx context.Context, key CacheKey, mr *dns.Msg, ttl time.Duration) {
|
||||
if key == "" || mr == nil || ttl < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if ttl == 0 {
|
||||
for _, answer := range mr.Answer {
|
||||
v := time.Duration(answer.Header().Ttl) * time.Second
|
||||
if ttl == 0 || ttl > v {
|
||||
ttl = v
|
||||
}
|
||||
}
|
||||
if ttl == 0 {
|
||||
ttl = defaultTTL
|
||||
}
|
||||
} else {
|
||||
for i := range mr.Answer {
|
||||
mr.Answer[i].Header().Ttl = uint32(ttl.Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
c.m.Store(key, &cacheItem{
|
||||
msg: mr.Copy(),
|
||||
ts: time.Now(),
|
||||
ttl: ttl,
|
||||
})
|
||||
|
||||
if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
|
||||
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
|
||||
log = log.WithFields(map[string]any{
|
||||
"sid": sid,
|
||||
})
|
||||
}
|
||||
log.Debugf("resolver cache store: %s, ttl: %v", key, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) RefreshTTL(key CacheKey) {
|
||||
v, ok := c.m.Load(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
item, ok := v.(*cacheItem)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
item.ts = time.Now()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func AddSubnetOpt(m *dns.Msg, ip net.IP) {
|
||||
if m == nil || ip == nil {
|
||||
return
|
||||
}
|
||||
|
||||
opt := new(dns.OPT)
|
||||
opt.Hdr.Name = "."
|
||||
opt.Hdr.Rrtype = dns.TypeOPT
|
||||
e := new(dns.EDNS0_SUBNET)
|
||||
e.Code = dns.EDNS0SUBNET
|
||||
if ip := ip.To4(); ip != nil {
|
||||
e.Family = 1
|
||||
e.SourceNetmask = 24
|
||||
e.Address = ip
|
||||
} else {
|
||||
e.Family = 2
|
||||
e.SourceNetmask = 128
|
||||
e.Address = ip.To16()
|
||||
}
|
||||
opt.Option = append(opt.Option, e)
|
||||
m.Extra = append(m.Extra, opt)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
port io.ReadWriteCloser
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewConn(port io.ReadWriteCloser, addr net.Addr, cancel context.CancelFunc) net.Conn {
|
||||
return &conn{
|
||||
port: port,
|
||||
laddr: addr,
|
||||
raddr: &Addr{Port: "@"},
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
return c.port.Read(b)
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
return c.port.Write(b)
|
||||
}
|
||||
|
||||
func (c *conn) LocalAddr() net.Addr {
|
||||
return c.laddr
|
||||
}
|
||||
|
||||
func (c *conn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
|
||||
func (c *conn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *conn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *conn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *conn) Close() (err error) {
|
||||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
}
|
||||
return c.port.Close()
|
||||
}
|
||||
|
||||
type Addr struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
func (a *Addr) Network() string {
|
||||
return "serial"
|
||||
}
|
||||
|
||||
func (a *Addr) String() string {
|
||||
return a.Port
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Goserial is a simple go package to allow you to read and write from
|
||||
the serial port as a stream of bytes.
|
||||
|
||||
It aims to have the same API on all platforms, including windows. As
|
||||
an added bonus, the windows package does not use cgo, so you can cross
|
||||
compile for windows from another platform. Unfortunately goinstall
|
||||
does not currently let you cross compile so you will have to do it
|
||||
manually:
|
||||
|
||||
GOOS=windows make clean install
|
||||
|
||||
Currently there is very little in the way of configurability. You can
|
||||
set the baud rate. Then you can Read(), Write(), or Close() the
|
||||
connection. Read() will block until at least one byte is returned.
|
||||
Write is the same. There is currently no exposed way to set the
|
||||
timeouts, though patches are welcome.
|
||||
|
||||
Currently all ports are opened with 8 data bits, 1 stop bit, no
|
||||
parity, no hardware flow control, and no software flow control. This
|
||||
works fine for many real devices and many faux serial devices
|
||||
including usb-to-serial converters and bluetooth serial ports.
|
||||
|
||||
You may Read() and Write() simulantiously on the same connection (from
|
||||
different goroutines).
|
||||
|
||||
Example usage:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tarm/serial"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := &serial.Config{Name: "COM5", Baud: 115200}
|
||||
s, err := serial.OpenPort(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := s.Write([]byte("test"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 128)
|
||||
n, err = s.Read(buf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Print("%q", buf[:n])
|
||||
}
|
||||
*/
|
||||
package serial
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultSize = 8 // Default value for Config.Size
|
||||
|
||||
type StopBits byte
|
||||
type Parity byte
|
||||
|
||||
const (
|
||||
Stop1 StopBits = 1
|
||||
Stop1Half StopBits = 15
|
||||
Stop2 StopBits = 2
|
||||
)
|
||||
|
||||
const (
|
||||
ParityNone Parity = 'N'
|
||||
ParityOdd Parity = 'O'
|
||||
ParityEven Parity = 'E'
|
||||
ParityMark Parity = 'M' // parity bit is always 1
|
||||
ParitySpace Parity = 'S' // parity bit is always 0
|
||||
)
|
||||
|
||||
// Config contains the information needed to open a serial port.
|
||||
//
|
||||
// Currently few options are implemented, but more may be added in the
|
||||
// future (patches welcome), so it is recommended that you create a
|
||||
// new config addressing the fields by name rather than by order.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500}
|
||||
//
|
||||
// or
|
||||
//
|
||||
// c1 := new(serial.Config)
|
||||
// c1.Name = "/dev/tty.usbserial"
|
||||
// c1.Baud = 115200
|
||||
// c1.ReadTimeout = time.Millisecond * 500
|
||||
type Config struct {
|
||||
Name string
|
||||
Baud int
|
||||
ReadTimeout time.Duration // Total timeout
|
||||
|
||||
// Size is the number of data bits. If 0, DefaultSize is used.
|
||||
Size byte
|
||||
|
||||
// Parity is the bit to use and defaults to ParityNone (no parity bit).
|
||||
Parity Parity
|
||||
|
||||
// Number of stop bits to use. Default is 1 (1 stop bit).
|
||||
StopBits StopBits
|
||||
|
||||
// RTSFlowControl bool
|
||||
// DTRFlowControl bool
|
||||
// XONFlowControl bool
|
||||
|
||||
// CRLFTranslate bool
|
||||
}
|
||||
|
||||
// ErrBadSize is returned if Size is not supported.
|
||||
var ErrBadSize error = errors.New("unsupported serial data size")
|
||||
|
||||
// ErrBadStopBits is returned if the specified StopBits setting not supported.
|
||||
var ErrBadStopBits error = errors.New("unsupported stop bit setting")
|
||||
|
||||
// ErrBadParity is returned if the parity is not supported.
|
||||
var ErrBadParity error = errors.New("unsupported parity setting")
|
||||
|
||||
// OpenPort opens a serial port with the specified configuration
|
||||
func OpenPort(c *Config) (io.ReadWriteCloser, error) {
|
||||
size, par, stop := c.Size, c.Parity, c.StopBits
|
||||
if size == 0 {
|
||||
size = DefaultSize
|
||||
}
|
||||
if par == 0 {
|
||||
par = ParityNone
|
||||
}
|
||||
if stop == 0 {
|
||||
stop = Stop1
|
||||
}
|
||||
return openPort(c.Name, c.Baud, size, par, stop, c.ReadTimeout)
|
||||
}
|
||||
|
||||
// Converts the timeout values for Linux / POSIX systems
|
||||
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
|
||||
const MAXUINT8 = 1<<8 - 1 // 255
|
||||
// set blocking / non-blocking read
|
||||
var minBytesToRead uint8 = 1
|
||||
var readTimeoutInDeci int64
|
||||
if readTimeout > 0 {
|
||||
// EOF on zero read
|
||||
minBytesToRead = 0
|
||||
// convert timeout to deciseconds as expected by VTIME
|
||||
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
|
||||
// capping the timeout
|
||||
if readTimeoutInDeci < 1 {
|
||||
// min possible timeout 1 Deciseconds (0.1s)
|
||||
readTimeoutInDeci = 1
|
||||
} else if readTimeoutInDeci > MAXUINT8 {
|
||||
// max possible timeout is 255 deciseconds (25.5s)
|
||||
readTimeoutInDeci = MAXUINT8
|
||||
}
|
||||
}
|
||||
return minBytesToRead, uint8(readTimeoutInDeci)
|
||||
}
|
||||
|
||||
// func SendBreak()
|
||||
|
||||
// func RegisterBreakHandler(func())
|
||||
@@ -0,0 +1,165 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package serial
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
|
||||
var bauds = map[int]uint32{
|
||||
50: unix.B50,
|
||||
75: unix.B75,
|
||||
110: unix.B110,
|
||||
134: unix.B134,
|
||||
150: unix.B150,
|
||||
200: unix.B200,
|
||||
300: unix.B300,
|
||||
600: unix.B600,
|
||||
1200: unix.B1200,
|
||||
1800: unix.B1800,
|
||||
2400: unix.B2400,
|
||||
4800: unix.B4800,
|
||||
9600: unix.B9600,
|
||||
19200: unix.B19200,
|
||||
38400: unix.B38400,
|
||||
57600: unix.B57600,
|
||||
115200: unix.B115200,
|
||||
230400: unix.B230400,
|
||||
460800: unix.B460800,
|
||||
500000: unix.B500000,
|
||||
576000: unix.B576000,
|
||||
921600: unix.B921600,
|
||||
1000000: unix.B1000000,
|
||||
1152000: unix.B1152000,
|
||||
1500000: unix.B1500000,
|
||||
2000000: unix.B2000000,
|
||||
2500000: unix.B2500000,
|
||||
3000000: unix.B3000000,
|
||||
3500000: unix.B3500000,
|
||||
4000000: unix.B4000000,
|
||||
}
|
||||
|
||||
rate, ok := bauds[baud]
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unrecognized baud rate")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil && f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Base settings
|
||||
cflagToUse := unix.CREAD | unix.CLOCAL | rate
|
||||
switch databits {
|
||||
case 5:
|
||||
cflagToUse |= unix.CS5
|
||||
case 6:
|
||||
cflagToUse |= unix.CS6
|
||||
case 7:
|
||||
cflagToUse |= unix.CS7
|
||||
case 8:
|
||||
cflagToUse |= unix.CS8
|
||||
default:
|
||||
return nil, ErrBadSize
|
||||
}
|
||||
// Stop bits settings
|
||||
switch stopbits {
|
||||
case Stop1:
|
||||
// default is 1 stop bit
|
||||
case Stop2:
|
||||
cflagToUse |= unix.CSTOPB
|
||||
default:
|
||||
// Don't know how to set 1.5
|
||||
return nil, ErrBadStopBits
|
||||
}
|
||||
// Parity settings
|
||||
switch parity {
|
||||
case ParityNone:
|
||||
// default is no parity
|
||||
case ParityOdd:
|
||||
cflagToUse |= unix.PARENB
|
||||
cflagToUse |= unix.PARODD
|
||||
case ParityEven:
|
||||
cflagToUse |= unix.PARENB
|
||||
default:
|
||||
return nil, ErrBadParity
|
||||
}
|
||||
fd := f.Fd()
|
||||
vmin, vtime := posixTimeoutValues(readTimeout)
|
||||
t := unix.Termios{
|
||||
Iflag: unix.IGNPAR,
|
||||
Cflag: cflagToUse,
|
||||
Ispeed: rate,
|
||||
Ospeed: rate,
|
||||
}
|
||||
t.Cc[unix.VMIN] = vmin
|
||||
t.Cc[unix.VTIME] = vtime
|
||||
|
||||
if _, _, errno := unix.Syscall6(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(unix.TCSETS),
|
||||
uintptr(unsafe.Pointer(&t)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
); errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
if err = unix.SetNonblock(int(fd), false); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return &Port{f: f}, nil
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
// We intentionly do not use an "embedded" struct so that we
|
||||
// don't export File
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (p *Port) Read(b []byte) (n int, err error) {
|
||||
return p.f.Read(b)
|
||||
}
|
||||
|
||||
func (p *Port) Write(b []byte) (n int, err error) {
|
||||
return p.f.Write(b)
|
||||
}
|
||||
|
||||
// Discards data written to the port but not transmitted,
|
||||
// or data received but not read
|
||||
func (p *Port) Flush() error {
|
||||
const TCFLSH = 0x540B
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(p.f.Fd()),
|
||||
uintptr(TCFLSH),
|
||||
uintptr(unix.TCIOFLUSH),
|
||||
)
|
||||
|
||||
if errno == 0 {
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
|
||||
func (p *Port) Close() (err error) {
|
||||
return p.f.Close()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//go:build !windows && !linux
|
||||
// +build !windows,!linux
|
||||
|
||||
package serial
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
|
||||
return nil, errors.New("unsupported platform")
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
}
|
||||
|
||||
func (p *Port) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Port) Write(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Port) Close() (err error) {
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package serial
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Port struct {
|
||||
f *os.File
|
||||
fd syscall.Handle
|
||||
rl sync.Mutex
|
||||
wl sync.Mutex
|
||||
ro *syscall.Overlapped
|
||||
wo *syscall.Overlapped
|
||||
}
|
||||
|
||||
type structDCB struct {
|
||||
DCBlength, BaudRate uint32
|
||||
flags [4]byte
|
||||
wReserved, XonLim, XoffLim uint16
|
||||
ByteSize, Parity, StopBits byte
|
||||
XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
|
||||
wReserved1 uint16
|
||||
}
|
||||
|
||||
type structTimeouts struct {
|
||||
ReadIntervalTimeout uint32
|
||||
ReadTotalTimeoutMultiplier uint32
|
||||
ReadTotalTimeoutConstant uint32
|
||||
WriteTotalTimeoutMultiplier uint32
|
||||
WriteTotalTimeoutConstant uint32
|
||||
}
|
||||
|
||||
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
|
||||
if len(name) > 0 && name[0] != '\\' {
|
||||
name = "\\\\.\\" + name
|
||||
}
|
||||
|
||||
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
|
||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||
0,
|
||||
nil,
|
||||
syscall.OPEN_EXISTING,
|
||||
syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := os.NewFile(uintptr(h), name)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
f.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err = setCommState(h, baud, databits, parity, stopbits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = setupComm(h, 64, 64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = setCommTimeouts(h, readTimeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = setCommMask(h); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ro, err := newOverlapped()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wo, err := newOverlapped()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := new(Port)
|
||||
port.f = f
|
||||
port.fd = h
|
||||
port.ro = ro
|
||||
port.wo = wo
|
||||
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func (p *Port) Close() error {
|
||||
return p.f.Close()
|
||||
}
|
||||
|
||||
func (p *Port) Write(buf []byte) (int, error) {
|
||||
p.wl.Lock()
|
||||
defer p.wl.Unlock()
|
||||
|
||||
if err := resetEvent(p.wo.HEvent); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var n uint32
|
||||
err := syscall.WriteFile(p.fd, buf, &n, p.wo)
|
||||
if err != nil && err != syscall.ERROR_IO_PENDING {
|
||||
return int(n), err
|
||||
}
|
||||
return getOverlappedResult(p.fd, p.wo)
|
||||
}
|
||||
|
||||
func (p *Port) Read(buf []byte) (int, error) {
|
||||
if p == nil || p.f == nil {
|
||||
return 0, fmt.Errorf("Invalid port on read")
|
||||
}
|
||||
|
||||
p.rl.Lock()
|
||||
defer p.rl.Unlock()
|
||||
|
||||
if err := resetEvent(p.ro.HEvent); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var done uint32
|
||||
err := syscall.ReadFile(p.fd, buf, &done, p.ro)
|
||||
if err != nil && err != syscall.ERROR_IO_PENDING {
|
||||
return int(done), err
|
||||
}
|
||||
return getOverlappedResult(p.fd, p.ro)
|
||||
}
|
||||
|
||||
// Discards data written to the port but not transmitted,
|
||||
// or data received but not read
|
||||
func (p *Port) Flush() error {
|
||||
return purgeComm(p.fd)
|
||||
}
|
||||
|
||||
var (
|
||||
nSetCommState,
|
||||
nSetCommTimeouts,
|
||||
nSetCommMask,
|
||||
nSetupComm,
|
||||
nGetOverlappedResult,
|
||||
nCreateEvent,
|
||||
nResetEvent,
|
||||
nPurgeComm,
|
||||
nFlushFileBuffers uintptr
|
||||
)
|
||||
|
||||
func init() {
|
||||
k32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
panic("LoadLibrary " + err.Error())
|
||||
}
|
||||
defer syscall.FreeLibrary(k32)
|
||||
|
||||
nSetCommState = getProcAddr(k32, "SetCommState")
|
||||
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
|
||||
nSetCommMask = getProcAddr(k32, "SetCommMask")
|
||||
nSetupComm = getProcAddr(k32, "SetupComm")
|
||||
nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
|
||||
nCreateEvent = getProcAddr(k32, "CreateEventW")
|
||||
nResetEvent = getProcAddr(k32, "ResetEvent")
|
||||
nPurgeComm = getProcAddr(k32, "PurgeComm")
|
||||
nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers")
|
||||
}
|
||||
|
||||
func getProcAddr(lib syscall.Handle, name string) uintptr {
|
||||
addr, err := syscall.GetProcAddress(lib, name)
|
||||
if err != nil {
|
||||
panic(name + " " + err.Error())
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func setCommState(h syscall.Handle, baud int, databits byte, parity Parity, stopbits StopBits) error {
|
||||
var params structDCB
|
||||
params.DCBlength = uint32(unsafe.Sizeof(params))
|
||||
|
||||
params.flags[0] = 0x01 // fBinary
|
||||
params.flags[0] |= 0x10 // Assert DSR
|
||||
|
||||
params.BaudRate = uint32(baud)
|
||||
|
||||
params.ByteSize = databits
|
||||
|
||||
switch parity {
|
||||
case ParityNone:
|
||||
params.Parity = 0
|
||||
case ParityOdd:
|
||||
params.Parity = 1
|
||||
case ParityEven:
|
||||
params.Parity = 2
|
||||
case ParityMark:
|
||||
params.Parity = 3
|
||||
case ParitySpace:
|
||||
params.Parity = 4
|
||||
default:
|
||||
return ErrBadParity
|
||||
}
|
||||
|
||||
switch stopbits {
|
||||
case Stop1:
|
||||
params.StopBits = 0
|
||||
case Stop1Half:
|
||||
params.StopBits = 1
|
||||
case Stop2:
|
||||
params.StopBits = 2
|
||||
default:
|
||||
return ErrBadStopBits
|
||||
}
|
||||
|
||||
r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(¶ms)), 0)
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error {
|
||||
var timeouts structTimeouts
|
||||
const MAXDWORD = 1<<32 - 1
|
||||
|
||||
// blocking read by default
|
||||
var timeoutMs int64 = MAXDWORD - 1
|
||||
|
||||
if readTimeout > 0 {
|
||||
// non-blocking read
|
||||
timeoutMs = readTimeout.Nanoseconds() / 1e6
|
||||
if timeoutMs < 1 {
|
||||
timeoutMs = 1
|
||||
} else if timeoutMs > MAXDWORD-1 {
|
||||
timeoutMs = MAXDWORD - 1
|
||||
}
|
||||
}
|
||||
|
||||
/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx
|
||||
|
||||
For blocking I/O see below:
|
||||
|
||||
Remarks:
|
||||
|
||||
If an application sets ReadIntervalTimeout and
|
||||
ReadTotalTimeoutMultiplier to MAXDWORD and sets
|
||||
ReadTotalTimeoutConstant to a value greater than zero and
|
||||
less than MAXDWORD, one of the following occurs when the
|
||||
ReadFile function is called:
|
||||
|
||||
If there are any bytes in the input buffer, ReadFile returns
|
||||
immediately with the bytes in the buffer.
|
||||
|
||||
If there are no bytes in the input buffer, ReadFile waits
|
||||
until a byte arrives and then returns immediately.
|
||||
|
||||
If no bytes arrive within the time specified by
|
||||
ReadTotalTimeoutConstant, ReadFile times out.
|
||||
*/
|
||||
|
||||
timeouts.ReadIntervalTimeout = MAXDWORD
|
||||
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
|
||||
timeouts.ReadTotalTimeoutConstant = uint32(timeoutMs)
|
||||
|
||||
r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0)
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupComm(h syscall.Handle, in, out int) error {
|
||||
r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setCommMask(h syscall.Handle) error {
|
||||
const EV_RXCHAR = 0x0001
|
||||
r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetEvent(h syscall.Handle) error {
|
||||
r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func purgeComm(h syscall.Handle) error {
|
||||
const PURGE_TXABORT = 0x0001
|
||||
const PURGE_RXABORT = 0x0002
|
||||
const PURGE_TXCLEAR = 0x0004
|
||||
const PURGE_RXCLEAR = 0x0008
|
||||
r, _, err := syscall.Syscall(nPurgeComm, 2, uintptr(h),
|
||||
PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR, 0)
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOverlapped() (*syscall.Overlapped, error) {
|
||||
var overlapped syscall.Overlapped
|
||||
r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
|
||||
if r == 0 {
|
||||
return nil, err
|
||||
}
|
||||
overlapped.HEvent = syscall.Handle(r)
|
||||
return &overlapped, nil
|
||||
}
|
||||
|
||||
func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
|
||||
var n int
|
||||
r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
|
||||
uintptr(h),
|
||||
uintptr(unsafe.Pointer(overlapped)),
|
||||
uintptr(unsafe.Pointer(&n)), 1, 0, 0)
|
||||
if r == 0 {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPort = "COM1"
|
||||
DefaultBaudRate = 9600
|
||||
)
|
||||
|
||||
// COM1,9600,odd
|
||||
func ParseConfigFromAddr(addr string) *Config {
|
||||
cfg := &Config{
|
||||
Name: DefaultPort,
|
||||
Baud: DefaultBaudRate,
|
||||
}
|
||||
ss := strings.Split(addr, ",")
|
||||
switch len(ss) {
|
||||
case 1:
|
||||
cfg.Name = ss[0]
|
||||
case 2:
|
||||
cfg.Name = ss[0]
|
||||
cfg.Baud, _ = strconv.Atoi(ss[1])
|
||||
case 3:
|
||||
cfg.Name = ss[0]
|
||||
cfg.Baud, _ = strconv.Atoi(ss[1])
|
||||
cfg.Parity = parseParity(ss[2])
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func AddrFromConfig(cfg *Config) string {
|
||||
ss := []string{
|
||||
cfg.Name,
|
||||
strconv.Itoa(cfg.Baud),
|
||||
}
|
||||
|
||||
switch cfg.Parity {
|
||||
case ParityEven:
|
||||
ss = append(ss, "even")
|
||||
case ParityOdd:
|
||||
ss = append(ss, "odd")
|
||||
case ParityMark:
|
||||
ss = append(ss, "mark")
|
||||
case ParitySpace:
|
||||
ss = append(ss, "space")
|
||||
}
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
|
||||
func parseParity(s string) Parity {
|
||||
switch strings.ToLower(s) {
|
||||
case "o", "odd":
|
||||
return ParityOdd
|
||||
case "e", "even":
|
||||
return ParityEven
|
||||
case "m", "mark":
|
||||
return ParityMark
|
||||
case "s", "space":
|
||||
return ParitySpace
|
||||
default:
|
||||
return ParityNone
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
dissector "github.com/go-gost/tls-dissector"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtoHTTP = "http"
|
||||
ProtoTLS = "tls"
|
||||
ProtoSSH = "ssh"
|
||||
)
|
||||
|
||||
func Sniff(ctx context.Context, r *bufio.Reader) (proto string, err error) {
|
||||
hdr, err := r.Peek(dissector.RecordHeaderLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// try to sniff TLS traffic
|
||||
tlsVersion := binary.BigEndian.Uint16(hdr[1:3])
|
||||
if hdr[0] == dissector.Handshake &&
|
||||
(tlsVersion >= tls.VersionTLS10 && tlsVersion <= tls.VersionTLS13) {
|
||||
return ProtoTLS, nil
|
||||
}
|
||||
|
||||
// try to sniff HTTP traffic
|
||||
if isHTTP(string(hdr[:])) {
|
||||
return ProtoHTTP, nil
|
||||
}
|
||||
|
||||
if string(hdr) == "SSH-2" {
|
||||
return ProtoSSH, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func isHTTP(s string) bool {
|
||||
return strings.HasPrefix(http.MethodGet, s[:3]) ||
|
||||
strings.HasPrefix(http.MethodPost, s[:4]) ||
|
||||
strings.HasPrefix(http.MethodPut, s[:3]) ||
|
||||
strings.HasPrefix(http.MethodDelete, s) ||
|
||||
strings.HasPrefix(http.MethodOptions, s) ||
|
||||
strings.HasPrefix(http.MethodPatch, s) ||
|
||||
strings.HasPrefix(http.MethodHead, s[:4]) ||
|
||||
strings.HasPrefix(http.MethodConnect, s) ||
|
||||
strings.HasPrefix(http.MethodTrace, s) ||
|
||||
// HTTP/2 connection preface
|
||||
// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
|
||||
strings.HasPrefix(s, "PRI *")
|
||||
}
|
||||
@@ -0,0 +1,882 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/recorder"
|
||||
dissector "github.com/go-gost/tls-dissector"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
xio "github.com/go-gost/x/internal/io"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
xhttp "github.com/go-gost/x/internal/net/http"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
ws_util "github.com/go-gost/x/internal/util/ws"
|
||||
xstats "github.com/go-gost/x/observer/stats"
|
||||
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
|
||||
xrecorder "github.com/go-gost/x/recorder"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultBodySize is the default HTTP body or websocket frame size to record.
|
||||
DefaultBodySize = 64 * 1024 // 64KB
|
||||
// MaxBodySize is the maximum HTTP body or websocket frame size to record.
|
||||
MaxBodySize = 1024 * 1024 // 1MB
|
||||
// DeafultSampleRate is the default websocket sample rate (samples per second).
|
||||
DefaultSampleRate = 10.0
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultCertPool = tls_util.NewMemoryCertPool()
|
||||
)
|
||||
|
||||
type HandleOptions struct {
|
||||
Dial func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
DialTLS func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error)
|
||||
|
||||
Bypass bypass.Bypass
|
||||
RecorderObject *xrecorder.HandlerRecorderObject
|
||||
Log logger.Logger
|
||||
}
|
||||
|
||||
type HandleOption func(opts *HandleOptions)
|
||||
|
||||
func WithDial(dial func(ctx context.Context, network, address string) (net.Conn, error)) HandleOption {
|
||||
return func(opts *HandleOptions) {
|
||||
opts.Dial = dial
|
||||
}
|
||||
}
|
||||
|
||||
func WithDialTLS(dialTLS func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error)) HandleOption {
|
||||
return func(opts *HandleOptions) {
|
||||
opts.DialTLS = dialTLS
|
||||
}
|
||||
}
|
||||
|
||||
func WithBypass(bypass bypass.Bypass) HandleOption {
|
||||
return func(opts *HandleOptions) {
|
||||
opts.Bypass = bypass
|
||||
}
|
||||
}
|
||||
|
||||
func WithRecorderObject(ro *xrecorder.HandlerRecorderObject) HandleOption {
|
||||
return func(opts *HandleOptions) {
|
||||
opts.RecorderObject = ro
|
||||
}
|
||||
}
|
||||
|
||||
func WithLog(log logger.Logger) HandleOption {
|
||||
return func(opts *HandleOptions) {
|
||||
opts.Log = log
|
||||
}
|
||||
}
|
||||
|
||||
type Sniffer struct {
|
||||
Websocket bool
|
||||
WebsocketSampleRate float64
|
||||
|
||||
Recorder recorder.Recorder
|
||||
RecorderOptions *recorder.Options
|
||||
|
||||
// MITM TLS termination
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey crypto.PrivateKey
|
||||
NegotiatedProtocol string
|
||||
CertPool tls_util.CertPool
|
||||
MitmBypass bypass.Bypass
|
||||
|
||||
ReadTimeout time.Duration
|
||||
}
|
||||
|
||||
func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
|
||||
var ho HandleOptions
|
||||
for _, opt := range opts {
|
||||
opt(&ho)
|
||||
}
|
||||
|
||||
pStats := xstats.Stats{}
|
||||
conn = stats_wrapper.WrapConn(conn, &pStats)
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log := ho.Log
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
ro := ho.RecorderObject
|
||||
ro.HTTP = &xrecorder.HTTPRecorderObject{
|
||||
Host: req.Host,
|
||||
Proto: req.Proto,
|
||||
Scheme: req.URL.Scheme,
|
||||
Method: req.Method,
|
||||
URI: req.RequestURI,
|
||||
Request: xrecorder.HTTPRequestRecorderObject{
|
||||
ContentLength: req.ContentLength,
|
||||
Header: req.Header.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
|
||||
ro.ClientIP = clientIP.String()
|
||||
}
|
||||
|
||||
clientAddr := ro.RemoteAddr
|
||||
if ro.ClientIP != "" {
|
||||
if _, port, _ := net.SplitHostPort(ro.RemoteAddr); port != "" {
|
||||
clientAddr = net.JoinHostPort(ro.ClientIP, port)
|
||||
}
|
||||
}
|
||||
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
|
||||
|
||||
// http/2
|
||||
if req.Method == "PRI" && len(req.Header) == 0 && req.URL.Path == "*" && req.Proto == "HTTP/2.0" {
|
||||
return h.serveH2(ctx, xnet.NewReadWriteConn(br, conn, conn), &ho)
|
||||
}
|
||||
|
||||
host := req.Host
|
||||
if host != "" {
|
||||
if _, _, err := net.SplitHostPort(host); err != nil {
|
||||
host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
|
||||
}
|
||||
ro.Host = host
|
||||
|
||||
log = log.WithFields(map[string]any{
|
||||
"host": host,
|
||||
})
|
||||
|
||||
if ho.Bypass != nil && ho.Bypass.Contains(ctx, "tcp", host) {
|
||||
return xbypass.ErrBypass
|
||||
}
|
||||
}
|
||||
|
||||
dial := ho.Dial
|
||||
if dial == nil {
|
||||
dial = (&net.Dialer{}).DialContext
|
||||
}
|
||||
cc, err := dial(ctx, "tcp", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
ro.Time = time.Time{}
|
||||
|
||||
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log)
|
||||
if err != nil || shouldClose {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
pStats.Reset()
|
||||
|
||||
req, err := http.ReadRequest(br)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log); err != nil || shouldClose {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Sniffer) serveH2(ctx context.Context, conn net.Conn, ho *HandleOptions) error {
|
||||
const expectedBody = "SM\r\n\r\n"
|
||||
|
||||
buf := make([]byte, len(expectedBody))
|
||||
n, err := io.ReadFull(conn, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("h2: error reading client preface: %s", err)
|
||||
}
|
||||
if string(buf[:n]) != expectedBody {
|
||||
return errors.New("h2: invalid client preface")
|
||||
}
|
||||
|
||||
ro := ho.RecorderObject
|
||||
log := ho.Log
|
||||
|
||||
ro.Time = time.Time{}
|
||||
|
||||
tr := &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
if dial := ho.DialTLS; dial != nil {
|
||||
return dial(ctx, network, addr, cfg)
|
||||
}
|
||||
|
||||
cc, err := (&net.Dialer{}).DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc = tls.Client(cc, cfg)
|
||||
return cc, nil
|
||||
},
|
||||
}
|
||||
defer tr.CloseIdleConnections()
|
||||
|
||||
(&http2.Server{}).ServeConn(conn, &http2.ServeConnOpts{
|
||||
Context: ctx,
|
||||
SawClientPreface: true,
|
||||
Handler: &h2Handler{
|
||||
transport: tr,
|
||||
recorder: h.Recorder,
|
||||
recorderOptions: h.RecorderOptions,
|
||||
recorderObject: ro,
|
||||
log: log,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, pStats stats.Stats, log logger.Logger) (close bool, err error) {
|
||||
close = true
|
||||
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro = ro2
|
||||
|
||||
ro.Time = time.Now()
|
||||
log.Infof("%s <-> %s", ro.RemoteAddr, req.Host)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ro.Err = err.Error()
|
||||
}
|
||||
ro.InputBytes = pStats.Get(stats.KindInputBytes)
|
||||
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
|
||||
ro.Duration = time.Since(ro.Time)
|
||||
if err := ro.Record(ctx, h.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(ro.Time),
|
||||
"inputBytes": ro.InputBytes,
|
||||
"outputBytes": ro.OutputBytes,
|
||||
}).Infof("%s >-< %s", ro.RemoteAddr, req.Host)
|
||||
}()
|
||||
|
||||
ro.HTTP = &xrecorder.HTTPRecorderObject{
|
||||
Host: req.Host,
|
||||
Proto: req.Proto,
|
||||
Scheme: req.URL.Scheme,
|
||||
Method: req.Method,
|
||||
URI: req.RequestURI,
|
||||
Request: xrecorder.HTTPRequestRecorderObject{
|
||||
ContentLength: req.ContentLength,
|
||||
Header: req.Header.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
// HTTP/1.0
|
||||
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
|
||||
if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" {
|
||||
req.Header.Del("Connection")
|
||||
} else {
|
||||
req.Header.Set("Connection", "close")
|
||||
}
|
||||
}
|
||||
|
||||
var reqBody *xhttp.Body
|
||||
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
|
||||
if req.Body != nil {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = DefaultBodySize
|
||||
}
|
||||
if bodySize > MaxBodySize {
|
||||
bodySize = MaxBodySize
|
||||
}
|
||||
reqBody = xhttp.NewBody(req.Body, bodySize)
|
||||
req.Body = reqBody
|
||||
}
|
||||
}
|
||||
|
||||
err = req.Write(cc)
|
||||
|
||||
if reqBody != nil {
|
||||
ro.HTTP.Request.Body = reqBody.Content()
|
||||
ro.HTTP.Request.ContentLength = reqBody.Length()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
|
||||
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read response: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
xio.SetReadDeadline(cc, time.Time{})
|
||||
|
||||
ro.HTTP.StatusCode = resp.StatusCode
|
||||
ro.HTTP.Response.Header = resp.Header
|
||||
ro.HTTP.Response.ContentLength = resp.ContentLength
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusSwitchingProtocols {
|
||||
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
|
||||
return
|
||||
}
|
||||
|
||||
// HTTP/1.0
|
||||
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
|
||||
if !resp.Close {
|
||||
resp.Header.Set("Connection", "keep-alive")
|
||||
}
|
||||
resp.ProtoMajor = req.ProtoMajor
|
||||
resp.ProtoMinor = req.ProtoMinor
|
||||
}
|
||||
|
||||
var respBody *xhttp.Body
|
||||
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = DefaultBodySize
|
||||
}
|
||||
if bodySize > MaxBodySize {
|
||||
bodySize = MaxBodySize
|
||||
}
|
||||
respBody = xhttp.NewBody(resp.Body, bodySize)
|
||||
resp.Body = respBody
|
||||
}
|
||||
|
||||
err = resp.Write(rw)
|
||||
|
||||
if respBody != nil {
|
||||
ro.HTTP.Response.Body = respBody.Content()
|
||||
ro.HTTP.Response.ContentLength = respBody.Length()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("write response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.ContentLength >= 0 {
|
||||
close = resp.Close
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func upgradeType(h http.Header) string {
|
||||
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||
return ""
|
||||
}
|
||||
return h.Get("Upgrade")
|
||||
}
|
||||
|
||||
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
||||
reqUpType := upgradeType(req.Header)
|
||||
resUpType := upgradeType(res.Header)
|
||||
if !strings.EqualFold(reqUpType, resUpType) {
|
||||
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
|
||||
}
|
||||
|
||||
res.Body = nil
|
||||
if err := res.Write(rw); err != nil {
|
||||
return fmt.Errorf("response write: %v", err)
|
||||
}
|
||||
|
||||
if reqUpType == "websocket" && h.Websocket {
|
||||
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
|
||||
}
|
||||
|
||||
return xnet.Transport(rw, cc)
|
||||
}
|
||||
|
||||
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
||||
errc := make(chan error, 1)
|
||||
|
||||
sampleRate := h.WebsocketSampleRate
|
||||
if sampleRate == 0 {
|
||||
sampleRate = DefaultSampleRate
|
||||
}
|
||||
if sampleRate < 0 {
|
||||
sampleRate = math.MaxFloat64
|
||||
}
|
||||
|
||||
go func() {
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro := ro2
|
||||
|
||||
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for {
|
||||
start := time.Now()
|
||||
|
||||
if err := h.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
if limiter.Allow() {
|
||||
ro.Duration = time.Since(start)
|
||||
ro.Time = time.Now()
|
||||
if err := ro.Record(ctx, h.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro := ro2
|
||||
|
||||
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for {
|
||||
start := time.Now()
|
||||
|
||||
if err := h.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
if limiter.Allow() {
|
||||
ro.Duration = time.Since(start)
|
||||
ro.Time = time.Now()
|
||||
if err := ro.Record(ctx, h.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-errc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
|
||||
fr := ws_util.Frame{}
|
||||
if _, err = fr.ReadFrom(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ws := &xrecorder.WebsocketRecorderObject{
|
||||
From: from,
|
||||
Fin: fr.Header.Fin,
|
||||
Rsv1: fr.Header.Rsv1,
|
||||
Rsv2: fr.Header.Rsv2,
|
||||
Rsv3: fr.Header.Rsv3,
|
||||
OpCode: int(fr.Header.OpCode),
|
||||
Masked: fr.Header.Masked,
|
||||
MaskKey: fr.Header.MaskKey,
|
||||
Length: fr.Header.PayloadLength,
|
||||
}
|
||||
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = DefaultBodySize
|
||||
}
|
||||
if bodySize > MaxBodySize {
|
||||
bodySize = MaxBodySize
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Payload = buf.Bytes()
|
||||
}
|
||||
|
||||
ro.Websocket = ws
|
||||
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
|
||||
if from == "client" {
|
||||
ro.InputBytes = length
|
||||
ro.OutputBytes = 0
|
||||
} else {
|
||||
ro.InputBytes = 0
|
||||
ro.OutputBytes = length
|
||||
}
|
||||
|
||||
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
|
||||
if _, err := fr.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
|
||||
var ho HandleOptions
|
||||
for _, opt := range opts {
|
||||
opt(&ho)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
clientHello, err := dissector.ParseClientHello(io.TeeReader(conn, buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log := ho.Log
|
||||
|
||||
ro := ho.RecorderObject
|
||||
ro.TLS = &xrecorder.TLSRecorderObject{
|
||||
ServerName: clientHello.ServerName,
|
||||
ClientHello: hex.EncodeToString(buf.Bytes()),
|
||||
}
|
||||
if len(clientHello.SupportedProtos) > 0 {
|
||||
ro.TLS.Proto = clientHello.SupportedProtos[0]
|
||||
}
|
||||
|
||||
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(ro.RemoteAddr))
|
||||
|
||||
host := clientHello.ServerName
|
||||
if host != "" {
|
||||
if _, _, err := net.SplitHostPort(host); err != nil {
|
||||
host = net.JoinHostPort(strings.Trim(host, "[]"), "443")
|
||||
}
|
||||
ro.Host = host
|
||||
|
||||
if ho.Bypass != nil && ho.Bypass.Contains(ctx, "tcp", host) {
|
||||
return xbypass.ErrBypass
|
||||
}
|
||||
}
|
||||
|
||||
dial := ho.Dial
|
||||
if dial == nil {
|
||||
dial = (&net.Dialer{}).DialContext
|
||||
}
|
||||
cc, err := dial(ctx, "tcp", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if h.Certificate != nil && h.PrivateKey != nil &&
|
||||
len(clientHello.SupportedProtos) > 0 && (clientHello.SupportedProtos[0] == "h2" || clientHello.SupportedProtos[0] == "http/1.1") {
|
||||
if host == "" {
|
||||
host = ro.Host
|
||||
}
|
||||
if h.MitmBypass == nil || !h.MitmBypass.Contains(ctx, "tcp", host) {
|
||||
return h.terminateTLS(ctx, xnet.NewReadWriteConn(io.MultiReader(buf, conn), conn, conn), cc, clientHello, &ho)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := buf.WriteTo(cc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
|
||||
serverHello, err := dissector.ParseServerHello(io.TeeReader(cc, buf))
|
||||
xio.SetReadDeadline(cc, time.Time{})
|
||||
|
||||
if serverHello != nil {
|
||||
ro.TLS.CipherSuite = tls_util.CipherSuite(serverHello.CipherSuite).String()
|
||||
ro.TLS.CompressionMethod = serverHello.CompressionMethod
|
||||
if serverHello.Proto != "" {
|
||||
ro.TLS.Proto = serverHello.Proto
|
||||
}
|
||||
if serverHello.Version > 0 {
|
||||
ro.TLS.Version = tls_util.Version(serverHello.Version).String()
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
ro.TLS.ServerHello = hex.EncodeToString(buf.Bytes())
|
||||
}
|
||||
|
||||
if _, err := buf.WriteTo(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("%s <-> %s", ro.RemoteAddr, ro.Host)
|
||||
xnet.Transport(conn, cc)
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(ro.Time),
|
||||
}).Infof("%s >-< %s", ro.RemoteAddr, ro.Host)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Sniffer) terminateTLS(ctx context.Context, conn, cc net.Conn, clientHello *dissector.ClientHelloInfo, ho *HandleOptions) error {
|
||||
ro := ho.RecorderObject
|
||||
log := ho.Log
|
||||
|
||||
nextProtos := clientHello.SupportedProtos
|
||||
if h.NegotiatedProtocol != "" {
|
||||
nextProtos = []string{h.NegotiatedProtocol}
|
||||
}
|
||||
|
||||
cfg := &tls.Config{
|
||||
ServerName: clientHello.ServerName,
|
||||
NextProtos: nextProtos,
|
||||
CipherSuites: clientHello.CipherSuites,
|
||||
}
|
||||
if cfg.ServerName == "" {
|
||||
cfg.InsecureSkipVerify = true
|
||||
}
|
||||
clientConn := tls.Client(cc, cfg)
|
||||
if err := clientConn.HandshakeContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := clientConn.ConnectionState()
|
||||
ro.TLS.CipherSuite = tls_util.CipherSuite(cs.CipherSuite).String()
|
||||
ro.TLS.Proto = cs.NegotiatedProtocol
|
||||
ro.TLS.Version = tls_util.Version(cs.Version).String()
|
||||
|
||||
host := cfg.ServerName
|
||||
if host == "" {
|
||||
if host = cs.PeerCertificates[0].Subject.CommonName; host == "" {
|
||||
host = ro.Host
|
||||
}
|
||||
}
|
||||
if h, _, _ := net.SplitHostPort(host); h != "" {
|
||||
host = h
|
||||
}
|
||||
|
||||
negotiatedProtocol := cs.NegotiatedProtocol
|
||||
if h.NegotiatedProtocol != "" {
|
||||
negotiatedProtocol = h.NegotiatedProtocol
|
||||
}
|
||||
nextProtos = nil
|
||||
if negotiatedProtocol != "" {
|
||||
nextProtos = []string{negotiatedProtocol}
|
||||
}
|
||||
|
||||
// cache the tls server handshake record.
|
||||
wb := &bytes.Buffer{}
|
||||
conn = xnet.NewReadWriteConn(conn, io.MultiWriter(wb, conn), conn)
|
||||
|
||||
serverConn := tls.Server(conn, &tls.Config{
|
||||
NextProtos: nextProtos,
|
||||
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
certPool := h.CertPool
|
||||
if certPool == nil {
|
||||
certPool = DefaultCertPool
|
||||
}
|
||||
serverName := chi.ServerName
|
||||
if serverName == "" {
|
||||
serverName = host
|
||||
}
|
||||
cert, err := certPool.Get(serverName)
|
||||
if cert != nil {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(h.Certificate)
|
||||
if _, err = cert.Verify(x509.VerifyOptions{
|
||||
DNSName: serverName,
|
||||
Roots: pool,
|
||||
}); err != nil {
|
||||
log.Warnf("verify cached certificate for %s: %v", serverName, err)
|
||||
cert = nil
|
||||
}
|
||||
}
|
||||
if cert == nil {
|
||||
cert, err = tls_util.GenerateCertificate(serverName, 7*24*time.Hour, h.Certificate, h.PrivateKey)
|
||||
certPool.Put(serverName, cert)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{cert.Raw},
|
||||
PrivateKey: h.PrivateKey,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
err := serverConn.HandshakeContext(ctx)
|
||||
if record, _ := dissector.ReadRecord(wb); record != nil {
|
||||
wb.Reset()
|
||||
record.WriteTo(wb)
|
||||
ro.TLS.ServerHello = hex.EncodeToString(wb.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := []HandleOption{
|
||||
WithDial(func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return clientConn, nil
|
||||
}),
|
||||
WithDialTLS(func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error) {
|
||||
return clientConn, nil
|
||||
}),
|
||||
WithRecorderObject(ro),
|
||||
WithLog(log),
|
||||
}
|
||||
return h.HandleHTTP(ctx, serverConn, opts...)
|
||||
}
|
||||
|
||||
type h2Handler struct {
|
||||
transport http.RoundTripper
|
||||
recorder recorder.Recorder
|
||||
recorderOptions *recorder.Options
|
||||
recorderObject *xrecorder.HandlerRecorderObject
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log := h.log
|
||||
|
||||
ro := &xrecorder.HandlerRecorderObject{}
|
||||
*ro = *h.recorderObject
|
||||
ro.Time = time.Now()
|
||||
|
||||
var err error
|
||||
log.Infof("%s <-> %s", ro.RemoteAddr, r.Host)
|
||||
defer func() {
|
||||
ro.Duration = time.Since(ro.Time)
|
||||
if err != nil {
|
||||
ro.Err = err.Error()
|
||||
}
|
||||
if err := ro.Record(r.Context(), h.recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(ro.Time),
|
||||
}).Infof("%s >-< %s", ro.RemoteAddr, r.Host)
|
||||
}()
|
||||
|
||||
if clientIP := xhttp.GetClientIP(r); clientIP != nil {
|
||||
ro.ClientIP = clientIP.String()
|
||||
}
|
||||
ro.HTTP = &xrecorder.HTTPRecorderObject{
|
||||
Host: r.Host,
|
||||
Proto: r.Proto,
|
||||
Scheme: "https",
|
||||
Method: r.Method,
|
||||
URI: r.RequestURI,
|
||||
Request: xrecorder.HTTPRequestRecorderObject{
|
||||
ContentLength: r.ContentLength,
|
||||
Header: r.Header.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
url := r.URL
|
||||
url.Scheme = "https"
|
||||
url.Host = r.Host
|
||||
req := &http.Request{
|
||||
Method: r.Method,
|
||||
URL: url,
|
||||
Host: r.Host,
|
||||
Header: r.Header,
|
||||
Body: r.Body,
|
||||
ContentLength: r.ContentLength,
|
||||
Trailer: r.Trailer,
|
||||
}
|
||||
|
||||
var reqBody *xhttp.Body
|
||||
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
|
||||
if req.Body != nil {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = DefaultBodySize
|
||||
}
|
||||
if bodySize > MaxBodySize {
|
||||
bodySize = MaxBodySize
|
||||
}
|
||||
|
||||
reqBody = xhttp.NewBody(req.Body, bodySize)
|
||||
req.Body = reqBody
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := h.transport.RoundTrip(req.WithContext(r.Context()))
|
||||
if reqBody != nil {
|
||||
ro.HTTP.Request.Body = reqBody.Content()
|
||||
ro.HTTP.Request.ContentLength = reqBody.Length()
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ro.HTTP.StatusCode = resp.StatusCode
|
||||
ro.HTTP.Response.Header = resp.Header
|
||||
ro.HTTP.Response.ContentLength = resp.ContentLength
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
h.setHeader(w, resp.Header)
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
var respBody *xhttp.Body
|
||||
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = DefaultBodySize
|
||||
}
|
||||
if bodySize > MaxBodySize {
|
||||
bodySize = MaxBodySize
|
||||
}
|
||||
respBody = xhttp.NewBody(resp.Body, bodySize)
|
||||
resp.Body = respBody
|
||||
}
|
||||
|
||||
io.Copy(w, resp.Body)
|
||||
|
||||
if respBody != nil {
|
||||
ro.HTTP.Response.Body = respBody.Content()
|
||||
ro.HTTP.Response.ContentLength = respBody.Length()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *h2Handler) setHeader(w http.ResponseWriter, header http.Header) {
|
||||
for k, v := range header {
|
||||
for i := range v {
|
||||
w.Header().Add(k, v[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gosocks5"
|
||||
)
|
||||
|
||||
type udpTunConn struct {
|
||||
net.Conn
|
||||
taddr net.Addr
|
||||
}
|
||||
|
||||
func UDPTunClientConn(c net.Conn, targetAddr net.Addr) net.Conn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
taddr: targetAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPTunClientPacketConn(c net.Conn) net.PacketConn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPTunServerConn(c net.Conn) net.PacketConn {
|
||||
return &udpTunConn{
|
||||
Conn: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpTunConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
socksAddr := gosocks5.Addr{}
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
_, err = dgram.ReadFrom(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = len(dgram.Data)
|
||||
if n > len(b) {
|
||||
n = copy(b, dgram.Data)
|
||||
}
|
||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
dgram.Header.Rsv = uint16(len(dgram.Data))
|
||||
dgram.Header.Frag = 0xff // UDP tun relay flag, used by shadowsocks
|
||||
_, err = dgram.WriteTo(c.Conn)
|
||||
n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpTunConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
const (
|
||||
MaxMessageSize = math.MaxUint16
|
||||
)
|
||||
|
||||
type udpConn struct {
|
||||
net.PacketConn
|
||||
raddr net.Addr
|
||||
taddr net.Addr
|
||||
}
|
||||
|
||||
func UDPConn(c net.PacketConn) net.PacketConn {
|
||||
return &udpConn{
|
||||
PacketConn: c,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads an UDP datagram.
|
||||
// NOTE: for server side,
|
||||
// the returned addr is the target address the client want to relay to.
|
||||
func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
var rbuf [MaxMessageSize]byte
|
||||
|
||||
n, c.raddr, err = c.PacketConn.ReadFrom(rbuf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
hlen, err := header.ReadFrom(bytes.NewReader(rbuf[:n]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n = copy(b, rbuf[hlen:n])
|
||||
|
||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
|
||||
var wbuf [MaxMessageSize]byte
|
||||
buf := bytes.NewBuffer(wbuf[:][:0])
|
||||
_, err = dgram.WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
|
||||
n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
func (c *udpConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package socks
|
||||
|
||||
const (
|
||||
// MethodTLS is an extended SOCKS5 method with tls encryption support.
|
||||
MethodTLS uint8 = 0x80
|
||||
// MethodTLSAuth is an extended SOCKS5 method with tls encryption and authentication support.
|
||||
MethodTLSAuth uint8 = 0x82
|
||||
// MethodMux is an extended SOCKS5 method for stream multiplexing.
|
||||
MethodMux = 0x88
|
||||
)
|
||||
|
||||
const (
|
||||
// CmdMuxBind is an extended SOCKS5 request CMD for
|
||||
// multiplexing transport with the binding server.
|
||||
CmdMuxBind uint8 = 0xF2
|
||||
// CmdUDPTun is an extended SOCKS5 request CMD for UDP over TCP.
|
||||
CmdUDPTun uint8 = 0xF3
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gosocks5"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxMessageSize = math.MaxUint16
|
||||
)
|
||||
|
||||
var (
|
||||
_ net.PacketConn = (*UDPConn)(nil)
|
||||
_ net.Conn = (*UDPConn)(nil)
|
||||
)
|
||||
|
||||
type UDPConn struct {
|
||||
net.PacketConn
|
||||
raddr net.Addr
|
||||
taddr net.Addr
|
||||
}
|
||||
|
||||
func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr) *UDPConn {
|
||||
return &UDPConn{
|
||||
PacketConn: c,
|
||||
raddr: remoteAddr,
|
||||
taddr: targetAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPServerConn(c net.PacketConn, remoteAddr net.Addr) *UDPConn {
|
||||
return &UDPConn{
|
||||
PacketConn: c,
|
||||
raddr: remoteAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
var rbuf [MaxMessageSize]byte
|
||||
|
||||
n, _, err = c.PacketConn.ReadFrom(rbuf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
saddr := gosocks5.Addr{}
|
||||
addrLen, err := saddr.ReadFrom(bytes.NewReader(rbuf[:n]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = copy(b, rbuf[addrLen:n])
|
||||
addr, err = net.ResolveUDPAddr("udp", saddr.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UDPConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
var wbuf [MaxMessageSize]byte
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addrLen, err := socksAddr.Encode(wbuf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = copy(wbuf[addrLen:], b)
|
||||
_, err = c.PacketConn.WriteTo(wbuf[:addrLen+n], c.raddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *UDPConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
func (c *UDPConn) RemoteAddr() net.Addr {
|
||||
return c.raddr
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||
)
|
||||
|
||||
type shadowCipher struct {
|
||||
cipher *ss.Cipher
|
||||
}
|
||||
|
||||
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
|
||||
return ss.NewConn(conn, c.cipher.Copy())
|
||||
}
|
||||
|
||||
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
|
||||
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
|
||||
}
|
||||
|
||||
func ShadowCipher(method, password string, key string) (core.Cipher, error) {
|
||||
if method == "" || password == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c, _ := ss.NewCipher(method, password)
|
||||
if c != nil {
|
||||
return &shadowCipher{cipher: c}, nil
|
||||
}
|
||||
|
||||
return core.PickCipher(method, []byte(key), password)
|
||||
}
|
||||
|
||||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
||||
// we wrap around it to make io.Copy happy.
|
||||
type shadowConn struct {
|
||||
net.Conn
|
||||
wbuf bytes.Buffer
|
||||
}
|
||||
|
||||
func ShadowConn(conn net.Conn, header []byte) net.Conn {
|
||||
c := &shadowConn{
|
||||
Conn: conn,
|
||||
}
|
||||
c.wbuf.Write(header)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
||||
n = len(b) // force byte length consistent
|
||||
if c.wbuf.Len() > 0 {
|
||||
c.wbuf.Write(b) // append the data to the cached header
|
||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
||||
c.wbuf.Reset()
|
||||
return
|
||||
}
|
||||
_, err = c.Conn.Write(b)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// a dummy ssh client conn used by client connector
|
||||
type ClientConn struct {
|
||||
net.Conn
|
||||
client *ssh.Client
|
||||
}
|
||||
|
||||
func NewClientConn(session *Session) net.Conn {
|
||||
return &ClientConn{
|
||||
Conn: session.Conn,
|
||||
client: session.client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientConn) Client() *ssh.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
type sshConn struct {
|
||||
channel ssh.Channel
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, channel ssh.Channel) net.Conn {
|
||||
return &sshConn{
|
||||
Conn: conn,
|
||||
channel: channel,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sshConn) Read(b []byte) (n int, err error) {
|
||||
return c.channel.Read(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Write(b []byte) (n int, err error) {
|
||||
return c.channel.Write(b)
|
||||
}
|
||||
|
||||
func (c *sshConn) Close() error {
|
||||
return c.channel.Close()
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultKeepaliveInterval = 30 * time.Second
|
||||
defaultKeepaliveTimeout = 15 * time.Second
|
||||
defaultkeepaliveRetries = 1
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
net.Conn
|
||||
client *ssh.Client
|
||||
closed chan struct{}
|
||||
dead chan struct{}
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
func NewSession(c net.Conn, client *ssh.Client, log logger.Logger) *Session {
|
||||
return &Session{
|
||||
Conn: c,
|
||||
client: client,
|
||||
closed: make(chan struct{}),
|
||||
dead: make(chan struct{}),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) OpenChannel(name string) (ssh.Channel, <-chan *ssh.Request, error) {
|
||||
return s.client.OpenChannel(name, nil)
|
||||
}
|
||||
|
||||
func (s *Session) IsClosed() bool {
|
||||
select {
|
||||
case <-s.dead:
|
||||
return true
|
||||
case <-s.closed:
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Session) Wait() error {
|
||||
defer close(s.closed)
|
||||
|
||||
return s.client.Wait()
|
||||
}
|
||||
|
||||
func (s *Session) WaitClose() {
|
||||
defer s.client.Close()
|
||||
|
||||
select {
|
||||
case <-s.dead:
|
||||
s.log.Debugf("session is dead")
|
||||
case <-s.closed:
|
||||
s.log.Debugf("session is closed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Keepalive(interval, timeout time.Duration, retries int) {
|
||||
if interval <= 0 {
|
||||
interval = defaultKeepaliveInterval
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = defaultKeepaliveTimeout
|
||||
}
|
||||
if retries <= 0 {
|
||||
retries = defaultkeepaliveRetries
|
||||
}
|
||||
|
||||
s.log.Debugf("keepalive is enabled, interval: %v, timeout: %v, retries: %d", interval, timeout, retries)
|
||||
defer close(s.dead)
|
||||
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
|
||||
count := retries
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
start := time.Now()
|
||||
err := func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
select {
|
||||
case err := <-s.ping():
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
s.log.Debugf("ssh ping: %v", err)
|
||||
count--
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
s.log.Debugf("ssh ping OK, RTT: %v", time.Since(start))
|
||||
count = retries
|
||||
case <-s.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) ping() <-chan error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if _, _, err := s.client.SendRequest("ping", true, nil); err != nil {
|
||||
ch <- err
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSessionDead = errors.New("session is dead")
|
||||
)
|
||||
|
||||
// PasswordCallbackFunc is a callback function used by SSH server.
|
||||
// It authenticates user using a password.
|
||||
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
||||
|
||||
func PasswordCallback(au auth.Authenticator) PasswordCallbackFunc {
|
||||
if au == nil {
|
||||
return nil
|
||||
}
|
||||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
if _, ok := au.Authenticate(context.Background(), conn.User(), string(password)); ok {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %s", conn.User())
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeyCallbackFunc is a callback function used by SSH server.
|
||||
// It offers a public key for authentication.
|
||||
type PublicKeyCallbackFunc func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error)
|
||||
|
||||
func PublicKeyCallback(keys map[string]bool) PublicKeyCallbackFunc {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if keys[string(pubKey.Marshal())] {
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
Extensions: map[string]string{
|
||||
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown public key for %q", c.User())
|
||||
}
|
||||
}
|
||||
|
||||
// ParseSSHAuthorizedKeysFile parses ssh authorized keys file.
|
||||
func ParseAuthorizedKeysFile(name string) (map[string]bool, error) {
|
||||
authorizedKeysBytes, err := os.ReadFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizedKeysMap := make(map[string]bool)
|
||||
for len(authorizedKeysBytes) > 0 {
|
||||
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizedKeysMap[string(pubKey.Marshal())] = true
|
||||
authorizedKeysBytes = rest
|
||||
}
|
||||
|
||||
return authorizedKeysMap, nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package sshd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type DirectForwardConn struct {
|
||||
conn ssh.Conn
|
||||
channel ssh.Channel
|
||||
dstAddr string
|
||||
}
|
||||
|
||||
func NewDirectForwardConn(conn ssh.Conn, channel ssh.Channel, dstAddr string) net.Conn {
|
||||
return &DirectForwardConn{
|
||||
conn: conn,
|
||||
channel: channel,
|
||||
dstAddr: dstAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) Read(b []byte) (n int, err error) {
|
||||
return c.channel.Read(b)
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) Write(b []byte) (n int, err error) {
|
||||
return c.channel.Write(b)
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) Close() error {
|
||||
return c.channel.Close()
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *DirectForwardConn) DstAddr() string {
|
||||
return c.dstAddr
|
||||
}
|
||||
|
||||
type RemoteForwardConn struct {
|
||||
ctx context.Context
|
||||
conn ssh.Conn
|
||||
req *ssh.Request
|
||||
}
|
||||
|
||||
func NewRemoteForwardConn(ctx context.Context, conn ssh.Conn, req *ssh.Request) net.Conn {
|
||||
return &RemoteForwardConn{
|
||||
ctx: ctx,
|
||||
conn: conn,
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Conn() ssh.Conn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Request() *ssh.Request {
|
||||
return c.req
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Read(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Write(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Close() error {
|
||||
return &net.OpError{Op: "close", Net: "nop", Source: nil, Addr: nil, Err: errors.New("close not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *RemoteForwardConn) Done() <-chan struct{} {
|
||||
return c.ctx.Done()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/core/observer"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
xstats "github.com/go-gost/x/observer/stats"
|
||||
)
|
||||
|
||||
type HandlerStats struct {
|
||||
service string
|
||||
stats map[string]stats.Stats
|
||||
resetTraffic bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHandlerStats(service string, resetTraffic bool) *HandlerStats {
|
||||
return &HandlerStats{
|
||||
service: service,
|
||||
stats: make(map[string]stats.Stats),
|
||||
resetTraffic: resetTraffic,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *HandlerStats) Stats(client string) stats.Stats {
|
||||
p.mu.RLock()
|
||||
pstats := p.stats[client]
|
||||
p.mu.RUnlock()
|
||||
if pstats != nil {
|
||||
return pstats
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
pstats = p.stats[client]
|
||||
if pstats == nil {
|
||||
pstats = xstats.NewStats(p.resetTraffic)
|
||||
}
|
||||
p.stats[client] = pstats
|
||||
|
||||
return pstats
|
||||
}
|
||||
|
||||
func (p *HandlerStats) Events() (events []observer.Event) {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
for k, v := range p.stats {
|
||||
if !v.IsUpdated() {
|
||||
continue
|
||||
}
|
||||
events = append(events, xstats.StatsEvent{
|
||||
Kind: "handler",
|
||||
Service: p.service,
|
||||
Client: k,
|
||||
TotalConns: v.Get(stats.KindTotalConns),
|
||||
CurrentConns: v.Get(stats.KindCurrentConns),
|
||||
InputBytes: v.Get(stats.KindInputBytes),
|
||||
OutputBytes: v.Get(stats.KindOutputBytes),
|
||||
TotalErrs: v.Get(stats.KindTotalErrs),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package tap
|
||||
|
||||
import "net"
|
||||
|
||||
// Route is an IP routing entry
|
||||
type Route struct {
|
||||
Net net.IPNet
|
||||
Gateway net.IP
|
||||
}
|
||||
type Config struct {
|
||||
Name string
|
||||
Net string
|
||||
MTU int
|
||||
Gateway string
|
||||
Routes []Route
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
const (
|
||||
VersionTLS10 = "VersionTLS10"
|
||||
VersionTLS11 = "VersionTLS11"
|
||||
VersionTLS12 = "VersionTLS12"
|
||||
VersionTLS13 = "VersionTLS13"
|
||||
)
|
||||
|
||||
// Cipher suites from https://pkg.go.dev/crypto/tls#pkg-constants
|
||||
const (
|
||||
// TLS 1.0 - 1.2 cipher suites.
|
||||
TLS_RSA_WITH_RC4_128_SHA = "TLS_RSA_WITH_RC4_128_SHA"
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA"
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA"
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256 = "TLS_RSA_WITH_AES_128_CBC_SHA256"
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256 = "TLS_RSA_WITH_AES_128_GCM_SHA256"
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384 = "TLS_RSA_WITH_AES_256_GCM_SHA384"
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
|
||||
TLS_ECDHE_RSA_WITH_RC4_128_SHA = "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
|
||||
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
|
||||
|
||||
// TLS 1.3 cipher suites.
|
||||
TLS_AES_128_GCM_SHA256 = "TLS_AES_128_GCM_SHA256"
|
||||
TLS_AES_256_GCM_SHA384 = "TLS_AES_256_GCM_SHA384"
|
||||
TLS_CHACHA20_POLY1305_SHA256 = "TLS_CHACHA20_POLY1305_SHA256"
|
||||
|
||||
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
|
||||
// that the client is doing version fallback. See RFC 7507.
|
||||
TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"
|
||||
)
|
||||
|
||||
var (
|
||||
cipherSuites = map[uint16]string{
|
||||
tls.TLS_RSA_WITH_RC4_128_SHA: TLS_RSA_WITH_RC4_128_SHA,
|
||||
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA: TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA: TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA256: TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
|
||||
tls.TLS_AES_128_GCM_SHA256: TLS_AES_128_GCM_SHA256,
|
||||
tls.TLS_AES_256_GCM_SHA384: TLS_AES_256_GCM_SHA384,
|
||||
tls.TLS_CHACHA20_POLY1305_SHA256: TLS_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
versions = map[uint16]string{
|
||||
tls.VersionSSL30: "sslv3",
|
||||
tls.VersionTLS10: "tls1.0",
|
||||
tls.VersionTLS11: "tls1.1",
|
||||
tls.VersionTLS12: "tls1.2",
|
||||
tls.VersionTLS13: "tls1.3",
|
||||
}
|
||||
)
|
||||
|
||||
type CipherSuite uint16
|
||||
|
||||
func (cs CipherSuite) String() string {
|
||||
if v, ok := cipherSuites[uint16(cs)]; ok {
|
||||
return v
|
||||
}
|
||||
return strconv.Itoa(int(cs))
|
||||
}
|
||||
|
||||
type Version uint16
|
||||
|
||||
func (ver Version) String() string {
|
||||
if v, ok := versions[uint16(ver)]; ok {
|
||||
return v
|
||||
}
|
||||
return strconv.Itoa(int(ver))
|
||||
}
|
||||
|
||||
// LoadDefaultConfig loads the certificate from cert & key files and optional CA file.
|
||||
func LoadDefaultConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
|
||||
pool, err := loadCA(caFile)
|
||||
if err != nil {
|
||||
logger.Default().Debugf("load default CA(%s): %v", caFile, err)
|
||||
}
|
||||
if pool != nil {
|
||||
cfg.ClientCAs = pool
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// LoadServerConfig loads the certificate from cert & key files and client CA file.
|
||||
func LoadServerConfig(config *config.TLSConfig) (*tls.Config, error) {
|
||||
if config.CertFile == "" && config.KeyFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
|
||||
pool, err := loadCA(config.CAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pool != nil {
|
||||
cfg.ClientCAs = pool
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
SetTLSOptions(cfg, config.Options)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// LoadClientConfig loads the certificate from cert & key files and CA file.
|
||||
func LoadClientConfig(config *config.TLSConfig) (*tls.Config, error) {
|
||||
var cfg *tls.Config
|
||||
|
||||
if config.CertFile == "" && config.KeyFile == "" {
|
||||
cfg = &tls.Config{}
|
||||
} else {
|
||||
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
rootCAs, err := loadCA(config.CAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.RootCAs = rootCAs
|
||||
cfg.ServerName = config.ServerName
|
||||
cfg.InsecureSkipVerify = !config.Secure
|
||||
|
||||
if config.Options != nil {
|
||||
SetTLSOptions(cfg, config.Options)
|
||||
}
|
||||
|
||||
// If the root ca is given, but skip verify, we verify the certificate manually.
|
||||
if cfg.RootCAs != nil && !config.Secure {
|
||||
cfg.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: cfg.RootCAs,
|
||||
CurrentTime: time.Now(),
|
||||
DNSName: "",
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
certs := state.PeerCertificates
|
||||
for i, cert := range certs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
_, err := certs[0].Verify(opts)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func SetTLSOptions(cfg *tls.Config, opts *config.TLSOptions) {
|
||||
if cfg == nil || opts == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch strings.ToLower(opts.MinVersion) {
|
||||
case strings.ToLower(VersionTLS10):
|
||||
cfg.MinVersion = tls.VersionTLS10
|
||||
case strings.ToLower(VersionTLS11):
|
||||
cfg.MinVersion = tls.VersionTLS11
|
||||
case strings.ToLower(VersionTLS12):
|
||||
cfg.MinVersion = tls.VersionTLS12
|
||||
case strings.ToLower(VersionTLS13):
|
||||
cfg.MinVersion = tls.VersionTLS13
|
||||
}
|
||||
switch strings.ToLower(opts.MaxVersion) {
|
||||
case strings.ToLower(VersionTLS10):
|
||||
cfg.MaxVersion = tls.VersionTLS10
|
||||
case strings.ToLower(VersionTLS11):
|
||||
cfg.MaxVersion = tls.VersionTLS11
|
||||
case strings.ToLower(VersionTLS12):
|
||||
cfg.MaxVersion = tls.VersionTLS12
|
||||
case strings.ToLower(VersionTLS13):
|
||||
cfg.MaxVersion = tls.VersionTLS13
|
||||
}
|
||||
for _, v := range opts.CipherSuites {
|
||||
switch strings.ToUpper(v) {
|
||||
case TLS_RSA_WITH_RC4_128_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_RC4_128_SHA)
|
||||
case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
|
||||
case TLS_RSA_WITH_AES_128_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
|
||||
case TLS_RSA_WITH_AES_256_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
|
||||
case TLS_RSA_WITH_AES_128_CBC_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
|
||||
case TLS_RSA_WITH_AES_128_GCM_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
|
||||
case TLS_RSA_WITH_AES_256_GCM_SHA384:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
|
||||
case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
|
||||
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
|
||||
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
|
||||
case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
|
||||
case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
|
||||
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
|
||||
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
|
||||
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
|
||||
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
|
||||
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
|
||||
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
|
||||
case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
|
||||
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
|
||||
case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||
case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
|
||||
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||
}
|
||||
}
|
||||
|
||||
cfg.NextProtos = opts.ALPN
|
||||
}
|
||||
|
||||
func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
||||
if caFile == "" {
|
||||
return
|
||||
}
|
||||
cp = x509.NewCertPool()
|
||||
data, err := os.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cp.AppendCertsFromPEM(data) {
|
||||
return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap a net.Conn into a client tls connection, performing any
|
||||
// additional verification as needed.
|
||||
//
|
||||
// As of go 1.3, crypto/tls only supports either doing no certificate
|
||||
// verification, or doing full verification including of the peer's
|
||||
// DNS name. For consul, we want to validate that the certificate is
|
||||
// signed by a known CA, but because consul doesn't use DNS names for
|
||||
// node names, we don't verify the certificate DNS names. Since go 1.3
|
||||
// no longer supports this mode of operation, we have to do it
|
||||
// manually.
|
||||
//
|
||||
// This code is taken from consul:
|
||||
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
|
||||
func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) (net.Conn, error) {
|
||||
var err error
|
||||
var tlsConn *tls.Conn
|
||||
|
||||
if timeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
tlsConn = tls.Client(conn, tlsConfig)
|
||||
|
||||
// Otherwise perform handshake, but don't verify the domain
|
||||
//
|
||||
// The following is lightly-modified from the doFullHandshake
|
||||
// method in https://golang.org/src/crypto/tls/handshake_client.go
|
||||
if err = tlsConn.Handshake(); err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We can do this in `tls.Config.VerifyConnection`, which effective for
|
||||
// other TLS protocols such as WebSocket. See `route.go:parseChainNode`
|
||||
/*
|
||||
// If crypto/tls is doing verification, there's no need to do our own.
|
||||
if tlsConfig.InsecureSkipVerify == false {
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
// Similarly if we use host's CA, we can do full handshake
|
||||
if tlsConfig.RootCAs == nil {
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: tlsConfig.RootCAs,
|
||||
CurrentTime: time.Now(),
|
||||
DNSName: "",
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
certs := tlsConn.ConnectionState().PeerCertificates
|
||||
for i, cert := range certs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
_, err = certs[0].Verify(opts)
|
||||
if err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
|
||||
return tlsConn, err
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCertNotFound = errors.New("certificate not found")
|
||||
)
|
||||
|
||||
type CertPool interface {
|
||||
Get(serverName string) (*x509.Certificate, error)
|
||||
Put(serverName string, cert *x509.Certificate)
|
||||
}
|
||||
|
||||
type memoryCertPool struct {
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewMemoryCertPool() CertPool {
|
||||
return &memoryCertPool{
|
||||
cache: cache.New(24*7*time.Hour, 1*time.Hour),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *memoryCertPool) Get(serverName string) (*x509.Certificate, error) {
|
||||
v, ok := p.cache.Get(serverName)
|
||||
if !ok {
|
||||
return nil, ErrCertNotFound
|
||||
}
|
||||
return v.(*x509.Certificate), nil
|
||||
}
|
||||
|
||||
func (p *memoryCertPool) Put(serverName string, cert *x509.Certificate) {
|
||||
p.cache.Set(serverName, cert, cache.DefaultExpiration)
|
||||
}
|
||||
|
||||
func GenerateCertificate(serverName string, validity time.Duration, caCert *x509.Certificate, caKey crypto.PrivateKey) (*x509.Certificate, error) {
|
||||
if host, _, _ := net.SplitHostPort(serverName); host != "" {
|
||||
serverName = host
|
||||
}
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().UnixNano() / 100000),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"GOST"},
|
||||
},
|
||||
NotBefore: time.Now().Add(-validity),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(serverName); ip != nil {
|
||||
tmpl.IPAddresses = []net.IP{ip}
|
||||
} else {
|
||||
tmpl.Subject.CommonName = serverName
|
||||
tmpl.DNSNames = []string{serverName}
|
||||
}
|
||||
|
||||
pk, ok := caKey.(privateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type")
|
||||
}
|
||||
|
||||
raw, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, pk.Public(), caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(raw)
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/crypto#PrivateKey
|
||||
type privateKey interface {
|
||||
Public() crypto.PublicKey
|
||||
Equal(x crypto.PrivateKey) bool
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/router"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
Net []net.IPNet
|
||||
// peer addr of point-to-point on MacOS
|
||||
Peer string
|
||||
MTU int
|
||||
Gateway net.IP
|
||||
Router router.Router
|
||||
DNS []net.IP
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// OpCode represents a WebSocket opcode.
|
||||
type OpCode int
|
||||
|
||||
// https://tools.ietf.org/html/rfc6455#section-11.8.
|
||||
const (
|
||||
OpContinuation OpCode = iota
|
||||
OpText
|
||||
OpBinary
|
||||
// 3 - 7 are reserved for further non-control frames.
|
||||
_
|
||||
_
|
||||
_
|
||||
_
|
||||
_
|
||||
OpClose
|
||||
OpPing
|
||||
OpPong
|
||||
// 11-16 are reserved for further control frames.
|
||||
)
|
||||
|
||||
// FrameHeader represents a WebSocket frame header.
|
||||
// See https://tools.ietf.org/html/rfc6455#section-5.2.
|
||||
type FrameHeader struct {
|
||||
Fin bool
|
||||
Rsv1 bool
|
||||
Rsv2 bool
|
||||
Rsv3 bool
|
||||
OpCode OpCode
|
||||
|
||||
PayloadLength int64
|
||||
|
||||
Masked bool
|
||||
MaskKey uint32
|
||||
}
|
||||
|
||||
// ReadFrom reads a header from the reader.
|
||||
// See https://tools.ietf.org/html/rfc6455#section-5.2.
|
||||
func (h *FrameHeader) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
var buf [8]byte
|
||||
|
||||
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
||||
nn, err := io.ReadFull(r, buf[:2])
|
||||
n += int64(n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b := buf[0]
|
||||
h.Fin = b&(1<<7) != 0
|
||||
h.Rsv1 = b&(1<<6) != 0
|
||||
h.Rsv2 = b&(1<<5) != 0
|
||||
h.Rsv3 = b&(1<<4) != 0
|
||||
|
||||
h.OpCode = OpCode(b & 0xf)
|
||||
|
||||
b = buf[1]
|
||||
h.Masked = b&(1<<7) != 0
|
||||
|
||||
payloadLength := b &^ (1 << 7)
|
||||
switch {
|
||||
case payloadLength < 126:
|
||||
h.PayloadLength = int64(payloadLength)
|
||||
case payloadLength == 126:
|
||||
nn, err = io.ReadFull(r, buf[:2])
|
||||
h.PayloadLength = int64(binary.BigEndian.Uint16(buf[:]))
|
||||
case payloadLength == 127:
|
||||
nn, err = io.ReadFull(r, buf[:])
|
||||
h.PayloadLength = int64(binary.BigEndian.Uint64(buf[:]))
|
||||
}
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h.PayloadLength < 0 {
|
||||
err = fmt.Errorf("received negative payload length: %v", h.PayloadLength)
|
||||
return
|
||||
}
|
||||
|
||||
if h.Masked {
|
||||
nn, err = io.ReadFull(r, buf[:4])
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.MaskKey = binary.LittleEndian.Uint32(buf[:])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h FrameHeader) Length() int {
|
||||
n := 2
|
||||
switch {
|
||||
case h.PayloadLength > math.MaxUint16:
|
||||
n += 8
|
||||
case h.PayloadLength > 125:
|
||||
n += 2
|
||||
}
|
||||
|
||||
if h.Masked {
|
||||
n += 4
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (h *FrameHeader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
var buf [14]byte
|
||||
pos := 0
|
||||
|
||||
var b byte
|
||||
if h.Fin {
|
||||
b |= 1 << 7
|
||||
}
|
||||
if h.Rsv1 {
|
||||
b |= 1 << 6
|
||||
}
|
||||
if h.Rsv2 {
|
||||
b |= 1 << 5
|
||||
}
|
||||
if h.Rsv3 {
|
||||
b |= 1 << 4
|
||||
}
|
||||
|
||||
b |= byte(h.OpCode)
|
||||
|
||||
buf[0] = b
|
||||
|
||||
lengthByte := byte(0)
|
||||
if h.Masked {
|
||||
lengthByte |= 1 << 7
|
||||
}
|
||||
|
||||
switch {
|
||||
case h.PayloadLength > math.MaxUint16:
|
||||
lengthByte |= 127
|
||||
case h.PayloadLength > 125:
|
||||
lengthByte |= 126
|
||||
case h.PayloadLength >= 0:
|
||||
lengthByte |= byte(h.PayloadLength)
|
||||
}
|
||||
buf[1] = lengthByte
|
||||
pos = 2
|
||||
|
||||
switch {
|
||||
case h.PayloadLength > math.MaxUint16:
|
||||
binary.BigEndian.PutUint64(buf[2:], uint64(h.PayloadLength))
|
||||
pos += 8
|
||||
case h.PayloadLength > 125:
|
||||
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadLength))
|
||||
pos += 2
|
||||
}
|
||||
|
||||
if h.Masked {
|
||||
binary.LittleEndian.PutUint32(buf[pos:], h.MaskKey)
|
||||
pos += 4
|
||||
}
|
||||
|
||||
nn, err := w.Write(buf[:pos])
|
||||
n = int64(nn)
|
||||
return
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
Header FrameHeader
|
||||
Data io.Reader
|
||||
}
|
||||
|
||||
func (fr *Frame) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
if n, err = fr.Header.ReadFrom(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fr.Data = io.LimitReader(r, fr.Header.PayloadLength)
|
||||
return
|
||||
}
|
||||
|
||||
func (fr *Frame) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = fr.Header.WriteTo(w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nn, err := io.Copy(w, fr.Data)
|
||||
n += nn
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WebsocketConn interface {
|
||||
net.Conn
|
||||
WriteMessage(int, []byte) error
|
||||
ReadMessage() (int, []byte, error)
|
||||
xnet.ClientAddr
|
||||
}
|
||||
|
||||
type websocketConn struct {
|
||||
*websocket.Conn
|
||||
rb []byte
|
||||
clientAddr net.Addr
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func Conn(conn *websocket.Conn) WebsocketConn {
|
||||
return &websocketConn{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func ConnWithClientAddr(conn *websocket.Conn, clientAddr net.Addr) WebsocketConn {
|
||||
return &websocketConn{
|
||||
Conn: conn,
|
||||
clientAddr: clientAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketConn) Read(b []byte) (n int, err error) {
|
||||
if len(c.rb) == 0 {
|
||||
_, c.rb, err = c.Conn.ReadMessage()
|
||||
}
|
||||
n = copy(b, c.rb)
|
||||
c.rb = c.rb[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func (c *websocketConn) Write(b []byte) (n int, err error) {
|
||||
err = c.WriteMessage(websocket.BinaryMessage, b)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *websocketConn) WriteMessage(messageType int, data []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
return c.Conn.WriteMessage(messageType, data)
|
||||
}
|
||||
|
||||
func (c *websocketConn) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *websocketConn) SetReadDeadline(t time.Time) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
return c.Conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *websocketConn) ClientAddr() net.Addr {
|
||||
return c.clientAddr
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package wt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
wt "github.com/quic-go/webtransport-go"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
session *wt.Session
|
||||
stream wt.Stream
|
||||
}
|
||||
|
||||
func Conn(session *wt.Session, stream wt.Stream) net.Conn {
|
||||
return &conn{
|
||||
session: session,
|
||||
stream: stream,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
return c.stream.Read(b)
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
return c.stream.Write(b)
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
return c.stream.Close()
|
||||
}
|
||||
|
||||
func (c *conn) LocalAddr() net.Addr {
|
||||
return c.session.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *conn) RemoteAddr() net.Addr {
|
||||
return c.session.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *conn) SetDeadline(t time.Time) error {
|
||||
return c.stream.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *conn) SetReadDeadline(t time.Time) error {
|
||||
return c.stream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *conn) SetWriteDeadline(t time.Time) error {
|
||||
return c.stream.SetWriteDeadline(t)
|
||||
}
|
||||
Reference in New Issue
Block a user