From c5aa4acb3598f361ce56dfa03a229b8f026edb5b Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Sun, 30 Apr 2017 23:37:30 +0200 Subject: [PATCH] prototype of vpndialer --- app/vpndialer/config.pb.go | 52 ++++++ app/vpndialer/config.proto | 11 ++ app/vpndialer/unix/errors.generated.go | 7 + app/vpndialer/unix/unix.go | 218 +++++++++++++++++++++++++ app/vpndialer/vpndialer.go | 1 + common/crypto/auth.go | 7 + common/serial/bytes.go | 7 + transport/internet/system_dialer.go | 5 +- 8 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 app/vpndialer/config.pb.go create mode 100644 app/vpndialer/config.proto create mode 100644 app/vpndialer/unix/errors.generated.go create mode 100644 app/vpndialer/unix/unix.go create mode 100644 app/vpndialer/vpndialer.go diff --git a/app/vpndialer/config.pb.go b/app/vpndialer/config.pb.go new file mode 100644 index 00000000..f2cc5e9e --- /dev/null +++ b/app/vpndialer/config.pb.go @@ -0,0 +1,52 @@ +package vpndialer + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Config struct { + Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Config) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func init() { + proto.RegisterType((*Config)(nil), "v2ray.core.app.vpndialer.Config") +} + +func init() { proto.RegisterFile("v2ray.com/core/app/vpndialer/config.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 150 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2c, 0x33, 0x2a, 0x4a, + 0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x2f, + 0x2b, 0xc8, 0x4b, 0xc9, 0x4c, 0xcc, 0x49, 0x2d, 0xd2, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x80, 0x29, 0x2d, 0x4a, 0xd5, 0x4b, 0x2c, 0x28, 0xd0, + 0x83, 0x2b, 0x53, 0x52, 0xe2, 0x62, 0x73, 0x06, 0xab, 0x14, 0x92, 0xe0, 0x62, 0x4f, 0x4c, 0x49, + 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, 0x9d, 0xdc, 0xb8, + 0x64, 0x92, 0xf3, 0x73, 0xf5, 0x70, 0x99, 0x11, 0xc0, 0x18, 0xc5, 0x09, 0xe7, 0xac, 0x62, 0x92, + 0x08, 0x33, 0x0a, 0x4a, 0xac, 0xd4, 0x73, 0x06, 0xa9, 0x73, 0x2c, 0x28, 0xd0, 0x0b, 0x2b, 0xc8, + 0x73, 0x01, 0x4b, 0x25, 0xb1, 0x81, 0x1d, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x97, 0xfc, + 0x09, 0x70, 0xb9, 0x00, 0x00, 0x00, +} diff --git a/app/vpndialer/config.proto b/app/vpndialer/config.proto new file mode 100644 index 00000000..b5b801fb --- /dev/null +++ b/app/vpndialer/config.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package v2ray.core.app.vpndialer; +option csharp_namespace = "V2Ray.Core.App.VpnDialer"; +option go_package = "vpndialer"; +option java_package = "com.v2ray.core.app.vpndialer"; +option java_multiple_files = true; + +message Config { + string address = 1; +} diff --git a/app/vpndialer/unix/errors.generated.go b/app/vpndialer/unix/errors.generated.go new file mode 100644 index 00000000..d5873901 --- /dev/null +++ b/app/vpndialer/unix/errors.generated.go @@ -0,0 +1,7 @@ +package unix + +import "v2ray.com/core/common/errors" + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).Path("App", "VPNDialer", "Unix") +} diff --git a/app/vpndialer/unix/unix.go b/app/vpndialer/unix/unix.go new file mode 100644 index 00000000..e8f072cc --- /dev/null +++ b/app/vpndialer/unix/unix.go @@ -0,0 +1,218 @@ +package unix + +import ( + "context" + "net" + "os" + "sync" + + "golang.org/x/sys/unix" + "v2ray.com/core/app/vpndialer" + "v2ray.com/core/common" + v2net "v2ray.com/core/common/net" + "v2ray.com/core/common/serial" + "v2ray.com/core/transport/internet" +) + +//go:generate go run $GOPATH/src/v2ray.com/core/tools/generrorgen/main.go -pkg unix -path App,VPNDialer,Unix + +type status int + +const ( + statusNew status = iota + statusOK + statusFail +) + +type fdStatus struct { + status status + fd int + callback chan<- error +} + +type protector struct { + sync.Mutex + address string + conn *net.UnixConn + status chan fdStatus +} + +func readFrom(conn *net.UnixConn, schan chan<- fdStatus) { + var payload [6]byte + for { + _, err := conn.Read(payload[:]) + if err != nil { + break + } + fd := serial.BytesToInt(payload[1:5]) + s := status(payload[5]) + schan <- fdStatus{ + fd: fd, + status: s, + } + } +} + +func (m *protector) dial() (*net.UnixConn, error) { + m.Lock() + defer m.Unlock() + + if m.conn != nil { + return m.conn, nil + } + + conn, err := net.DialUnix("unix", nil, &net.UnixAddr{ + Name: m.address, + Net: "unix", + }) + if err != nil { + return nil, err + } + m.conn = conn + m.status = make(chan fdStatus, 32) + go readFrom(conn, m.status) + go m.monitor(conn) + return conn, nil +} + +func (m *protector) close() { + m.Lock() + defer m.Unlock() + if m.conn == nil { + return + } + m.conn.Close() + m.conn = nil +} + +func (m *protector) monitor(c *net.UnixConn) { + pendingFd := make(map[int]chan<- error, 32) + for s := range m.status { + switch s.status { + case statusNew: + pendingFd[s.fd] = s.callback + case statusOK: + if c, f := pendingFd[s.fd]; f { + close(c) + delete(pendingFd, s.fd) + } + case statusFail: + if c, f := pendingFd[s.fd]; f { + c <- newError("failed to protect fd") + close(c) + delete(pendingFd, s.fd) + } + } + } +} + +func (m *protector) protect(fd int) error { + conn, err := m.dial() + if err != nil { + return err + } + + var payload [6]byte + serial.IntToBytes(fd, payload[1:1]) + payload[5] = byte(statusNew) + if _, err := conn.Write(payload[:]); err != nil { + return err + } + + wait := make(chan error) + m.status <- fdStatus{ + status: statusNew, + fd: fd, + callback: wait, + } + return <-wait +} + +type App struct { + protector *protector + dialer *Dialer +} + +func NewApp(ctx context.Context, config *vpndialer.Config) (*App, error) { + a := &App{ + dialer: &Dialer{}, + protector: &protector{ + address: config.Address, + }, + } + a.dialer.protect = a.protector.protect + return a, nil +} + +func (*App) Interface() interface{} { + return (*App)(nil) +} + +func (a *App) Start() error { + internet.UseAlternativeSystemDialer(a.dialer) + return nil +} + +func (a *App) Close() { + internet.UseAlternativeSystemDialer(nil) +} + +type Dialer struct { + protect func(fd int) error +} + +func socket(dest v2net.Destination) (int, error) { + switch dest.Network { + case v2net.Network_TCP: + return unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP) + case v2net.Network_UDP: + return unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + default: + return 0, newError("unknown network ", dest.Network) + } +} + +func getIP(addr v2net.Address) (net.IP, error) { + if addr.Family().Either(v2net.AddressFamilyIPv4, v2net.AddressFamilyIPv6) { + return addr.IP(), nil + } + ips, err := net.LookupIP(addr.Domain()) + if err != nil { + return nil, err + } + return ips[0], nil +} + +func (d *Dialer) Dial(ctx context.Context, source v2net.Address, dest v2net.Destination) (net.Conn, error) { + fd, err := socket(dest) + if err != nil { + return nil, err + } + if err := d.protect(fd); err != nil { + return nil, err + } + + ip, err := getIP(dest.Address) + if err != nil { + return nil, err + } + + addr := &unix.SockaddrInet6{ + Port: int(dest.Port), + ZoneId: 0, + } + copy(addr.Addr[:], ip.To16()) + + if err := unix.Connect(fd, addr); err != nil { + return nil, err + } + + file := os.NewFile(uintptr(fd), "Socket") + return net.FileConn(file) +} + +func init() { + common.Must(common.RegisterConfig((*vpndialer.Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewApp(ctx, config.(*vpndialer.Config)) + })) +} diff --git a/app/vpndialer/vpndialer.go b/app/vpndialer/vpndialer.go new file mode 100644 index 00000000..a4a3ea92 --- /dev/null +++ b/app/vpndialer/vpndialer.go @@ -0,0 +1 @@ +package vpndialer diff --git a/common/crypto/auth.go b/common/crypto/auth.go index 9e0ebfe1..2c0b3b85 100644 --- a/common/crypto/auth.go +++ b/common/crypto/auth.go @@ -60,6 +60,13 @@ func (v *AEADAuthenticator) Seal(dst, plainText []byte) ([]byte, error) { return v.AEAD.Seal(dst, iv, plainText, additionalData), nil } +type StreamMode int + +const ( + ModeStream StreamMode = iota + ModePacket +) + type AuthenticationReader struct { auth Authenticator buffer *buf.Buffer diff --git a/common/serial/bytes.go b/common/serial/bytes.go index dcee4213..002b6957 100644 --- a/common/serial/bytes.go +++ b/common/serial/bytes.go @@ -20,6 +20,13 @@ func BytesToUint32(value []byte) uint32 { uint32(value[3]) } +func BytesToInt(value []byte) int { + return int(value[0])<<24 | + int(value[1])<<16 | + int(value[2])<<8 | + int(value[3]) +} + // BytesToInt64 deserializes a byte array to an int64 in big endian order. The byte array must have at least 8 elements. func BytesToInt64(value []byte) int64 { return int64(value[0])<<56 | diff --git a/transport/internet/system_dialer.go b/transport/internet/system_dialer.go index 68fe3e52..324280b4 100644 --- a/transport/internet/system_dialer.go +++ b/transport/internet/system_dialer.go @@ -63,9 +63,12 @@ func (v *SimpleSystemDialer) Dial(ctx context.Context, src v2net.Address, dest v // UseAlternativeSystemDialer replaces the current system dialer with a given one. // Caller must ensure there is no race condition. func UseAlternativeSystemDialer(dialer SystemDialer) { + if dialer == nil { + effectiveSystemDialer = &DefaultSystemDialer{} + } effectiveSystemDialer = dialer } func init() { - effectiveSystemDialer = &DefaultSystemDialer{} + UseAlternativeSystemDialer(nil) }