diff --git a/io/aes.go b/io/aes.go new file mode 100644 index 000000000..f416e3b09 --- /dev/null +++ b/io/aes.go @@ -0,0 +1,27 @@ +package io + +import ( + "crypto/aes" + "crypto/cipher" + "io" +) + +func NewAesDecryptReader(key []byte, iv []byte, reader io.Reader) (io.Reader, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aesMode := cipher.NewCBCDecrypter(aesBlock, iv) + return NewCryptionReader(aesMode, reader), nil +} + +func NewAesEncryptWriter(key []byte, iv []byte, writer io.Writer) (io.Writer, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aesMode := cipher.NewCBCEncrypter(aesBlock, iv) + return NewCryptionWriter(aesMode, writer), nil +} diff --git a/io/socks/socks.go b/io/socks/socks.go index 6dba6ca6c..9da41b1b7 100644 --- a/io/socks/socks.go +++ b/io/socks/socks.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "fmt" "io" + + v2net "github.com/v2ray/v2ray-core/net" ) const ( @@ -128,7 +130,7 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { // buffer[2] is a reserved field request.AddrType = buffer[3] switch request.AddrType { - case 0x01: + case AddrTypeIPv4: nBytes, err = reader.Read(request.IPv4[:]) if err != nil { return @@ -137,7 +139,7 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { err = fmt.Errorf("Unable to read IPv4 address.") return } - case 0x03: + case AddrTypeDomain: buffer = make([]byte, 257) nBytes, err = reader.Read(buffer) if err != nil { @@ -149,7 +151,7 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { return } request.Domain = string(buffer[1 : domainLength+1]) - case 0x04: + case AddrTypeIPv6: nBytes, err = reader.Read(request.IPv6[:]) if err != nil { return @@ -177,6 +179,19 @@ func ReadRequest(reader io.Reader) (request *Socks5Request, err error) { return } +func (request *Socks5Request) Destination() v2net.VAddress { + switch request.AddrType { + case AddrTypeIPv4: + return v2net.IPAddress(request.IPv4[:], request.Port) + case AddrTypeIPv6: + return v2net.IPAddress(request.IPv6[:], request.Port) + case AddrTypeDomain: + return v2net.DomainAddress(request.Domain, request.Port) + default: + panic("Unknown address type") + } +} + const ( ErrorSuccess = byte(0x00) ErrorGeneralFailure = byte(0x01) diff --git a/io/vmess/vmess.go b/io/vmess/vmess.go index cbfb6f6a2..b67f315e4 100644 --- a/io/vmess/vmess.go +++ b/io/vmess/vmess.go @@ -15,6 +15,7 @@ import ( "github.com/v2ray/v2ray-core" v2io "github.com/v2ray/v2ray-core/io" + v2net "github.com/v2ray/v2ray-core/net" ) const ( @@ -22,7 +23,7 @@ const ( addrTypeIPv6 = byte(0x03) addrTypeDomain = byte(0x02) - vMessVersion = byte(0x01) + Version = byte(0x01) ) var ( @@ -95,12 +96,25 @@ func (r *VMessRequest) targetAddressType() byte { return r[56] } +func (r *VMessRequest) Destination() v2net.VAddress { + switch r.targetAddressType() { + case addrTypeIPv4: + fallthrough + case addrTypeIPv6: + return v2net.IPAddress(r.targetAddressBytes(), r.Port()) + case addrTypeDomain: + return v2net.DomainAddress(r.TargetAddress(), r.Port()) + default: + panic("Unpexected address type") + } +} + func (r *VMessRequest) TargetAddress() string { switch r.targetAddressType() { case addrTypeIPv4: - return string(net.IPv4(r[57], r[58], r[59], r[60])) + return net.IP(r[57:61]).String() case addrTypeIPv6: - return string(net.IP(r[57:73])) + return net.IP(r[57:73]).String() case addrTypeDomain: domainLength := int(r[57]) return string(r[58 : 58+domainLength]) @@ -326,4 +340,10 @@ func (w *VMessRequestWriter) Write(writer io.Writer, request *VMessRequest) erro return nil } -type VMessOutput [4]byte +type VMessResponse [4]byte + +func NewVMessResponse(request *VMessRequest) *VMessResponse { + response := new(VMessResponse) + copy(response[:], request.ResponseHeader()) + return response +} diff --git a/net/freedom/freedom.go b/net/freedom/freedom.go index 6fa29b5ab..aaca4ad6e 100644 --- a/net/freedom/freedom.go +++ b/net/freedom/freedom.go @@ -5,24 +5,25 @@ import ( "net" "github.com/v2ray/v2ray-core" + v2net "github.com/v2ray/v2ray-core/net" ) type VFreeConnection struct { - network string - address string + vPoint *core.VPoint + dest v2net.VAddress } -func NewVFreeConnection(network string, address string) *VFreeConnection { +func NewVFreeConnection(vp *core.VPoint, dest v2net.VAddress) *VFreeConnection { conn := new(VFreeConnection) - conn.network = network - conn.address = address + conn.vPoint = vp + conn.dest = dest return conn } func (vconn *VFreeConnection) Start(vRay core.OutboundVRay) error { input := vRay.OutboundInput() output := vRay.OutboundOutput() - conn, err := net.Dial(vconn.network, vconn.address) + conn, err := net.Dial("tcp", vconn.dest.String()) if err != nil { return err } diff --git a/net/socks/socks.go b/net/socks/socks.go index b4560d14a..e150e724d 100644 --- a/net/socks/socks.go +++ b/net/socks/socks.go @@ -2,8 +2,10 @@ package socks import ( "errors" + "io" "net" + "github.com/v2ray/v2ray-core" socksio "github.com/v2ray/v2ray-core/io/socks" ) @@ -15,6 +17,13 @@ var ( // SocksServer is a SOCKS 5 proxy server type SocksServer struct { accepting bool + vPoint *core.VPoint +} + +func NewSocksServer(vp *core.VPoint) *SocksServer { + server := new(SocksServer) + server.vPoint = vp + return server } func (server *SocksServer) Listen(port uint8) error { @@ -65,7 +74,43 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { return ErrorCommandNotSupported } - // TODO: establish connection with VNext + ray := server.vPoint.NewInboundConnectionAccepted(request.Destination()) + input := ray.InboundInput() + output := ray.InboundOutput() + finish := make(chan bool, 2) + + go server.dumpInput(connection, input, finish) + go server.dumpOutput(connection, output, finish) + server.waitForFinish(finish) return nil } + +func (server *SocksServer) dumpInput(conn net.Conn, input chan<- []byte, finish chan<- bool) { + for { + buffer := make([]byte, 256) + nBytes, err := conn.Read(buffer) + if err == io.EOF { + finish <- true + break + } + input <- buffer[:nBytes] + } +} + +func (server *SocksServer) dumpOutput(conn net.Conn, output <-chan []byte, finish chan<- bool) { + for { + buffer, open := <-output + if !open { + finish <- true + break + } + conn.Write(buffer) + } +} + +func (server *SocksServer) waitForFinish(finish <-chan bool) { + for i := 0; i < 2; i++ { + <-finish + } +} diff --git a/net/socks/socksfactory.go b/net/socks/socksfactory.go new file mode 100644 index 000000000..6a6b9d5f5 --- /dev/null +++ b/net/socks/socksfactory.go @@ -0,0 +1,12 @@ +package socks + +import ( + "github.com/v2ray/v2ray-core" +) + +type SocksServerFactory struct { +} + +func (factory *SocksServerFactory) Create(vp *core.VPoint) *SocksServer { + return NewSocksServer(vp) +} diff --git a/net/vdest.go b/net/vdest.go new file mode 100644 index 000000000..1dda4f6f3 --- /dev/null +++ b/net/vdest.go @@ -0,0 +1,60 @@ +package core + +import ( + "net" + "strconv" +) + +const ( + AddrTypeIP = byte(0x01) + AddrTypeDomain = byte(0x03) +) + +type VAddress struct { + Type byte + IP net.IP + Domain string + Port uint16 +} + +func IPAddress(ip []byte, port uint16) VAddress { + // TODO: check IP length + return VAddress{ + AddrTypeIP, + net.IP(ip), + "", + port} +} + +func DomainAddress(domain string, port uint16) VAddress { + return VAddress{ + AddrTypeDomain, + nil, + domain, + port} +} + +func (addr VAddress) IsIPv4() bool { + return addr.Type == AddrTypeIP && len(addr.IP) == net.IPv4len +} + +func (addr VAddress) IsIPv6() bool { + return addr.Type == AddrTypeIP && len(addr.IP) == net.IPv6len +} + +func (addr VAddress) IsDomain() bool { + return addr.Type == AddrTypeDomain +} + +func (addr VAddress) String() string { + var host string + switch addr.Type { + case AddrTypeIP: + host = addr.IP.String() + case AddrTypeDomain: + host = addr.Domain + default: + panic("Unknown Address Type " + strconv.Itoa(int(addr.Type))) + } + return host + ":" + strconv.Itoa(int(addr.Port)) +} diff --git a/net/vmess/vmess.go b/net/vmess/vmess.go index 691e3c835..60f17caa1 100644 --- a/net/vmess/vmess.go +++ b/net/vmess/vmess.go @@ -1,17 +1,5 @@ -package vemss +package vmess -import ( - "net" +const ( + BufferSize = 256 ) - -type VMessHandler struct { -} - -func (*VMessHandler) Listen(port uint8) error { - _, err := net.Listen("tcp", ":"+string(port)) - if err != nil { - return err - } - - return nil -} diff --git a/net/vmess/vmessin.go b/net/vmess/vmessin.go new file mode 100644 index 000000000..8ad01ceab --- /dev/null +++ b/net/vmess/vmessin.go @@ -0,0 +1,119 @@ +package vmess + +import ( + "crypto/md5" + "io" + "net" + + "github.com/v2ray/v2ray-core" + v2io "github.com/v2ray/v2ray-core/io" + vmessio "github.com/v2ray/v2ray-core/io/vmess" +) + +type VMessInboundHandler struct { + vPoint *core.VPoint + accepting bool +} + +func NewVMessInboundHandler(vp *core.VPoint) *VMessInboundHandler { + handler := new(VMessInboundHandler) + handler.vPoint = vp + return handler +} + +func (handler *VMessInboundHandler) Listen(port uint8) error { + listener, err := net.Listen("tcp", ":"+string(port)) + if err != nil { + return err + } + handler.accepting = true + go handler.AcceptConnections(listener) + + return nil +} + +func (handler *VMessInboundHandler) AcceptConnections(listener net.Listener) error { + for handler.accepting { + connection, err := listener.Accept() + if err != nil { + return err + } + go handler.HandleConnection(connection) + } + return nil +} + +func (handler *VMessInboundHandler) HandleConnection(connection net.Conn) error { + defer connection.Close() + reader := vmessio.NewVMessRequestReader(handler.vPoint.UserSet) + + request, err := reader.Read(connection) + if err != nil { + return err + } + + response := vmessio.NewVMessResponse(request) + connection.Write(response[:]) + + requestKey := request.RequestKey() + requestIV := request.RequestIV() + responseKey := md5.Sum(requestKey) + responseIV := md5.Sum(requestIV) + + requestReader, err := v2io.NewAesDecryptReader(requestKey, requestIV, connection) + if err != nil { + return err + } + + responseWriter, err := v2io.NewAesEncryptWriter(responseKey[:], responseIV[:], connection) + if err != nil { + return err + } + + ray := handler.vPoint.NewInboundConnectionAccepted(request.Destination()) + input := ray.InboundInput() + output := ray.InboundOutput() + finish := make(chan bool, 2) + + go handler.dumpInput(requestReader, input, finish) + go handler.dumpOutput(responseWriter, output, finish) + handler.waitForFinish(finish) + + return nil +} + +func (handler *VMessInboundHandler) dumpInput(reader io.Reader, input chan<- []byte, finish chan<- bool) { + for { + buffer := make([]byte, BufferSize) + nBytes, err := reader.Read(buffer) + if err == io.EOF { + finish <- true + break + } + input <- buffer[:nBytes] + } +} + +func (handler *VMessInboundHandler) dumpOutput(writer io.Writer, output <-chan []byte, finish chan<- bool) { + for { + buffer, open := <-output + if !open { + finish <- true + break + } + writer.Write(buffer) + } +} + +func (handler *VMessInboundHandler) waitForFinish(finish <-chan bool) { + for i := 0; i < 2; i++ { + <-finish + } +} + +type VMessInboundHandlerFactory struct { +} + +func (factory *VMessInboundHandlerFactory) Create(vp *core.VPoint) *VMessInboundHandler { + return NewVMessInboundHandler(vp) +} diff --git a/net/vmess/vmessout.go b/net/vmess/vmessout.go new file mode 100644 index 000000000..51742902a --- /dev/null +++ b/net/vmess/vmessout.go @@ -0,0 +1,133 @@ +package vmess + +import ( + "crypto/md5" + "crypto/rand" + "io" + mrand "math/rand" + "net" + + "github.com/v2ray/v2ray-core" + v2io "github.com/v2ray/v2ray-core/io" + vmessio "github.com/v2ray/v2ray-core/io/vmess" + v2net "github.com/v2ray/v2ray-core/net" +) + +type VMessOutboundHandler struct { + vPoint *core.VPoint + dest v2net.VAddress +} + +func NewVMessOutboundHandler(vp *core.VPoint, dest v2net.VAddress) *VMessOutboundHandler { + handler := new(VMessOutboundHandler) + handler.vPoint = vp + handler.dest = dest + return handler +} + +func (handler *VMessOutboundHandler) pickVNext() (v2net.VAddress, core.VUser) { + vNextLen := len(handler.vPoint.Config.VNextList) + if vNextLen == 0 { + panic("Zero vNext is configured.") + } + vNextIndex := mrand.Intn(vNextLen) + vNext := handler.vPoint.Config.VNextList[vNextIndex] + vNextUserLen := len(vNext.Users) + if vNextUserLen == 0 { + panic("Zero User account.") + } + vNextUserIndex := mrand.Intn(vNextUserLen) + vNextUser := vNext.Users[vNextUserIndex] + return vNext.Address, vNextUser +} + +func (handler *VMessOutboundHandler) Start(ray core.OutboundVRay) error { + vNextAddress, vNextUser := handler.pickVNext() + + request := new(vmessio.VMessRequest) + request.SetVersion(vmessio.Version) + copy(request.UserHash(), vNextUser.Id.Hash([]byte("ASK"))) + rand.Read(request.RequestIV()) + rand.Read(request.RequestKey()) + rand.Read(request.ResponseHeader()) + request.SetCommand(byte(0x01)) + request.SetPort(handler.dest.Port) + + address := handler.dest + switch { + case address.IsIPv4(): + request.SetIPv4(address.IP) + case address.IsIPv6(): + request.SetIPv6(address.IP) + case address.IsDomain(): + request.SetDomain(address.Domain) + } + + conn, err := net.Dial("tcp", vNextAddress.String()) + if err != nil { + return err + } + defer conn.Close() + + requestWriter := vmessio.NewVMessRequestWriter(handler.vPoint.UserSet) + requestWriter.Write(conn, request) + + requestKey := request.RequestKey() + requestIV := request.RequestIV() + responseKey := md5.Sum(requestKey) + responseIV := md5.Sum(requestIV) + + encryptRequestWriter, err := v2io.NewAesEncryptWriter(requestKey, requestIV, conn) + if err != nil { + return err + } + responseReader, err := v2io.NewAesDecryptReader(responseKey[:], responseIV[:], conn) + if err != nil { + return err + } + + input := ray.OutboundInput() + output := ray.OutboundOutput() + finish := make(chan bool, 2) + + go handler.dumpInput(encryptRequestWriter, input, finish) + go handler.dumpOutput(responseReader, output, finish) + handler.waitForFinish(finish) + return nil +} + +func (handler *VMessOutboundHandler) dumpOutput(reader io.Reader, output chan<- []byte, finish chan<- bool) { + for { + buffer := make([]byte, BufferSize) + nBytes, err := reader.Read(buffer) + if err == io.EOF { + finish <- true + break + } + output <- buffer[:nBytes] + } +} + +func (handler *VMessOutboundHandler) dumpInput(writer io.Writer, input <-chan []byte, finish chan<- bool) { + for { + buffer, open := <-input + if !open { + finish <- true + break + } + writer.Write(buffer) + } +} + +func (handler *VMessOutboundHandler) waitForFinish(finish <-chan bool) { + for i := 0; i < 2; i++ { + <-finish + } +} + +type VMessOutboundHandlerFactory struct { +} + +func (factory *VMessOutboundHandlerFactory) Create(vp *core.VPoint, destination v2net.VAddress) *VMessOutboundHandler { + return NewVMessOutboundHandler(vp, destination) +} diff --git a/vconfig.go b/vconfig.go index d80f184aa..6d57bf8cc 100644 --- a/vconfig.go +++ b/vconfig.go @@ -1,5 +1,9 @@ package core +import ( + v2net "github.com/v2ray/v2ray-core/net" +) + // VUser is the user account that is used for connection to a VPoint type VUser struct { Id VID // The ID of this VUser. @@ -7,8 +11,8 @@ type VUser struct { // VNext is the next VPoint server in the connection chain. type VNext struct { - ServerAddress string // Address of VNext server, in the form of "IP:Port" - User []VUser // User accounts for accessing VNext. + Address v2net.VAddress // Address of VNext server + Users []VUser // User accounts for accessing VNext. } // VConfig is the config for VPoint server. diff --git a/vpoint.go b/vpoint.go index 44d917a78..ea855e175 100644 --- a/vpoint.go +++ b/vpoint.go @@ -2,11 +2,14 @@ package core import ( "fmt" + + v2net "github.com/v2ray/v2ray-core/net" ) // VPoint is an single server in V2Ray system. type VPoint struct { - config VConfig + Config VConfig + UserSet *VUserSet ichFactory InboundConnectionHandlerFactory ochFactory OutboundConnectionHandlerFactory } @@ -15,7 +18,13 @@ type VPoint struct { // The server is not started at this point. func NewVPoint(config *VConfig) (*VPoint, error) { var vpoint = new(VPoint) - vpoint.config = *config + vpoint.Config = *config + vpoint.UserSet = NewVUserSet() + + for _, user := range vpoint.Config.AllowedClients { + vpoint.UserSet.AddUser(user) + } + return vpoint, nil } @@ -28,23 +37,46 @@ type InboundConnectionHandler interface { } type OutboundConnectionHandlerFactory interface { - Create(vPoint *VPoint) (OutboundConnectionHandler, error) + Create(vPoint *VPoint, dest v2net.VAddress) (OutboundConnectionHandler, error) } type OutboundConnectionHandler interface { - Start(vray *OutboundVRay) error + Start(vray OutboundVRay) error } // Start starts the VPoint server, and return any error during the process. // In the case of any errors, the state of the server is unpredicatable. func (vp *VPoint) Start() error { - if vp.config.Port <= 0 { - return fmt.Errorf("Invalid port %d", vp.config.Port) + if vp.Config.Port <= 0 { + return fmt.Errorf("Invalid port %d", vp.Config.Port) } inboundConnectionHandler, err := vp.ichFactory.Create(vp) if err != nil { return err } - err = inboundConnectionHandler.Listen(vp.config.Port) + err = inboundConnectionHandler.Listen(vp.Config.Port) return nil } + +func (vp *VPoint) NewInboundConnectionAccepted(destination v2net.VAddress) InboundVRay { + /* + vNextLen := len(vp.Config.VNextList) + if vNextLen > 0 { + vNextIndex := rand.Intn(vNextLen) + vNext := vp.Config.VNextList[vNextIndex] + vNextUser := dest.User + vNextUserLen := len(vNext.Users) + if vNextUserLen > 0 { + vNextUserIndex = rand.Intn(vNextUserLen) + vNextUser = vNext.Users[vNextUserIndex] + } + newDest := VDestination{"tcp", vNext.ServerAddress, vNextUser} + dest = newDest + }*/ + + ray := NewVRay() + // TODO: handle error + och, _ := vp.ochFactory.Create(vp, destination) + _ = och.Start(ray) + return ray +} diff --git a/vray.go b/vray.go index 8ad7a1ea2..9c4599cfc 100644 --- a/vray.go +++ b/vray.go @@ -5,11 +5,8 @@ type VRay struct { Output chan []byte } -func NewVRay() *VRay { - ray := new(VRay) - ray.Input = make(chan []byte, 128) - ray.Output = make(chan []byte, 128) - return ray +func NewVRay() VRay { + return VRay{make(chan []byte, 128), make(chan []byte, 128)} } type OutboundVRay interface { @@ -19,21 +16,21 @@ type OutboundVRay interface { type InboundVRay interface { InboundInput() chan<- []byte - OutboundOutput() <-chan []byte + InboundOutput() <-chan []byte } -func (ray *VRay) OutboundInput() <-chan []byte { +func (ray VRay) OutboundInput() <-chan []byte { return ray.Input } -func (ray *VRay) OutboundOutput() chan<- []byte { +func (ray VRay) OutboundOutput() chan<- []byte { return ray.Output } -func (ray *VRay) InboundInput() chan<- []byte { +func (ray VRay) InboundInput() chan<- []byte { return ray.Input } -func (ray *VRay) InboundOutput() <-chan []byte { +func (ray VRay) InboundOutput() <-chan []byte { return ray.Output }