initial commit
This commit is contained in:
148
internal/util/grpc/proto/gost.pb.go
Normal file
148
internal/util/grpc/proto/gost.pb.go
Normal file
@ -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
|
||||
}
|
10
internal/util/grpc/proto/gost.proto
Normal file
10
internal/util/grpc/proto/gost.proto
Normal file
@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/go-gost/gost/v3/pkg/common/util/grpc/proto";
|
||||
|
||||
message Chunk {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
service GostTunel {
|
||||
rpc Tunnel (stream Chunk) returns (stream Chunk);
|
||||
}
|
133
internal/util/grpc/proto/gost_grpc.pb.go
Normal file
133
internal/util/grpc/proto/gost_grpc.pb.go
Normal file
@ -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",
|
||||
}
|
3
internal/util/grpc/proto/protoc.sh
Executable file
3
internal/util/grpc/proto/protoc.sh
Executable file
@ -0,0 +1,3 @@
|
||||
protoc --go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||
gost.proto
|
132
internal/util/http2/conn.go
Normal file
132
internal/util/http2/conn.go
Normal file
@ -0,0 +1,132 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// a dummy HTTP2 client conn used by HTTP2 client connector
|
||||
type ClientConn struct {
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
client *http.Client
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func NewClientConn(localAddr, remoteAddr net.Addr, client *http.Client, onClose func()) net.Conn {
|
||||
return &ClientConn{
|
||||
localAddr: localAddr,
|
||||
remoteAddr: remoteAddr,
|
||||
client: client,
|
||||
onClose: onClose,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientConn) Client() *http.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func (c *ClientConn) Close() error {
|
||||
if c.onClose != nil {
|
||||
c.onClose()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientConn) 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 *ClientConn) 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 *ClientConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *ClientConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *ClientConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *ClientConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *ClientConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
// a dummy HTTP2 server conn used by HTTP2 handler
|
||||
type ServerConn struct {
|
||||
r *http.Request
|
||||
w http.ResponseWriter
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
func NewServerConn(w http.ResponseWriter, r *http.Request, localAddr, remoteAddr net.Addr) *ServerConn {
|
||||
return &ServerConn{
|
||||
r: r,
|
||||
w: w,
|
||||
localAddr: localAddr,
|
||||
remoteAddr: remoteAddr,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ServerConn) Done() <-chan struct{} {
|
||||
return c.closed
|
||||
}
|
||||
|
||||
func (c *ServerConn) Request() *http.Request {
|
||||
return c.r
|
||||
}
|
||||
|
||||
func (c *ServerConn) Writer() http.ResponseWriter {
|
||||
return c.w
|
||||
}
|
||||
|
||||
func (c *ServerConn) Read(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "read", Net: "http2", Source: nil, Addr: nil, Err: errors.New("read not supported")}
|
||||
}
|
||||
|
||||
func (c *ServerConn) Write(b []byte) (n int, err error) {
|
||||
return 0, &net.OpError{Op: "write", Net: "http2", Source: nil, Addr: nil, Err: errors.New("write not supported")}
|
||||
}
|
||||
|
||||
func (c *ServerConn) Close() error {
|
||||
select {
|
||||
case <-c.closed:
|
||||
default:
|
||||
close(c.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServerConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *ServerConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *ServerConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *ServerConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *ServerConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
285
internal/util/icmp/conn.go
Normal file
285
internal/util/icmp/conn.go
Normal file
@ -0,0 +1,285 @@
|
||||
package icmp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/common/bufpool"
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
readBufferSize = 1500
|
||||
writeBufferSize = 1500
|
||||
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 {
|
||||
net.PacketConn
|
||||
id int
|
||||
seq uint32
|
||||
}
|
||||
|
||||
func ClientConn(conn net.PacketConn, id int) net.PacketConn {
|
||||
return &clientConn{
|
||||
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
|
||||
}
|
||||
|
||||
m, err := icmp.ParseMessage(1, (*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 {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
// 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}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
wb, err := m.Marshal(nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(wb, addr)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
net.PacketConn
|
||||
seqs [65535]uint32
|
||||
}
|
||||
|
||||
func ServerConn(conn net.PacketConn) net.PacketConn {
|
||||
return &serverConn{
|
||||
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
|
||||
}
|
||||
|
||||
m, err := icmp.ParseMessage(1, (*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.ICMPTypeEcho || echo.ID <= 0 {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
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}
|
||||
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,
|
||||
}
|
||||
wb, err := m.Marshal(nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(wb, addr)
|
||||
n = len(b)
|
||||
return
|
||||
}
|
115
internal/util/kcp/config.go
Normal file
115
internal/util/kcp/config.go
Normal file
@ -0,0 +1,115 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
|
||||
"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,
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
34
internal/util/kcp/kcp.go
Normal file
34
internal/util/kcp/kcp.go
Normal file
@ -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
|
||||
}
|
85
internal/util/mux/mux.go
Normal file
85
internal/util/mux/mux.go
Normal file
@ -0,0 +1,85 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
smux "github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
conn net.Conn
|
||||
session *smux.Session
|
||||
}
|
||||
|
||||
func ClientSession(conn net.Conn) (*Session, error) {
|
||||
s, err := smux.Client(conn, smux.DefaultConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{
|
||||
conn: conn,
|
||||
session: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ServerSession(conn net.Conn) (*Session, error) {
|
||||
s, err := smux.Server(conn, smux.DefaultConfig())
|
||||
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()
|
||||
}
|
105
internal/util/pht/client.go
Normal file
105
internal/util/pht/client.go
Normal file
@ -0,0 +1,105 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/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.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
c.Logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
resp, err := c.Client.Do(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.Logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.Logger.Debug(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
|
||||
}
|
176
internal/util/pht/conn.go
Normal file
176
internal/util/pht/conn.go
Normal file
@ -0,0 +1,176 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
)
|
||||
|
||||
type clientConn struct {
|
||||
client *http.Client
|
||||
pushURL string
|
||||
pullURL string
|
||||
buf []byte
|
||||
rxc chan []byte
|
||||
closed chan struct{}
|
||||
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 = net.ErrClosed
|
||||
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
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(base64.StdEncoding.EncodeToString(b))
|
||||
buf.WriteByte('\n')
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, c.pushURL, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
c.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = errors.New(resp.Status)
|
||||
return
|
||||
}
|
||||
|
||||
n = len(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *clientConn) readLoop() {
|
||||
defer c.Close()
|
||||
|
||||
for {
|
||||
err := func() error {
|
||||
r, err := http.NewRequest(http.MethodGet, c.pullURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
c.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
c.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
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.logger.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *clientConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *clientConn) Close() error {
|
||||
select {
|
||||
case <-c.closed:
|
||||
default:
|
||||
close(c.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
344
internal/util/pht/server.go
Normal file
344
internal/util/pht/server.go
Normal file
@ -0,0 +1,344 @@
|
||||
package pht
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/common/bufpool"
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBacklog = 128
|
||||
)
|
||||
|
||||
type serverOptions struct {
|
||||
authorizePath string
|
||||
pushPath string
|
||||
pullPath string
|
||||
backlog int
|
||||
tlsEnabled bool
|
||||
tlsConfig *tls.Config
|
||||
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 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
http3Server: &http3.Server{
|
||||
Server: &http.Server{
|
||||
Addr: addr,
|
||||
TLSConfig: options.tlsConfig,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
},
|
||||
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 {
|
||||
addr, err := net.ResolveUDPAddr("udp", s.http3Server.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.addr = addr
|
||||
return s.http3Server.ListenAndServe()
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", 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.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
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.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
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 {
|
||||
s.options.logger.Error(err)
|
||||
conn.Close()
|
||||
s.conns.Delete(cid)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
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.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(r, false)
|
||||
s.options.logger.Debug(string(dump))
|
||||
}
|
||||
|
||||
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(4096)
|
||||
defer bufpool.Put(b)
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
n, err := conn.Read(*b)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
s.options.logger.Error(err)
|
||||
s.conns.Delete(cid)
|
||||
conn.Close()
|
||||
} else {
|
||||
(*b)[0] = '\n'
|
||||
w.Write((*b)[:1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
90
internal/util/quic/conn.go
Normal file
90
internal/util/quic/conn.go
Normal file
@ -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)
|
||||
}
|
172
internal/util/relay/conn.go
Normal file
172
internal/util/relay/conn.go
Normal file
@ -0,0 +1,172 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/gost/v3/pkg/common/bufpool"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
96
internal/util/ss/conn.go
Normal file
96
internal/util/ss/conn.go
Normal file
@ -0,0 +1,96 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/gost/v3/pkg/common/bufpool"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultBufferSize = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
_ net.PacketConn = (*UDPConn)(nil)
|
||||
_ net.Conn = (*UDPConn)(nil)
|
||||
)
|
||||
|
||||
type UDPConn struct {
|
||||
net.PacketConn
|
||||
raddr net.Addr
|
||||
taddr net.Addr
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr, bufferSize int) *UDPConn {
|
||||
return &UDPConn{
|
||||
PacketConn: c,
|
||||
raddr: remoteAddr,
|
||||
taddr: targetAddr,
|
||||
bufferSize: bufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
func UDPServerConn(c net.PacketConn, remoteAddr net.Addr, bufferSize int) *UDPConn {
|
||||
return &UDPConn{
|
||||
PacketConn: c,
|
||||
raddr: remoteAddr,
|
||||
bufferSize: bufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
rbuf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(rbuf)
|
||||
|
||||
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) {
|
||||
wbuf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(wbuf)
|
||||
|
||||
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
|
||||
}
|
60
internal/util/ss/ss.go
Normal file
60
internal/util/ss/ss.go
Normal file
@ -0,0 +1,60 @@
|
||||
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 {
|
||||
return &shadowConn{
|
||||
Conn: conn,
|
||||
wbuf: bytes.NewBuffer(header),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
48
internal/util/ssh/conn.go
Normal file
48
internal/util/ssh/conn.go
Normal file
@ -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(conn net.Conn, client *ssh.Client) net.Conn {
|
||||
return &ClientConn{
|
||||
Conn: conn,
|
||||
client: 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()
|
||||
}
|
75
internal/util/ssh/ssh.go
Normal file
75
internal/util/ssh/ssh.go
Normal file
@ -0,0 +1,75 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/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 au.Authenticate(conn.User(), string(password)) {
|
||||
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 := ioutil.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
|
||||
}
|
118
internal/util/sshd/conn.go
Normal file
118
internal/util/sshd/conn.go
Normal file
@ -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()
|
||||
}
|
9
internal/util/tap/config.go
Normal file
9
internal/util/tap/config.go
Normal file
@ -0,0 +1,9 @@
|
||||
package tap
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
Net string
|
||||
MTU int
|
||||
Routes []string
|
||||
Gateway string
|
||||
}
|
61
internal/util/tap/conn.go
Normal file
61
internal/util/tap/conn.go
Normal file
@ -0,0 +1,61 @@
|
||||
package tap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/songgao/water"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
config *Config
|
||||
ifce *water.Interface
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
}
|
||||
|
||||
func NewConn(config *Config, ifce *water.Interface, laddr, raddr net.Addr) *Conn {
|
||||
return &Conn{
|
||||
config: config,
|
||||
ifce: ifce,
|
||||
laddr: laddr,
|
||||
raddr: raddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Config() *Config {
|
||||
return c.config
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
return c.ifce.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.ifce.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Close() (err error) {
|
||||
return c.ifce.Close()
|
||||
}
|
||||
|
||||
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: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
19
internal/util/tun/config.go
Normal file
19
internal/util/tun/config.go
Normal file
@ -0,0 +1,19 @@
|
||||
package tun
|
||||
|
||||
import "net"
|
||||
|
||||
// Route is an IP routing entry
|
||||
type Route struct {
|
||||
Net net.IPNet
|
||||
Gateway net.IP
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
Net string
|
||||
// peer addr of point-to-point on MacOS
|
||||
Peer string
|
||||
MTU int
|
||||
Gateway string
|
||||
Routes []Route
|
||||
}
|
61
internal/util/tun/conn.go
Normal file
61
internal/util/tun/conn.go
Normal file
@ -0,0 +1,61 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/songgao/water"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
config *Config
|
||||
ifce *water.Interface
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
}
|
||||
|
||||
func NewConn(config *Config, ifce *water.Interface, laddr, raddr net.Addr) *Conn {
|
||||
return &Conn{
|
||||
config: config,
|
||||
ifce: ifce,
|
||||
laddr: laddr,
|
||||
raddr: raddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Config() *Config {
|
||||
return c.config
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
return c.ifce.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.ifce.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Close() (err error) {
|
||||
return c.ifce.Close()
|
||||
}
|
||||
|
||||
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: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
56
internal/util/ws/ws.go
Normal file
56
internal/util/ws/ws.go
Normal file
@ -0,0 +1,56 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WebsocketConn interface {
|
||||
net.Conn
|
||||
WriteMessage(int, []byte) error
|
||||
ReadMessage() (int, []byte, error)
|
||||
}
|
||||
|
||||
type websocketConn struct {
|
||||
*websocket.Conn
|
||||
rb []byte
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func Conn(conn *websocket.Conn) WebsocketConn {
|
||||
return &websocketConn{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
Reference in New Issue
Block a user