mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 19:56:51 +08:00
[test]core 1.8
This commit is contained in:
parent
d3521e6b3a
commit
f5d2e7cd04
369
adapter/outbound/vless.go
Normal file
369
adapter/outbound/vless.go
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vless struct {
|
||||||
|
*Base
|
||||||
|
client *vless.Client
|
||||||
|
option *VlessOption
|
||||||
|
|
||||||
|
// for gun mux
|
||||||
|
gunTLSConfig *tls.Config
|
||||||
|
gunConfig *gun.Config
|
||||||
|
transport *http2.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
type VlessOption struct {
|
||||||
|
BasicOption
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Server string `proxy:"server"`
|
||||||
|
Port int `proxy:"port"`
|
||||||
|
UUID string `proxy:"uuid"`
|
||||||
|
Flow string `proxy:"flow,omitempty"`
|
||||||
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
|
Network string `proxy:"network,omitempty"`
|
||||||
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
switch v.option.Network {
|
||||||
|
case "ws":
|
||||||
|
if v.option.WSOpts.Path == "" {
|
||||||
|
v.option.WSOpts.Path = v.option.WSPath
|
||||||
|
}
|
||||||
|
if len(v.option.WSOpts.Headers) == 0 {
|
||||||
|
v.option.WSOpts.Headers = v.option.WSHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Path: v.option.WSOpts.Path,
|
||||||
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.option.WSOpts.Headers) != 0 {
|
||||||
|
header := http.Header{}
|
||||||
|
for key, value := range v.option.WSOpts.Headers {
|
||||||
|
header.Add(key, value)
|
||||||
|
}
|
||||||
|
wsOpts.Headers = header
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.TLS {
|
||||||
|
wsOpts.TLS = true
|
||||||
|
wsOpts.TLSConfig = &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
|
NextProtos: []string{"http/1.1"},
|
||||||
|
}
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
|
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||||
|
wsOpts.TLSConfig.ServerName = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||||
|
case "http":
|
||||||
|
// readability first, so just copy default TLS logic
|
||||||
|
c, err = v.streamTLSOrXTLSConn(c, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
httpOpts := &vmess.HTTPConfig{
|
||||||
|
Host: host,
|
||||||
|
Method: v.option.HTTPOpts.Method,
|
||||||
|
Path: v.option.HTTPOpts.Path,
|
||||||
|
Headers: v.option.HTTPOpts.Headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||||
|
case "h2":
|
||||||
|
c, err = v.streamTLSOrXTLSConn(c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h2Opts := &vmess.H2Config{
|
||||||
|
Hosts: v.option.HTTP2Opts.Host,
|
||||||
|
Path: v.option.HTTP2Opts.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
|
case "grpc":
|
||||||
|
if v.isXTLSEnabled() {
|
||||||
|
c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
|
} else {
|
||||||
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// handle TLS And XTLS
|
||||||
|
c, err = v.streamTLSOrXTLSConn(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
|
||||||
|
if v.isXTLSEnabled() {
|
||||||
|
xtlsOpts := vless.XTLSConfig{
|
||||||
|
Host: host,
|
||||||
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isH2 {
|
||||||
|
xtlsOpts.NextProtos = []string{"h2"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
xtlsOpts.Host = v.option.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
return vless.StreamXTLSConn(conn, &xtlsOpts)
|
||||||
|
|
||||||
|
} else if v.option.TLS {
|
||||||
|
tlsOpts := vmess.TLSConfig{
|
||||||
|
Host: host,
|
||||||
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isH2 {
|
||||||
|
tlsOpts.NextProtos = []string{"h2"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName != "" {
|
||||||
|
tlsOpts.Host = v.option.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
return vmess.StreamTLSConn(conn, &tlsOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vless) isXTLSEnabled() bool {
|
||||||
|
return v.client.Addons != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
// gun transport
|
||||||
|
if v.transport != nil && len(opts) == 0 {
|
||||||
|
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
return NewConn(c, v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
|
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
|
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
|
||||||
|
if !metadata.Resolved() {
|
||||||
|
ip, err := resolver.ResolveIP(metadata.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("can't resolve ip")
|
||||||
|
}
|
||||||
|
metadata.DstIP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
var c net.Conn
|
||||||
|
// gun transport
|
||||||
|
if v.transport != nil && len(opts) == 0 {
|
||||||
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
|
||||||
|
} else {
|
||||||
|
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
|
c, err = v.StreamConn(c, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
|
||||||
|
var addrType byte
|
||||||
|
var addr []byte
|
||||||
|
switch metadata.AddrType {
|
||||||
|
case C.AtypIPv4:
|
||||||
|
addrType = byte(vless.AtypIPv4)
|
||||||
|
addr = make([]byte, net.IPv4len)
|
||||||
|
copy(addr[:], metadata.DstIP.To4())
|
||||||
|
case C.AtypIPv6:
|
||||||
|
addrType = byte(vless.AtypIPv6)
|
||||||
|
addr = make([]byte, net.IPv6len)
|
||||||
|
copy(addr[:], metadata.DstIP.To16())
|
||||||
|
case C.AtypDomainName:
|
||||||
|
addrType = byte(vless.AtypDomainName)
|
||||||
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
|
addr[0] = byte(len(metadata.Host))
|
||||||
|
copy(addr[1:], []byte(metadata.Host))
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
|
return &vless.DstAddr{
|
||||||
|
UDP: metadata.NetWork == C.UDP,
|
||||||
|
AddrType: addrType,
|
||||||
|
Addr: addr,
|
||||||
|
Port: uint(port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type vlessPacketConn struct {
|
||||||
|
net.Conn
|
||||||
|
rAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
return uc.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, err := uc.Conn.Read(b)
|
||||||
|
return n, uc.rAddr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVless(option VlessOption) (*Vless, error) {
|
||||||
|
if !option.TLS {
|
||||||
|
return nil, fmt.Errorf("TLS must be true with vless")
|
||||||
|
}
|
||||||
|
|
||||||
|
var addons *vless.Addons
|
||||||
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||||
|
option.Flow = option.Flow[:16]
|
||||||
|
switch option.Flow {
|
||||||
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
|
addons = &vless.Addons{
|
||||||
|
Flow: option.Flow,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported vless flow type: %s", option.Flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := &Vless{
|
||||||
|
Base: &Base{
|
||||||
|
name: option.Name,
|
||||||
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
|
tp: C.Vless,
|
||||||
|
udp: option.UDP,
|
||||||
|
iface: option.Interface,
|
||||||
|
},
|
||||||
|
client: client,
|
||||||
|
option: &option,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch option.Network {
|
||||||
|
case "h2":
|
||||||
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
|
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||||
|
}
|
||||||
|
case "grpc":
|
||||||
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gunConfig := &gun.Config{
|
||||||
|
ServiceName: v.option.GrpcOpts.GrpcServiceName,
|
||||||
|
Host: v.option.ServerName,
|
||||||
|
}
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
|
ServerName: v.option.ServerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.option.ServerName == "" {
|
||||||
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
tlsConfig.ServerName = host
|
||||||
|
gunConfig.Host = host
|
||||||
|
}
|
||||||
|
|
||||||
|
v.gunTLSConfig = tlsConfig
|
||||||
|
v.gunConfig = gunConfig
|
||||||
|
if v.isXTLSEnabled() {
|
||||||
|
v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
|
||||||
|
} else {
|
||||||
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
158
transport/vless/config.pb.go
Normal file
158
transport/vless/config.pb.go
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc v3.17.3
|
||||||
|
// source: transport/vless/config.proto
|
||||||
|
|
||||||
|
package vless
|
||||||
|
|
||||||
|
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 Addons struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Flow string `protobuf:"bytes,1,opt,name=Flow,proto3" json:"Flow,omitempty"`
|
||||||
|
Seed []byte `protobuf:"bytes,2,opt,name=Seed,proto3" json:"Seed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Addons) Reset() {
|
||||||
|
*x = Addons{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_transport_vless_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Addons) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Addons) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Addons) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_transport_vless_config_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 Addons.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Addons) Descriptor() ([]byte, []int) {
|
||||||
|
return file_transport_vless_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Addons) GetFlow() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Flow
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Addons) GetSeed() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Seed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_transport_vless_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_transport_vless_config_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x6c, 0x65, 0x73,
|
||||||
|
0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15,
|
||||||
|
0x63, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||||
|
0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x30, 0x0a, 0x06, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x73, 0x12,
|
||||||
|
0x12, 0x0a, 0x04, 0x46, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x46,
|
||||||
|
0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x53, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
|
0x0c, 0x52, 0x04, 0x53, 0x65, 0x65, 0x64, 0x42, 0x61, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x63,
|
||||||
|
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x76,
|
||||||
|
0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||||
|
0x6f, 0x6d, 0x2f, 0x44, 0x72, 0x65, 0x61, 0x6d, 0x61, 0x63, 0x72, 0x6f, 0x2f, 0x63, 0x6c, 0x61,
|
||||||
|
0x73, 0x68, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x76, 0x6c, 0x65,
|
||||||
|
0x73, 0x73, 0xaa, 0x02, 0x15, 0x43, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
||||||
|
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_transport_vless_config_proto_rawDescOnce sync.Once
|
||||||
|
file_transport_vless_config_proto_rawDescData = file_transport_vless_config_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_transport_vless_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_transport_vless_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_transport_vless_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_vless_config_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_transport_vless_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_transport_vless_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_transport_vless_config_proto_goTypes = []interface{}{
|
||||||
|
(*Addons)(nil), // 0: clash.transport.vless.Addons
|
||||||
|
}
|
||||||
|
var file_transport_vless_config_proto_depIdxs = []int32{
|
||||||
|
0, // [0:0] is the sub-list for method output_type
|
||||||
|
0, // [0:0] 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_transport_vless_config_proto_init() }
|
||||||
|
func file_transport_vless_config_proto_init() {
|
||||||
|
if File_transport_vless_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_transport_vless_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Addons); 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_transport_vless_config_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_transport_vless_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_transport_vless_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_transport_vless_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_transport_vless_config_proto = out.File
|
||||||
|
file_transport_vless_config_proto_rawDesc = nil
|
||||||
|
file_transport_vless_config_proto_goTypes = nil
|
||||||
|
file_transport_vless_config_proto_depIdxs = nil
|
||||||
|
}
|
12
transport/vless/config.proto
Normal file
12
transport/vless/config.proto
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package clash.transport.vless;
|
||||||
|
option csharp_namespace = "Clash.Transport.Vless";
|
||||||
|
option go_package = "github.com/Dreamacro/clash/transport/vless";
|
||||||
|
option java_package = "com.clash.transport.vless";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Addons {
|
||||||
|
string Flow = 1;
|
||||||
|
bytes Seed = 2;
|
||||||
|
}
|
128
transport/vless/conn.go
Normal file
128
transport/vless/conn.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
dst *DstAddr
|
||||||
|
id *uuid.UUID
|
||||||
|
addons *Addons
|
||||||
|
received bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) Read(b []byte) (int, error) {
|
||||||
|
if vc.received {
|
||||||
|
return vc.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vc.recvResponse(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
vc.received = true
|
||||||
|
return vc.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) sendRequest() error {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
buf.WriteByte(Version) // protocol version
|
||||||
|
buf.Write(vc.id.Bytes()) // 16 bytes of uuid
|
||||||
|
|
||||||
|
if vc.addons != nil {
|
||||||
|
bytes, err := proto.Marshal(vc.addons)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteByte(byte(len(bytes)))
|
||||||
|
buf.Write(bytes)
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(0) // addon data length. 0 means no addon data
|
||||||
|
}
|
||||||
|
|
||||||
|
// command
|
||||||
|
if vc.dst.UDP {
|
||||||
|
buf.WriteByte(CommandUDP)
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(CommandTCP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port AddrType Addr
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port))
|
||||||
|
buf.WriteByte(vc.dst.AddrType)
|
||||||
|
buf.Write(vc.dst.Addr)
|
||||||
|
|
||||||
|
_, err := vc.Conn.Write(buf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *Conn) recvResponse() error {
|
||||||
|
var err error
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
_, err = io.ReadFull(vc.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != Version {
|
||||||
|
return errors.New("unexpected response version")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.ReadFull(vc.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
length := int64(buf[0])
|
||||||
|
if length != 0 { // addon data length > 0
|
||||||
|
io.CopyN(io.Discard, vc.Conn, length) // just discard
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConn return a Conn instance
|
||||||
|
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
||||||
|
c := &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
id: client.uuid,
|
||||||
|
dst: dst,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dst.UDP && client.Addons != nil {
|
||||||
|
switch client.Addons.Flow {
|
||||||
|
case XRO, XRD, XRS:
|
||||||
|
if xtlsConn, ok := conn.(*xtls.Conn); ok {
|
||||||
|
xtlsConn.RPRX = true
|
||||||
|
xtlsConn.SHOW = client.XTLSShow
|
||||||
|
xtlsConn.MARK = "XTLS"
|
||||||
|
if client.Addons.Flow == XRS {
|
||||||
|
client.Addons.Flow = XRD
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Addons.Flow == XRD {
|
||||||
|
xtlsConn.DirectMode = true
|
||||||
|
}
|
||||||
|
c.addons = client.Addons
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.sendRequest(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
71
transport/vless/vless.go
Normal file
71
transport/vless/vless.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XRO = "xtls-rprx-origin"
|
||||||
|
XRD = "xtls-rprx-direct"
|
||||||
|
XRS = "xtls-rprx-splice"
|
||||||
|
|
||||||
|
Version byte = 0 // protocol version. preview version is 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command types
|
||||||
|
const (
|
||||||
|
CommandTCP byte = 1
|
||||||
|
CommandUDP byte = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Addr types
|
||||||
|
const (
|
||||||
|
AtypIPv4 byte = 1
|
||||||
|
AtypDomainName byte = 2
|
||||||
|
AtypIPv6 byte = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// DstAddr store destination address
|
||||||
|
type DstAddr struct {
|
||||||
|
UDP bool
|
||||||
|
AddrType byte
|
||||||
|
Addr []byte
|
||||||
|
Port uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config of vless
|
||||||
|
type Config struct {
|
||||||
|
UUID string
|
||||||
|
AlterID uint16
|
||||||
|
Security string
|
||||||
|
Port string
|
||||||
|
HostName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is vless connection generator
|
||||||
|
type Client struct {
|
||||||
|
uuid *uuid.UUID
|
||||||
|
Addons *Addons
|
||||||
|
XTLSShow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamConn return a Conn with net.Conn and DstAddr
|
||||||
|
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||||
|
return newConn(conn, c, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient return Client instance
|
||||||
|
func NewClient(uuidStr string, addons *Addons, xtlsShow bool) (*Client, error) {
|
||||||
|
uid, err := uuid.FromString(uuidStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
uuid: &uid,
|
||||||
|
Addons: addons,
|
||||||
|
XTLSShow: xtlsShow,
|
||||||
|
}, nil
|
||||||
|
}
|
25
transport/vless/xtls.go
Normal file
25
transport/vless/xtls.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
xtls "github.com/xtls/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XTLSConfig struct {
|
||||||
|
Host string
|
||||||
|
SkipCertVerify bool
|
||||||
|
NextProtos []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) {
|
||||||
|
xtlsConfig := &xtls.Config{
|
||||||
|
ServerName: cfg.Host,
|
||||||
|
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
|
}
|
||||||
|
|
||||||
|
xtlsConn := xtls.Client(conn, xtlsConfig)
|
||||||
|
err := xtlsConn.Handshake()
|
||||||
|
return xtlsConn, err
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user