Browse Source

support using specific address

v2ray 9 years ago
parent
commit
a4d76dc394
48 changed files with 412 additions and 301 deletions
  1. 1 1
      app/dns/server_test.go
  2. 4 0
      common/net/network.go
  3. 1 1
      common/net/testing/port.go
  4. 3 3
      common/protocol/user_validator.go
  5. 1 1
      proxy/blackhole/blackhole.go
  6. 12 16
      proxy/dokodemo/dokodemo.go
  7. 8 8
      proxy/dokodemo/dokodemo_test.go
  8. 6 4
      proxy/freedom/freedom.go
  9. 3 3
      proxy/freedom/freedom_test.go
  10. 11 13
      proxy/http/server.go
  11. 3 3
      proxy/http/server_test.go
  12. 3 2
      proxy/internal/creator.go
  13. 7 6
      proxy/internal/handler_cache.go
  14. 2 2
      proxy/proxy.go
  15. 5 4
      proxy/repo/repo.go
  16. 2 1
      proxy/shadowsocks/protocol_test.go
  17. 13 15
      proxy/shadowsocks/server.go
  18. 16 14
      proxy/socks/server.go
  19. 32 28
      proxy/socks/server_test.go
  20. 4 6
      proxy/testing/mocks/inboundhandler.go
  21. 1 1
      proxy/testing/mocks/outboundhandler.go
  22. 9 11
      proxy/vmess/inbound/inbound.go
  23. 4 2
      proxy/vmess/outbound/outbound.go
  24. 19 15
      proxy/vmess/vmess_test.go
  25. 3 2
      release/config/vpoint_socks_vmess.json
  26. 1 1
      release/config/vpoint_vmess_freedom.json
  27. 15 7
      shell/point/config.go
  28. 59 23
      shell/point/config_json.go
  29. 2 2
      shell/point/config_json_test.go
  30. 2 2
      shell/point/inbound_detour_always.go
  31. 48 42
      shell/point/inbound_detour_dynamic.go
  32. 10 6
      shell/point/point.go
  33. 2 2
      testing/scenarios/data/test_1_client.json
  34. 1 1
      testing/scenarios/data/test_1_server.json
  35. 1 1
      testing/scenarios/data/test_2_client.json
  36. 1 1
      testing/scenarios/data/test_2_server.json
  37. 1 1
      testing/scenarios/data/test_3_client.json
  38. 1 1
      testing/scenarios/data/test_3_server.json
  39. 1 1
      testing/scenarios/data/test_4_client.json
  40. 1 1
      testing/scenarios/data/test_4_server.json
  41. 1 1
      testing/scenarios/data/test_5_client.json
  42. 1 1
      testing/scenarios/data/test_5_server.json
  43. 1 1
      testing/scenarios/data/test_6_server.json
  44. 30 14
      testing/scenarios/server_env.go
  45. 1 1
      transport/hub/connection_cache.go
  46. 28 27
      transport/hub/dialer.go
  47. 30 1
      transport/hub/dialer_test.go
  48. 1 1
      transport/hub/udp_server.go

+ 1 - 1
app/dns/server_test.go

@@ -20,7 +20,7 @@ func TestDnsAdd(t *testing.T) {
 	space := app.NewSpace()
 
 	outboundHandlerManager := proxyman.NewDefaultOutboundHandlerManager()
-	outboundHandlerManager.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space))
+	outboundHandlerManager.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space, v2net.AnyIP))
 	space.BindApp(proxyman.APP_ID_OUTBOUND_MANAGER, outboundHandlerManager)
 	space.BindApp(dispatcher.APP_ID, dispatchers.NewDefaultDispatcher(space))
 

+ 4 - 0
common/net/network.go

@@ -22,6 +22,10 @@ func (this Network) AsList() *NetworkList {
 	return &list
 }
 
+func (this Network) String() string {
+	return string(this)
+}
+
 // NetworkList is a list of Networks.
 type NetworkList []Network
 

+ 1 - 1
common/net/testing/port.go

@@ -6,5 +6,5 @@ import (
 )
 
 func PickPort() v2net.Port {
-	return v2net.Port(30000 + dice.Roll(5000))
+	return v2net.Port(30000 + dice.Roll(20000))
 }

+ 3 - 3
common/protocol/user_validator.go

@@ -50,7 +50,7 @@ func NewTimedUserValidator(hasher IDHash) UserValidator {
 		hasher:     hasher,
 		cancel:     signal.NewCloseSignal(),
 	}
-	go tus.updateUserHash(time.Tick(updateIntervalSec*time.Second), tus.cancel)
+	go tus.updateUserHash(updateIntervalSec*time.Second, tus.cancel)
 	return tus
 }
 
@@ -88,11 +88,11 @@ func (this *TimedUserValidator) generateNewHashes(nowSec Timestamp, idx int, ent
 	}
 }
 
-func (this *TimedUserValidator) updateUserHash(tick <-chan time.Time, cancel *signal.CancelSignal) {
+func (this *TimedUserValidator) updateUserHash(interval time.Duration, cancel *signal.CancelSignal) {
 L:
 	for {
 		select {
-		case now := <-tick:
+		case now := <-time.After(interval):
 			nowSec := Timestamp(now.Unix() + cacheDurationSec)
 			for _, entry := range this.ids {
 				this.generateNewHashes(nowSec, entry.userIdx, entry)

+ 1 - 1
proxy/blackhole/blackhole.go

@@ -31,7 +31,7 @@ func (this *BlackHole) Dispatch(destination v2net.Destination, payload *alloc.Bu
 
 func init() {
 	internal.MustRegisterOutboundHandlerCreator("blackhole",
-		func(space app.Space, config interface{}) (proxy.OutboundHandler, error) {
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
 			return NewBlackHole(), nil
 		})
 }

+ 12 - 16
proxy/dokodemo/dokodemo.go

@@ -29,11 +29,13 @@ type DokodemoDoor struct {
 	listeningAddress v2net.Address
 }
 
-func NewDokodemoDoor(config *Config, space app.Space) *DokodemoDoor {
+func NewDokodemoDoor(config *Config, space app.Space, listen v2net.Address, port v2net.Port) *DokodemoDoor {
 	d := &DokodemoDoor{
-		config:  config,
-		address: config.Address,
-		port:    config.Port,
+		config:           config,
+		address:          config.Address,
+		port:             config.Port,
+		listeningAddress: listen,
+		listeningPort:    port,
 	}
 	space.InitializeApplication(func() error {
 		if !space.HasApp(dispatcher.APP_ID) {
@@ -66,26 +68,20 @@ func (this *DokodemoDoor) Close() {
 	}
 }
 
-func (this *DokodemoDoor) Listen(address v2net.Address, port v2net.Port) error {
+func (this *DokodemoDoor) Start() error {
 	if this.accepting {
-		if this.listeningPort == port && this.listeningAddress.Equals(address) {
-			return nil
-		} else {
-			return proxy.ErrorAlreadyListening
-		}
+		return nil
 	}
-	this.listeningPort = port
-	this.listeningAddress = address
 	this.accepting = true
 
 	if this.config.Network.HasNetwork(v2net.TCPNetwork) {
-		err := this.ListenTCP(address, port)
+		err := this.ListenTCP(this.listeningAddress, this.listeningPort)
 		if err != nil {
 			return err
 		}
 	}
 	if this.config.Network.HasNetwork(v2net.UDPNetwork) {
-		err := this.ListenUDP(address, port)
+		err := this.ListenUDP(this.listeningAddress, this.listeningPort)
 		if err != nil {
 			return err
 		}
@@ -168,7 +164,7 @@ func (this *DokodemoDoor) HandleTCPConnection(conn *hub.Connection) {
 
 func init() {
 	internal.MustRegisterInboundHandlerCreator("dokodemo-door",
-		func(space app.Space, rawConfig interface{}) (proxy.InboundHandler, error) {
-			return NewDokodemoDoor(rawConfig.(*Config), space), nil
+		func(space app.Space, rawConfig interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
+			return NewDokodemoDoor(rawConfig.(*Config), space, listen, port), nil
 		})
 }

+ 8 - 8
proxy/dokodemo/dokodemo_test.go

@@ -37,23 +37,23 @@ func TestDokodemoTCP(t *testing.T) {
 	space := app.NewSpace()
 	space.BindApp(dispatcher.APP_ID, dispatchers.NewDefaultDispatcher(space))
 	ohm := proxyman.NewDefaultOutboundHandlerManager()
-	ohm.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space))
+	ohm.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space, v2net.LocalHostIP))
 	space.BindApp(proxyman.APP_ID_OUTBOUND_MANAGER, ohm)
 
 	data2Send := "Data to be sent to remote."
 
+	port := v2nettesting.PickPort()
 	dokodemo := NewDokodemoDoor(&Config{
 		Address: v2net.LocalHostIP,
 		Port:    tcpServer.Port,
 		Network: v2net.TCPNetwork.AsList(),
 		Timeout: 600,
-	}, space)
+	}, space, v2net.LocalHostIP, port)
 	defer dokodemo.Close()
 
 	assert.Error(space.Initialize()).IsNil()
 
-	port := v2nettesting.PickPort()
-	err = dokodemo.Listen(v2net.LocalHostIP, port)
+	err = dokodemo.Start()
 	assert.Error(err).IsNil()
 	assert.Port(port).Equals(dokodemo.Port())
 
@@ -95,23 +95,23 @@ func TestDokodemoUDP(t *testing.T) {
 	space := app.NewSpace()
 	space.BindApp(dispatcher.APP_ID, dispatchers.NewDefaultDispatcher(space))
 	ohm := proxyman.NewDefaultOutboundHandlerManager()
-	ohm.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space))
+	ohm.SetDefaultHandler(freedom.NewFreedomConnection(&freedom.Config{}, space, v2net.AnyIP))
 	space.BindApp(proxyman.APP_ID_OUTBOUND_MANAGER, ohm)
 
 	data2Send := "Data to be sent to remote."
 
+	port := v2nettesting.PickPort()
 	dokodemo := NewDokodemoDoor(&Config{
 		Address: v2net.LocalHostIP,
 		Port:    udpServer.Port,
 		Network: v2net.UDPNetwork.AsList(),
 		Timeout: 600,
-	}, space)
+	}, space, v2net.LocalHostIP, port)
 	defer dokodemo.Close()
 
 	assert.Error(space.Initialize()).IsNil()
 
-	port := v2nettesting.PickPort()
-	err = dokodemo.Listen(v2net.LocalHostIP, port)
+	err = dokodemo.Start()
 	assert.Error(err).IsNil()
 	assert.Port(port).Equals(dokodemo.Port())
 

+ 6 - 4
proxy/freedom/freedom.go

@@ -23,12 +23,14 @@ type FreedomConnection struct {
 	domainStrategy DomainStrategy
 	timeout        uint32
 	dns            dns.Server
+	sendThrough    v2net.Address
 }
 
-func NewFreedomConnection(config *Config, space app.Space) *FreedomConnection {
+func NewFreedomConnection(config *Config, space app.Space, sendThrough v2net.Address) *FreedomConnection {
 	f := &FreedomConnection{
 		domainStrategy: config.DomainStrategy,
 		timeout:        config.Timeout,
+		sendThrough:    sendThrough,
 	}
 	space.InitializeApplication(func() error {
 		if config.DomainStrategy == DomainStrategyUseIP {
@@ -78,7 +80,7 @@ func (this *FreedomConnection) Dispatch(destination v2net.Destination, payload *
 		destination = this.ResolveIP(destination)
 	}
 	err := retry.Timed(5, 100).On(func() error {
-		rawConn, err := hub.DialWithoutCache(destination)
+		rawConn, err := hub.DialWithoutCache(this.sendThrough, destination)
 		if err != nil {
 			return err
 		}
@@ -138,7 +140,7 @@ func (this *FreedomConnection) Dispatch(destination v2net.Destination, payload *
 
 func init() {
 	internal.MustRegisterOutboundHandlerCreator("freedom",
-		func(space app.Space, config interface{}) (proxy.OutboundHandler, error) {
-			return NewFreedomConnection(config.(*Config), space), nil
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
+			return NewFreedomConnection(config.(*Config), space, sendThrough), nil
 		})
 }

+ 3 - 3
proxy/freedom/freedom_test.go

@@ -37,7 +37,7 @@ func TestSinglePacket(t *testing.T) {
 	assert.Error(err).IsNil()
 
 	space := app.NewSpace()
-	freedom := NewFreedomConnection(&Config{}, space)
+	freedom := NewFreedomConnection(&Config{}, space, v2net.AnyIP)
 	space.Initialize()
 
 	traffic := ray.NewRay()
@@ -57,7 +57,7 @@ func TestSinglePacket(t *testing.T) {
 func TestUnreachableDestination(t *testing.T) {
 	assert := assert.On(t)
 
-	freedom := NewFreedomConnection(&Config{}, app.NewSpace())
+	freedom := NewFreedomConnection(&Config{}, app.NewSpace(), v2net.AnyIP)
 	traffic := ray.NewRay()
 	data2Send := "Data to be sent to remote"
 	payload := alloc.NewSmallBuffer().Clear().Append([]byte(data2Send))
@@ -81,7 +81,7 @@ func TestIPResolution(t *testing.T) {
 	})
 	space.BindApp(dns.APP_ID, dnsServer)
 
-	freedom := NewFreedomConnection(&Config{DomainStrategy: DomainStrategyUseIP}, space)
+	freedom := NewFreedomConnection(&Config{DomainStrategy: DomainStrategyUseIP}, space, v2net.AnyIP)
 
 	space.Initialize()
 

+ 11 - 13
proxy/http/server.go

@@ -33,10 +33,12 @@ type Server struct {
 	listeningAddress v2net.Address
 }
 
-func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher) *Server {
+func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher, listen v2net.Address, port v2net.Port) *Server {
 	return &Server{
 		packetDispatcher: packetDispatcher,
 		config:           config,
+		listeningAddress: listen,
+		listeningPort:    port,
 	}
 }
 
@@ -54,24 +56,18 @@ func (this *Server) Close() {
 	}
 }
 
-func (this *Server) Listen(address v2net.Address, port v2net.Port) error {
+func (this *Server) Start() error {
 	if this.accepting {
-		if this.listeningPort == port && this.listeningAddress.Equals(address) {
-			return nil
-		} else {
-			return proxy.ErrorAlreadyListening
-		}
+		return nil
 	}
-	this.listeningPort = port
-	this.listeningAddress = address
 
 	var tlsConfig *tls.Config
 	if this.config.TLSConfig != nil {
 		tlsConfig = this.config.TLSConfig.GetConfig()
 	}
-	tcpListener, err := hub.ListenTCP(address, port, this.handleConnection, tlsConfig)
+	tcpListener, err := hub.ListenTCP(this.listeningAddress, this.listeningPort, this.handleConnection, tlsConfig)
 	if err != nil {
-		log.Error("HTTP: Failed listen on port ", port, ": ", err)
+		log.Error("HTTP: Failed listen on port ", this.listeningPort, ": ", err)
 		return err
 	}
 	this.Lock()
@@ -276,12 +272,14 @@ func (this *Server) handlePlainHTTP(request *http.Request, dest v2net.Destinatio
 
 func init() {
 	internal.MustRegisterInboundHandlerCreator("http",
-		func(space app.Space, rawConfig interface{}) (proxy.InboundHandler, error) {
+		func(space app.Space, rawConfig interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
 			if !space.HasApp(dispatcher.APP_ID) {
 				return nil, internal.ErrorBadConfiguration
 			}
 			return NewServer(
 				rawConfig.(*Config),
-				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)), nil
+				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher),
+				listen,
+				port), nil
 		})
 }

+ 3 - 3
proxy/http/server_test.go

@@ -52,11 +52,11 @@ func TestNormalGetRequest(t *testing.T) {
 
 	testPacketDispatcher := testdispatcher.NewTestPacketDispatcher(nil)
 
-	httpProxy := NewServer(&Config{}, testPacketDispatcher)
+	port := v2nettesting.PickPort()
+	httpProxy := NewServer(&Config{}, testPacketDispatcher, v2net.LocalHostIP, port)
 	defer httpProxy.Close()
 
-	port := v2nettesting.PickPort()
-	err := httpProxy.Listen(v2net.LocalHostIP, port)
+	err := httpProxy.Start()
 	assert.Error(err).IsNil()
 	assert.Port(port).Equals(httpProxy.Port())
 

+ 3 - 2
proxy/internal/creator.go

@@ -2,8 +2,9 @@ package internal
 
 import (
 	"github.com/v2ray/v2ray-core/app"
+	v2net "github.com/v2ray/v2ray-core/common/net"
 	"github.com/v2ray/v2ray-core/proxy"
 )
 
-type InboundHandlerCreator func(space app.Space, config interface{}) (proxy.InboundHandler, error)
-type OutboundHandlerCreator func(space app.Space, config interface{}) (proxy.OutboundHandler, error)
+type InboundHandlerCreator func(space app.Space, config interface{}, listenOn v2net.Address, port v2net.Port) (proxy.InboundHandler, error)
+type OutboundHandlerCreator func(space app.Space, config interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error)

+ 7 - 6
proxy/internal/handler_cache.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 
 	"github.com/v2ray/v2ray-core/app"
+	v2net "github.com/v2ray/v2ray-core/common/net"
 	"github.com/v2ray/v2ray-core/proxy"
 	"github.com/v2ray/v2ray-core/proxy/internal/config"
 )
@@ -45,7 +46,7 @@ func MustRegisterOutboundHandlerCreator(name string, creator OutboundHandlerCrea
 	}
 }
 
-func CreateInboundHandler(name string, space app.Space, rawConfig []byte) (proxy.InboundHandler, error) {
+func CreateInboundHandler(name string, space app.Space, rawConfig []byte, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
 	creator, found := inboundFactories[name]
 	if !found {
 		return nil, ErrorProxyNotFound
@@ -55,12 +56,12 @@ func CreateInboundHandler(name string, space app.Space, rawConfig []byte) (proxy
 		if err != nil {
 			return nil, err
 		}
-		return creator(space, proxyConfig)
+		return creator(space, proxyConfig, listen, port)
 	}
-	return creator(space, nil)
+	return creator(space, nil, listen, port)
 }
 
-func CreateOutboundHandler(name string, space app.Space, rawConfig []byte) (proxy.OutboundHandler, error) {
+func CreateOutboundHandler(name string, space app.Space, rawConfig []byte, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
 	creator, found := outboundFactories[name]
 	if !found {
 		return nil, ErrorNameExists
@@ -71,8 +72,8 @@ func CreateOutboundHandler(name string, space app.Space, rawConfig []byte) (prox
 		if err != nil {
 			return nil, err
 		}
-		return creator(space, proxyConfig)
+		return creator(space, proxyConfig, sendThrough)
 	}
 
-	return creator(space, nil)
+	return creator(space, nil, sendThrough)
 }

+ 2 - 2
proxy/proxy.go

@@ -16,8 +16,8 @@ const (
 
 // An InboundHandler handles inbound network connections to V2Ray.
 type InboundHandler interface {
-	// Listen starts a InboundHandler by listen on a specific port.
-	Listen(on v2net.Address, port v2net.Port) error
+	// Listen starts a InboundHandler.
+	Start() error
 	// Close stops the handler to accepting anymore inbound connections.
 	Close()
 	// Port returns the port that the handler is listening on.

+ 5 - 4
proxy/repo/repo.go

@@ -2,14 +2,15 @@ package repo
 
 import (
 	"github.com/v2ray/v2ray-core/app"
+	v2net "github.com/v2ray/v2ray-core/common/net"
 	"github.com/v2ray/v2ray-core/proxy"
 	"github.com/v2ray/v2ray-core/proxy/internal"
 )
 
-func CreateInboundHandler(name string, space app.Space, rawConfig []byte) (proxy.InboundHandler, error) {
-	return internal.CreateInboundHandler(name, space, rawConfig)
+func CreateInboundHandler(name string, space app.Space, rawConfig []byte, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
+	return internal.CreateInboundHandler(name, space, rawConfig, listen, port)
 }
 
-func CreateOutboundHandler(name string, space app.Space, rawConfig []byte) (proxy.OutboundHandler, error) {
-	return internal.CreateOutboundHandler(name, space, rawConfig)
+func CreateOutboundHandler(name string, space app.Space, rawConfig []byte, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
+	return internal.CreateOutboundHandler(name, space, rawConfig, sendThrough)
 }

+ 2 - 1
proxy/shadowsocks/protocol_test.go

@@ -1,6 +1,7 @@
 package shadowsocks_test
 
 import (
+	"io"
 	"testing"
 
 	"github.com/v2ray/v2ray-core/common/alloc"
@@ -29,7 +30,7 @@ func TestEmptyPayload(t *testing.T) {
 
 	buffer := alloc.NewSmallBuffer().Clear()
 	_, err := ReadRequest(buffer, nil, false)
-	assert.Error(err).Equals(transport.ErrorCorruptedPacket)
+	assert.Error(err).Equals(io.EOF)
 }
 
 func TestSingleBytePayload(t *testing.T) {

+ 13 - 15
proxy/shadowsocks/server.go

@@ -30,10 +30,12 @@ type Server struct {
 	udpServer        *hub.UDPServer
 }
 
-func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher) *Server {
+func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher, listen v2net.Address, port v2net.Port) *Server {
 	return &Server{
 		config:           config,
 		packetDispatcher: packetDispatcher,
+		address:          listen,
+		port:             port,
 	}
 }
 
@@ -56,34 +58,28 @@ func (this *Server) Close() {
 
 }
 
-func (this *Server) Listen(address v2net.Address, port v2net.Port) error {
+func (this *Server) Start() error {
 	if this.accepting {
-		if this.port == port && this.address.Equals(address) {
-			return nil
-		} else {
-			return proxy.ErrorAlreadyListening
-		}
+		return nil
 	}
 
-	tcpHub, err := hub.ListenTCP(address, port, this.handleConnection, nil)
+	tcpHub, err := hub.ListenTCP(this.address, this.port, this.handleConnection, nil)
 	if err != nil {
-		log.Error("Shadowsocks: Failed to listen TCP on port ", port, ": ", err)
+		log.Error("Shadowsocks: Failed to listen TCP on port ", this.port, ": ", err)
 		return err
 	}
 	this.tcpHub = tcpHub
 
 	if this.config.UDP {
 		this.udpServer = hub.NewUDPServer(this.packetDispatcher)
-		udpHub, err := hub.ListenUDP(address, port, this.handlerUDPPayload)
+		udpHub, err := hub.ListenUDP(this.address, this.port, this.handlerUDPPayload)
 		if err != nil {
-			log.Error("Shadowsocks: Failed to listen UDP on port ", port, ": ", err)
+			log.Error("Shadowsocks: Failed to listen UDP on port ", this.port, ": ", err)
 			return err
 		}
 		this.udpHub = udpHub
 	}
 
-	this.port = port
-	this.address = address
 	this.accepting = true
 
 	return nil
@@ -256,12 +252,14 @@ func (this *Server) handleConnection(conn *hub.Connection) {
 
 func init() {
 	internal.MustRegisterInboundHandlerCreator("shadowsocks",
-		func(space app.Space, rawConfig interface{}) (proxy.InboundHandler, error) {
+		func(space app.Space, rawConfig interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
 			if !space.HasApp(dispatcher.APP_ID) {
 				return nil, internal.ErrorBadConfiguration
 			}
 			return NewServer(
 				rawConfig.(*Config),
-				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)), nil
+				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher),
+				listen,
+				port), nil
 		})
 }

+ 16 - 14
proxy/socks/server.go

@@ -38,10 +38,12 @@ type Server struct {
 }
 
 // NewServer creates a new Server object.
-func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher) *Server {
+func NewServer(config *Config, packetDispatcher dispatcher.PacketDispatcher, listen v2net.Address, port v2net.Port) *Server {
 	return &Server{
 		config:           config,
 		packetDispatcher: packetDispatcher,
+		listeningAddress: listen,
+		listeningPort:    port,
 	}
 }
 
@@ -68,20 +70,18 @@ func (this *Server) Close() {
 }
 
 // Listen implements InboundHandler.Listen().
-func (this *Server) Listen(address v2net.Address, port v2net.Port) error {
+func (this *Server) Start() error {
 	if this.accepting {
-		if this.listeningPort == port && this.listeningAddress.Equals(address) {
-			return nil
-		} else {
-			return proxy.ErrorAlreadyListening
-		}
+		return nil
 	}
-	this.listeningPort = port
-	this.listeningAddress = address
 
-	listener, err := hub.ListenTCP(address, port, this.handleConnection, nil)
+	listener, err := hub.ListenTCP(
+		this.listeningAddress,
+		this.listeningPort,
+		this.handleConnection,
+		nil)
 	if err != nil {
-		log.Error("Socks: failed to listen on port ", port, ": ", err)
+		log.Error("Socks: failed to listen on port ", this.listeningPort, ": ", err)
 		return err
 	}
 	this.accepting = true
@@ -89,7 +89,7 @@ func (this *Server) Listen(address v2net.Address, port v2net.Port) error {
 	this.tcpListener = listener
 	this.tcpMutex.Unlock()
 	if this.config.UDPEnabled {
-		this.listenUDP(address, port)
+		this.listenUDP(this.listeningAddress, this.listeningPort)
 	}
 	return nil
 }
@@ -301,12 +301,14 @@ func (this *Server) transport(reader io.Reader, writer io.Writer, destination v2
 
 func init() {
 	internal.MustRegisterInboundHandlerCreator("socks",
-		func(space app.Space, rawConfig interface{}) (proxy.InboundHandler, error) {
+		func(space app.Space, rawConfig interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
 			if !space.HasApp(dispatcher.APP_ID) {
 				return nil, internal.ErrorBadConfiguration
 			}
 			return NewServer(
 				rawConfig.(*Config),
-				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)), nil
+				space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher),
+				listen,
+				port), nil
 		})
 }

+ 32 - 28
proxy/socks/server_test.go

@@ -31,16 +31,17 @@ func TestSocksTcpConnect(t *testing.T) {
 		ConnInput:  bytes.NewReader(connInput),
 	}
 
-	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (v2proxy.OutboundHandler, error) {
-		return och, nil
-	})
+	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och",
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (v2proxy.OutboundHandler, error) {
+			return och, nil
+		})
 	assert.Error(err).IsNil()
 
 	config := &point.Config{
-		Port:     port,
-		ListenOn: v2net.LocalHostIP,
-		InboundConfig: &point.ConnectionConfig{
+		Port: port,
+		InboundConfig: &point.InboundConnectionConfig{
 			Protocol: "socks",
+			ListenOn: v2net.LocalHostIP,
 			Settings: []byte(`
       {
         "auth": "noauth"
@@ -51,7 +52,7 @@ func TestSocksTcpConnect(t *testing.T) {
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: protocol,
 			Settings: nil,
 		},
@@ -96,16 +97,17 @@ func TestSocksTcpConnectWithUserPass(t *testing.T) {
 		ConnOutput: connOutput,
 	}
 
-	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (v2proxy.OutboundHandler, error) {
-		return och, nil
-	})
+	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och",
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (v2proxy.OutboundHandler, error) {
+			return och, nil
+		})
 	assert.Error(err).IsNil()
 
 	config := &point.Config{
-		Port:     port,
-		ListenOn: v2net.LocalHostIP,
-		InboundConfig: &point.ConnectionConfig{
+		Port: port,
+		InboundConfig: &point.InboundConnectionConfig{
 			Protocol: "socks",
+			ListenOn: v2net.LocalHostIP,
 			Settings: []byte(`
       {
         "auth": "password",
@@ -119,7 +121,7 @@ func TestSocksTcpConnectWithUserPass(t *testing.T) {
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: protocol,
 			Settings: nil,
 		},
@@ -164,16 +166,17 @@ func TestSocksTcpConnectWithWrongUserPass(t *testing.T) {
 		ConnOutput: connOutput,
 	}
 
-	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (v2proxy.OutboundHandler, error) {
-		return och, nil
-	})
+	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och",
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (v2proxy.OutboundHandler, error) {
+			return och, nil
+		})
 	assert.Error(err).IsNil()
 
 	config := &point.Config{
-		Port:     port,
-		ListenOn: v2net.LocalHostIP,
-		InboundConfig: &point.ConnectionConfig{
+		Port: port,
+		InboundConfig: &point.InboundConnectionConfig{
 			Protocol: "socks",
+			ListenOn: v2net.LocalHostIP,
 			Settings: []byte(`
       {
         "auth": "password",
@@ -187,7 +190,7 @@ func TestSocksTcpConnectWithWrongUserPass(t *testing.T) {
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: protocol,
 			Settings: nil,
 		},
@@ -218,15 +221,16 @@ func TestSocksTcpConnectWithWrongAuthMethod(t *testing.T) {
 		ConnOutput: connOutput,
 	}
 
-	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (v2proxy.OutboundHandler, error) {
-		return och, nil
-	})
+	protocol, err := proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och",
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (v2proxy.OutboundHandler, error) {
+			return och, nil
+		})
 	assert.Error(err).IsNil()
 
 	config := &point.Config{
-		Port:     port,
-		ListenOn: v2net.LocalHostIP,
-		InboundConfig: &point.ConnectionConfig{
+		Port: port,
+		InboundConfig: &point.InboundConnectionConfig{
+			ListenOn: v2net.LocalHostIP,
 			Protocol: "socks",
 			Settings: []byte(`
       {
@@ -241,7 +245,7 @@ func TestSocksTcpConnectWithWrongAuthMethod(t *testing.T) {
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: protocol,
 			Settings: nil,
 		},

+ 4 - 6
proxy/testing/mocks/inboundhandler.go

@@ -10,21 +10,19 @@ import (
 )
 
 type InboundConnectionHandler struct {
-	port             v2net.Port
-	address          v2net.Address
+	ListeningPort    v2net.Port
+	ListeningAddress v2net.Address
 	PacketDispatcher dispatcher.PacketDispatcher
 	ConnInput        io.Reader
 	ConnOutput       io.Writer
 }
 
-func (this *InboundConnectionHandler) Listen(address v2net.Address, port v2net.Port) error {
-	this.port = port
-	this.address = address
+func (this *InboundConnectionHandler) Start() error {
 	return nil
 }
 
 func (this *InboundConnectionHandler) Port() v2net.Port {
-	return this.port
+	return this.ListeningPort
 }
 
 func (this *InboundConnectionHandler) Close() {

+ 1 - 1
proxy/testing/mocks/outboundhandler.go

@@ -50,6 +50,6 @@ func (this *OutboundConnectionHandler) Dispatch(destination v2net.Destination, p
 	return nil
 }
 
-func (this *OutboundConnectionHandler) Create(space app.Space, config interface{}) (proxy.OutboundHandler, error) {
+func (this *OutboundConnectionHandler) Create(space app.Space, config interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
 	return this, nil
 }

+ 9 - 11
proxy/vmess/inbound/inbound.go

@@ -88,6 +88,8 @@ func (this *VMessInboundHandler) Close() {
 		this.Lock()
 		this.listener.Close()
 		this.listener = nil
+		this.clients.Release()
+		this.clients = nil
 		this.Unlock()
 	}
 }
@@ -100,20 +102,14 @@ func (this *VMessInboundHandler) GetUser(email string) *protocol.User {
 	return user
 }
 
-func (this *VMessInboundHandler) Listen(address v2net.Address, port v2net.Port) error {
+func (this *VMessInboundHandler) Start() error {
 	if this.accepting {
-		if this.listeningPort == port && this.listeningAddress.Equals(address) {
-			return nil
-		} else {
-			return proxy.ErrorAlreadyListening
-		}
+		return nil
 	}
-	this.listeningPort = port
-	this.listeningAddress = address
 
-	tcpListener, err := hub.ListenTCP(address, port, this.HandleConnection, nil)
+	tcpListener, err := hub.ListenTCP(this.listeningAddress, this.listeningPort, this.HandleConnection, nil)
 	if err != nil {
-		log.Error("Unable to listen tcp port ", port, ": ", err)
+		log.Error("Unable to listen tcp port ", this.listeningPort, ": ", err)
 		return err
 	}
 	this.accepting = true
@@ -224,7 +220,7 @@ func (this *VMessInboundHandler) HandleConnection(connection *hub.Connection) {
 
 func init() {
 	internal.MustRegisterInboundHandlerCreator("vmess",
-		func(space app.Space, rawConfig interface{}) (proxy.InboundHandler, error) {
+		func(space app.Space, rawConfig interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
 			if !space.HasApp(dispatcher.APP_ID) {
 				return nil, internal.ErrorBadConfiguration
 			}
@@ -240,6 +236,8 @@ func init() {
 				clients:          allowedClients,
 				detours:          config.DetourConfig,
 				usersByEmail:     NewUserByEmail(config.AllowedUsers, config.Defaults),
+				listeningAddress: listen,
+				listeningPort:    port,
 			}
 
 			if space.HasApp(proxyman.APP_ID_INBOUND_MANAGER) {

+ 4 - 2
proxy/vmess/outbound/outbound.go

@@ -21,6 +21,7 @@ import (
 
 type VMessOutboundHandler struct {
 	receiverManager *ReceiverManager
+	sendThrough     v2net.Address
 }
 
 func (this *VMessOutboundHandler) Dispatch(target v2net.Destination, payload *alloc.Buffer, ray ray.OutboundRay) error {
@@ -42,7 +43,7 @@ func (this *VMessOutboundHandler) Dispatch(target v2net.Destination, payload *al
 		Option:  protocol.RequestOptionChunkStream,
 	}
 
-	conn, err := hub.Dial(destination)
+	conn, err := hub.Dial(this.sendThrough, destination)
 	if err != nil {
 		log.Error("Failed to open ", destination, ": ", err)
 		return err
@@ -142,10 +143,11 @@ func (this *VMessOutboundHandler) handleResponse(session *raw.ClientSession, con
 
 func init() {
 	internal.MustRegisterOutboundHandlerCreator("vmess",
-		func(space app.Space, rawConfig interface{}) (proxy.OutboundHandler, error) {
+		func(space app.Space, rawConfig interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
 			vOutConfig := rawConfig.(*Config)
 			return &VMessOutboundHandler{
 				receiverManager: NewReceiverManager(vOutConfig.Receivers),
+				sendThrough:     sendThrough,
 			}, nil
 		})
 }

+ 19 - 15
proxy/vmess/vmess_test.go

@@ -38,25 +38,28 @@ func TestVMessInAndOut(t *testing.T) {
 		ConnOutput: ichConnOutput,
 	}
 
-	protocol, err := proxytesting.RegisterInboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (proxy.InboundHandler, error) {
-		ich.PacketDispatcher = space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)
-		return ich, nil
-	})
+	protocol, err := proxytesting.RegisterInboundConnectionHandlerCreator("mock_ich",
+		func(space app.Space, config interface{}, listen v2net.Address, port v2net.Port) (proxy.InboundHandler, error) {
+			ich.ListeningAddress = listen
+			ich.ListeningPort = port
+			ich.PacketDispatcher = space.GetApp(dispatcher.APP_ID).(dispatcher.PacketDispatcher)
+			return ich, nil
+		})
 	assert.Error(err).IsNil()
 
 	configA := &point.Config{
-		Port:     portA,
-		ListenOn: v2net.LocalHostIP,
+		Port: portA,
 		DNSConfig: &dns.Config{
 			NameServers: []v2net.Destination{
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		InboundConfig: &point.ConnectionConfig{
+		InboundConfig: &point.InboundConnectionConfig{
 			Protocol: protocol,
+			ListenOn: v2net.LocalHostIP,
 			Settings: nil,
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: "vmess",
 			Settings: []byte(`{
         "vnext": [
@@ -85,28 +88,29 @@ func TestVMessInAndOut(t *testing.T) {
 		ConnOutput: ochConnOutput,
 	}
 
-	protocol, err = proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och", func(space app.Space, config interface{}) (proxy.OutboundHandler, error) {
-		return och, nil
-	})
+	protocol, err = proxytesting.RegisterOutboundConnectionHandlerCreator("mock_och",
+		func(space app.Space, config interface{}, sendThrough v2net.Address) (proxy.OutboundHandler, error) {
+			return och, nil
+		})
 	assert.Error(err).IsNil()
 
 	configB := &point.Config{
-		Port:     portB,
-		ListenOn: v2net.LocalHostIP,
+		Port: portB,
 		DNSConfig: &dns.Config{
 			NameServers: []v2net.Destination{
 				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
 			},
 		},
-		InboundConfig: &point.ConnectionConfig{
+		InboundConfig: &point.InboundConnectionConfig{
 			Protocol: "vmess",
+			ListenOn: v2net.LocalHostIP,
 			Settings: []byte(`{
         "clients": [
           {"id": "` + testAccount.String() + `"}
         ]
       }`),
 		},
-		OutboundConfig: &point.ConnectionConfig{
+		OutboundConfig: &point.OutboundConnectionConfig{
 			Protocol: protocol,
 			Settings: nil,
 		},

+ 3 - 2
release/config/vpoint_socks_vmess.json

@@ -1,9 +1,10 @@
 {
-  "port": 1080,
   "log": {
-    "access": ""
+    "loglevel": "warning"
   },
   "inbound": {
+    "port": 1080,
+    "listen": "127.0.0.1",
     "protocol": "socks",
     "settings": {
       "auth": "noauth",

+ 1 - 1
release/config/vpoint_vmess_freedom.json

@@ -1,11 +1,11 @@
 {
-  "port": 10086,
   "log" : {
     "access": "/var/log/v2ray/access.log",
     "error": "/var/log/v2ray/error.log",
     "loglevel": "warning"
   },
   "inbound": {
+    "port": 10086,
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 15 - 7
shell/point/config.go

@@ -8,11 +8,19 @@ import (
 	"github.com/v2ray/v2ray-core/transport"
 )
 
-type ConnectionConfig struct {
+type InboundConnectionConfig struct {
+	Port     v2net.Port
+	ListenOn v2net.Address
 	Protocol string
 	Settings []byte
 }
 
+type OutboundConnectionConfig struct {
+	Protocol    string
+	SendThrough v2net.Address
+	Settings    []byte
+}
+
 type LogConfig struct {
 	AccessLog string
 	ErrorLog  string
@@ -41,19 +49,19 @@ type InboundDetourConfig struct {
 }
 
 type OutboundDetourConfig struct {
-	Protocol string
-	Tag      string
-	Settings []byte
+	Protocol    string
+	SendThrough v2net.Address
+	Tag         string
+	Settings    []byte
 }
 
 type Config struct {
 	Port            v2net.Port
-	ListenOn        v2net.Address
 	LogConfig       *LogConfig
 	RouterConfig    *router.Config
 	DNSConfig       *dns.Config
-	InboundConfig   *ConnectionConfig
-	OutboundConfig  *ConnectionConfig
+	InboundConfig   *InboundConnectionConfig
+	OutboundConfig  *OutboundConnectionConfig
 	InboundDetours  []*InboundDetourConfig
 	OutboundDetours []*OutboundDetourConfig
 	TransportConfig *transport.Config

+ 59 - 23
shell/point/config_json.go

@@ -22,29 +22,21 @@ const (
 
 func (this *Config) UnmarshalJSON(data []byte) error {
 	type JsonConfig struct {
-		Port            v2net.Port              `json:"port"` // Port of this Point server.
-		ListenOn        *v2net.AddressJson      `json:"listen"`
-		LogConfig       *LogConfig              `json:"log"`
-		RouterConfig    *router.Config          `json:"routing"`
-		DNSConfig       *dns.Config             `json:"dns"`
-		InboundConfig   *ConnectionConfig       `json:"inbound"`
-		OutboundConfig  *ConnectionConfig       `json:"outbound"`
-		InboundDetours  []*InboundDetourConfig  `json:"inboundDetour"`
-		OutboundDetours []*OutboundDetourConfig `json:"outboundDetour"`
-		Transport       *transport.Config       `json:"transport"`
+		Port            v2net.Port                `json:"port"` // Port of this Point server.
+		LogConfig       *LogConfig                `json:"log"`
+		RouterConfig    *router.Config            `json:"routing"`
+		DNSConfig       *dns.Config               `json:"dns"`
+		InboundConfig   *InboundConnectionConfig  `json:"inbound"`
+		OutboundConfig  *OutboundConnectionConfig `json:"outbound"`
+		InboundDetours  []*InboundDetourConfig    `json:"inboundDetour"`
+		OutboundDetours []*OutboundDetourConfig   `json:"outboundDetour"`
+		Transport       *transport.Config         `json:"transport"`
 	}
 	jsonConfig := new(JsonConfig)
 	if err := json.Unmarshal(data, jsonConfig); err != nil {
 		return err
 	}
 	this.Port = jsonConfig.Port
-	this.ListenOn = v2net.AnyIP
-	if jsonConfig.ListenOn != nil {
-		if jsonConfig.ListenOn.Address.IsDomain() {
-			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.ListenOn.Address.Domain())
-		}
-		this.ListenOn = jsonConfig.ListenOn.Address
-	}
 	this.LogConfig = jsonConfig.LogConfig
 	this.RouterConfig = jsonConfig.RouterConfig
 	this.InboundConfig = jsonConfig.InboundConfig
@@ -63,10 +55,37 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-func (this *ConnectionConfig) UnmarshalJSON(data []byte) error {
+func (this *InboundConnectionConfig) UnmarshalJSON(data []byte) error {
+	type JsonConfig struct {
+		Port     uint16             `json:"port"`
+		Listen   *v2net.AddressJson `json:"listen"`
+		Protocol string             `json:"protocol"`
+		Settings json.RawMessage    `json:"settings"`
+	}
+
+	jsonConfig := new(JsonConfig)
+	if err := json.Unmarshal(data, jsonConfig); err != nil {
+		return err
+	}
+	this.Port = v2net.Port(jsonConfig.Port)
+	this.ListenOn = v2net.AnyIP
+	if jsonConfig.Listen != nil {
+		if jsonConfig.Listen.Address.IsDomain() {
+			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.Listen.Address.Domain())
+		}
+		this.ListenOn = jsonConfig.Listen.Address
+	}
+
+	this.Protocol = jsonConfig.Protocol
+	this.Settings = jsonConfig.Settings
+	return nil
+}
+
+func (this *OutboundConnectionConfig) UnmarshalJSON(data []byte) error {
 	type JsonConnectionConfig struct {
-		Protocol string          `json:"protocol"`
-		Settings json.RawMessage `json:"settings"`
+		Protocol    string             `json:"protocol"`
+		SendThrough *v2net.AddressJson `json:"sendThrough"`
+		Settings    json.RawMessage    `json:"settings"`
 	}
 	jsonConfig := new(JsonConnectionConfig)
 	if err := json.Unmarshal(data, jsonConfig); err != nil {
@@ -74,6 +93,14 @@ func (this *ConnectionConfig) UnmarshalJSON(data []byte) error {
 	}
 	this.Protocol = jsonConfig.Protocol
 	this.Settings = jsonConfig.Settings
+
+	if jsonConfig.SendThrough != nil {
+		address := jsonConfig.SendThrough.Address
+		if address.IsDomain() {
+			return errors.New("Point: Unable to send through: " + address.String())
+		}
+		this.SendThrough = address
+	}
 	return nil
 }
 
@@ -173,9 +200,10 @@ func (this *InboundDetourConfig) UnmarshalJSON(data []byte) error {
 
 func (this *OutboundDetourConfig) UnmarshalJSON(data []byte) error {
 	type JsonOutboundDetourConfig struct {
-		Protocol string          `json:"protocol"`
-		Tag      string          `json:"tag"`
-		Settings json.RawMessage `json:"settings"`
+		Protocol    string             `json:"protocol"`
+		SendThrough *v2net.AddressJson `json:"sendThrough"`
+		Tag         string             `json:"tag"`
+		Settings    json.RawMessage    `json:"settings"`
 	}
 	jsonConfig := new(JsonOutboundDetourConfig)
 	if err := json.Unmarshal(data, jsonConfig); err != nil {
@@ -184,6 +212,14 @@ func (this *OutboundDetourConfig) UnmarshalJSON(data []byte) error {
 	this.Protocol = jsonConfig.Protocol
 	this.Tag = jsonConfig.Tag
 	this.Settings = jsonConfig.Settings
+
+	if jsonConfig.SendThrough != nil {
+		address := jsonConfig.SendThrough.Address
+		if address.IsDomain() {
+			return errors.New("Point: Unable to send through: " + address.String())
+		}
+		this.SendThrough = address
+	}
 	return nil
 }
 

+ 2 - 2
shell/point/config_json_test.go

@@ -23,8 +23,8 @@ func TestClientSampleConfig(t *testing.T) {
 	pointConfig, err := LoadConfig(filepath.Join(baseDir, "vpoint_socks_vmess.json"))
 	assert.Error(err).IsNil()
 
-	assert.Port(pointConfig.Port).IsValid()
 	assert.Pointer(pointConfig.InboundConfig).IsNotNil()
+	assert.Port(pointConfig.InboundConfig.Port).IsValid()
 	assert.Pointer(pointConfig.OutboundConfig).IsNotNil()
 
 	assert.String(pointConfig.InboundConfig.Protocol).Equals("socks")
@@ -43,8 +43,8 @@ func TestServerSampleConfig(t *testing.T) {
 	pointConfig, err := LoadConfig(filepath.Join(baseDir, "vpoint_vmess_freedom.json"))
 	assert.Error(err).IsNil()
 
-	assert.Port(pointConfig.Port).IsValid()
 	assert.Pointer(pointConfig.InboundConfig).IsNotNil()
+	assert.Port(pointConfig.InboundConfig.Port).IsValid()
 	assert.Pointer(pointConfig.OutboundConfig).IsNotNil()
 
 	assert.String(pointConfig.InboundConfig.Protocol).Equals("vmess")

+ 2 - 2
shell/point/inbound_detour_always.go

@@ -32,7 +32,7 @@ func NewInboundDetourHandlerAlways(space app.Space, config *InboundDetourConfig)
 	handler.ich = make([]*InboundConnectionHandlerWithPort, 0, ports.To-ports.From+1)
 	for i := ports.From; i <= ports.To; i++ {
 		ichConfig := config.Settings
-		ich, err := proxyrepo.CreateInboundHandler(config.Protocol, space, ichConfig)
+		ich, err := proxyrepo.CreateInboundHandler(config.Protocol, space, ichConfig, config.ListenOn, i)
 		if err != nil {
 			log.Error("Failed to create inbound connection handler: ", err)
 			return nil, err
@@ -61,7 +61,7 @@ func (this *InboundDetourHandlerAlways) Close() {
 func (this *InboundDetourHandlerAlways) Start() error {
 	for _, ich := range this.ich {
 		err := retry.Timed(100 /* times */, 100 /* ms */).On(func() error {
-			err := ich.handler.Listen(ich.listen, ich.port)
+			err := ich.handler.Start()
 			if err != nil {
 				log.Error("Failed to start inbound detour on port ", ich.port, ": ", err)
 				return err

+ 48 - 42
shell/point/inbound_detour_dynamic.go

@@ -8,7 +8,6 @@ import (
 	"github.com/v2ray/v2ray-core/common/dice"
 	"github.com/v2ray/v2ray-core/common/log"
 	v2net "github.com/v2ray/v2ray-core/common/net"
-	"github.com/v2ray/v2ray-core/common/retry"
 	"github.com/v2ray/v2ray-core/proxy"
 	proxyrepo "github.com/v2ray/v2ray-core/proxy/repo"
 )
@@ -18,8 +17,7 @@ type InboundDetourHandlerDynamic struct {
 	space       app.Space
 	config      *InboundDetourConfig
 	portsInUse  map[v2net.Port]bool
-	ichInUse    []proxy.InboundHandler
-	ich2Recycle []proxy.InboundHandler
+	ichs        []proxy.InboundHandler
 	lastRefresh time.Time
 }
 
@@ -29,18 +27,16 @@ func NewInboundDetourHandlerDynamic(space app.Space, config *InboundDetourConfig
 		config:     config,
 		portsInUse: make(map[v2net.Port]bool),
 	}
-	ichCount := config.Allocation.Concurrency
-	ichArray := make([]proxy.InboundHandler, ichCount*2)
-	for idx := range ichArray {
-		ich, err := proxyrepo.CreateInboundHandler(config.Protocol, space, config.Settings)
-		if err != nil {
-			log.Error("Point: Failed to create inbound connection handler: ", err)
-			return nil, err
-		}
-		ichArray[idx] = ich
+	handler.ichs = make([]proxy.InboundHandler, config.Allocation.Concurrency)
+
+	// To test configuration
+	ich, err := proxyrepo.CreateInboundHandler(config.Protocol, space, config.Settings, config.ListenOn, 0)
+	if err != nil {
+		log.Error("Point: Failed to create inbound connection handler: ", err)
+		return nil, err
 	}
-	handler.ichInUse = ichArray[:ichCount]
-	handler.ich2Recycle = ichArray[ichCount:]
+	ich.Close()
+
 	return handler, nil
 }
 
@@ -59,7 +55,7 @@ func (this *InboundDetourHandlerDynamic) pickUnusedPort() v2net.Port {
 func (this *InboundDetourHandlerDynamic) GetConnectionHandler() (proxy.InboundHandler, int) {
 	this.RLock()
 	defer this.RUnlock()
-	ich := this.ichInUse[dice.Roll(len(this.ichInUse))]
+	ich := this.ichs[dice.Roll(len(this.ichs))]
 	until := this.config.Allocation.Refresh - int((time.Now().Unix()-this.lastRefresh.Unix())/60/1000)
 	if until < 0 {
 		until = 0
@@ -70,58 +66,68 @@ func (this *InboundDetourHandlerDynamic) GetConnectionHandler() (proxy.InboundHa
 func (this *InboundDetourHandlerDynamic) Close() {
 	this.Lock()
 	defer this.Unlock()
-	for _, ich := range this.ichInUse {
+	for _, ich := range this.ichs {
 		ich.Close()
 	}
-	if this.ich2Recycle != nil {
-		for _, ich := range this.ich2Recycle {
-			if ich != nil {
-				ich.Close()
-			}
-		}
-	}
 }
 
 func (this *InboundDetourHandlerDynamic) refresh() error {
 	this.lastRefresh = time.Now()
 
-	for _, ich := range this.ich2Recycle {
-		port2Delete := ich.Port()
+	config := this.config
+	ich2Recycle := this.ichs
+	newIchs := make([]proxy.InboundHandler, config.Allocation.Concurrency)
 
-		ich.Close()
-		err := retry.Timed(100 /* times */, 1000 /* ms */).On(func() error {
-			port := this.pickUnusedPort()
-			err := ich.Listen(this.config.ListenOn, port)
-			if err != nil {
-				log.Error("Point: Failed to start inbound detour on port ", port, ": ", err)
-				return err
-			}
-			this.portsInUse[port] = true
-			return nil
-		})
+	for idx, _ := range newIchs {
+		port := this.pickUnusedPort()
+		ich, err := proxyrepo.CreateInboundHandler(config.Protocol, this.space, config.Settings, config.ListenOn, port)
 		if err != nil {
-			continue
+			log.Error("Point: Failed to create inbound connection handler: ", err)
+			return err
 		}
-
-		delete(this.portsInUse, port2Delete)
+		err = ich.Start()
+		if err != nil {
+			log.Error("Point: Failed to start inbound connection handler: ", err)
+			return err
+		}
+		this.portsInUse[port] = true
+		newIchs[idx] = ich
 	}
 
 	this.Lock()
-	this.ich2Recycle, this.ichInUse = this.ichInUse, this.ich2Recycle
+	this.ichs = newIchs
 	this.Unlock()
 
+	go func() {
+		time.Sleep(time.Minute)
+		for _, ich := range ich2Recycle {
+			if ich == nil {
+				continue
+			}
+			port := ich.Port()
+			ich.Close()
+			delete(this.portsInUse, port)
+		}
+		ich2Recycle = nil
+	}()
+
 	return nil
 }
 
 func (this *InboundDetourHandlerDynamic) Start() error {
 	err := this.refresh()
 	if err != nil {
+		log.Error("Point: Failed to refresh dynamic allocations: ", err)
 		return err
 	}
 
 	go func() {
-		for range time.Tick(time.Duration(this.config.Allocation.Refresh) * time.Minute) {
-			this.refresh()
+		for {
+			time.Sleep(time.Duration(this.config.Allocation.Refresh) * time.Minute)
+			err := this.refresh()
+			if err != nil {
+				log.Error("Point: Failed to refresh dynamic allocations: ", err)
+			}
 		}
 	}()
 

+ 10 - 6
shell/point/point.go

@@ -35,8 +35,12 @@ type Point struct {
 // The server is not started at this point.
 func NewPoint(pConfig *Config) (*Point, error) {
 	var vpoint = new(Point)
-	vpoint.port = pConfig.Port
-	vpoint.listen = pConfig.ListenOn
+	vpoint.port = pConfig.InboundConfig.Port
+	if vpoint.port == 0 {
+		vpoint.port = pConfig.Port // Backward compatibility
+	}
+
+	vpoint.listen = pConfig.InboundConfig.ListenOn
 
 	if pConfig.TransportConfig != nil {
 		pConfig.TransportConfig.Apply()
@@ -87,7 +91,7 @@ func NewPoint(pConfig *Config) (*Point, error) {
 	vpoint.space.BindApp(dispatcher.APP_ID, dispatchers.NewDefaultDispatcher(vpoint.space))
 
 	ichConfig := pConfig.InboundConfig.Settings
-	ich, err := proxyrepo.CreateInboundHandler(pConfig.InboundConfig.Protocol, vpoint.space, ichConfig)
+	ich, err := proxyrepo.CreateInboundHandler(pConfig.InboundConfig.Protocol, vpoint.space, ichConfig, pConfig.InboundConfig.ListenOn, vpoint.port)
 	if err != nil {
 		log.Error("Failed to create inbound connection handler: ", err)
 		return nil, err
@@ -95,7 +99,7 @@ func NewPoint(pConfig *Config) (*Point, error) {
 	vpoint.ich = ich
 
 	ochConfig := pConfig.OutboundConfig.Settings
-	och, err := proxyrepo.CreateOutboundHandler(pConfig.OutboundConfig.Protocol, vpoint.space, ochConfig)
+	och, err := proxyrepo.CreateOutboundHandler(pConfig.OutboundConfig.Protocol, vpoint.space, ochConfig, pConfig.OutboundConfig.SendThrough)
 	if err != nil {
 		log.Error("Failed to create outbound connection handler: ", err)
 		return nil, err
@@ -140,7 +144,7 @@ func NewPoint(pConfig *Config) (*Point, error) {
 	if len(outboundDetours) > 0 {
 		vpoint.odh = make(map[string]proxy.OutboundHandler)
 		for _, detourConfig := range outboundDetours {
-			detourHandler, err := proxyrepo.CreateOutboundHandler(detourConfig.Protocol, vpoint.space, detourConfig.Settings)
+			detourHandler, err := proxyrepo.CreateOutboundHandler(detourConfig.Protocol, vpoint.space, detourConfig.Settings, detourConfig.SendThrough)
 			if err != nil {
 				log.Error("Point: Failed to create detour outbound connection handler: ", err)
 				return nil, err
@@ -173,7 +177,7 @@ func (this *Point) Start() error {
 	}
 
 	err := retry.Timed(100 /* times */, 100 /* ms */).On(func() error {
-		err := this.ich.Listen(this.listen, this.port)
+		err := this.ich.Start()
 		if err != nil {
 			return err
 		}

+ 2 - 2
testing/scenarios/data/test_1_client.json

@@ -1,7 +1,7 @@
 {
-  "port": 50000,
-  "listen": "127.0.0.1",
   "inbound": {
+    "port": 50000,
+    "listen": "127.0.0.1",
     "protocol": "socks",
     "settings": {
       "auth": "noauth",

+ 1 - 1
testing/scenarios/data/test_1_server.json

@@ -1,10 +1,10 @@
 {
   "port": 50001,
-  "listen": "127.0.0.1",
   "log": {
     "loglevel": "none"
   },
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 1 - 1
testing/scenarios/data/test_2_client.json

@@ -1,7 +1,7 @@
 {
   "port": 50010,
-  "listen": "127.0.0.1",
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "socks",
     "settings": {
       "auth": "noauth",

+ 1 - 1
testing/scenarios/data/test_2_server.json

@@ -1,10 +1,10 @@
 {
   "port": 50017,
-  "listen": "127.0.0.1",
   "log": {
     "loglevel": "none"
   },
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 1 - 1
testing/scenarios/data/test_3_client.json

@@ -1,10 +1,10 @@
 {
   "port": 50020,
-  "listen": "127.0.0.1",
   "log": {
     "loglevel": "none"
   },
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "dokodemo-door",
     "settings": {
       "address": "127.0.0.1",

+ 1 - 1
testing/scenarios/data/test_3_server.json

@@ -1,7 +1,7 @@
 {
   "port": 50021,
-  "listen": "127.0.0.1",
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 1 - 1
testing/scenarios/data/test_4_client.json

@@ -1,10 +1,10 @@
 {
   "port": 50030,
-  "listen": "127.0.0.1",
   "log": {
     "loglevel": "debug"
   },
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "dokodemo-door",
     "settings": {
       "address": "127.0.0.1",

+ 1 - 1
testing/scenarios/data/test_4_server.json

@@ -1,10 +1,10 @@
 {
   "port": 50031,
-  "listen": "127.0.0.1",
   "log": {
     "loglevel": "debug"
   },
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 1 - 1
testing/scenarios/data/test_5_client.json

@@ -1,7 +1,7 @@
 {
   "port": 50040,
-  "listen": "127.0.0.1",
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "http",
     "settings": {}
   },

+ 1 - 1
testing/scenarios/data/test_5_server.json

@@ -1,7 +1,7 @@
 {
   "port": 50041,
-  "listen": "127.0.0.1",
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "vmess",
     "settings": {
       "clients": [

+ 1 - 1
testing/scenarios/data/test_6_server.json

@@ -1,7 +1,7 @@
 {
   "port": 50051,
-  "listen": "127.0.0.1",
   "inbound": {
+    "listen": "127.0.0.1",
     "protocol": "shadowsocks",
     "settings": {
       "method": "aes-256-cfb",

+ 30 - 14
testing/scenarios/server_env.go

@@ -1,12 +1,13 @@
 package scenarios
 
 import (
+	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 
 	_ "github.com/v2ray/v2ray-core/app/router/rules"
 	"github.com/v2ray/v2ray-core/common/log"
-	"github.com/v2ray/v2ray-core/shell/point"
 
 	// The following are necessary as they register handlers in their init functions.
 	_ "github.com/v2ray/v2ray-core/proxy/blackhole"
@@ -20,9 +21,25 @@ import (
 )
 
 var (
-	runningServers = make([]*point.Point, 0, 10)
+	runningServers = make([]*exec.Cmd, 0, 10)
+
+	binaryPath string
 )
 
+func BuildV2Ray() error {
+	if len(binaryPath) > 0 {
+		return nil
+	}
+
+	dir, err := ioutil.TempDir("", "v2ray")
+	if err != nil {
+		return err
+	}
+	binaryPath = filepath.Join(dir, "v2ray.exe")
+	cmd := exec.Command("go", "build", "-tags=json", "-o="+binaryPath, filepath.Join("github.com", "v2ray", "v2ray-core", "release", "server"))
+	return cmd.Run()
+}
+
 func TestFile(filename string) string {
 	return filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "v2ray", "v2ray-core", "testing", "scenarios", "data", filename)
 }
@@ -46,31 +63,30 @@ func InitializeServerClient(testcase string) error {
 }
 
 func InitializeServer(configFile string) error {
-	config, err := point.LoadConfig(configFile)
+	err := BuildV2Ray()
 	if err != nil {
-		log.Error("Failed to read config file (", configFile, "): ", configFile, err)
 		return err
 	}
 
-	vPoint, err := point.NewPoint(config)
-	if err != nil {
-		log.Error("Failed to create Point server: ", err)
-		return err
-	}
+	proc := exec.Command(binaryPath, "-config="+configFile)
+	proc.Stderr = os.Stderr
+	proc.Stdout = os.Stdout
 
-	err = vPoint.Start()
+	err = proc.Start()
 	if err != nil {
-		log.Error("Error starting Point server: ", err)
 		return err
 	}
-	runningServers = append(runningServers, vPoint)
+
+	runningServers = append(runningServers, proc)
 
 	return nil
 }
 
 func CloseAllServers() {
+	log.Info("Closing all servers.")
 	for _, server := range runningServers {
-		server.Close()
+		server.Process.Kill()
 	}
-	runningServers = make([]*point.Point, 0, 10)
+	runningServers = make([]*exec.Cmd, 0, 10)
+	log.Info("All server closed.")
 }

+ 1 - 1
transport/hub/connection_cache.go

@@ -19,7 +19,7 @@ func (o *Once) Do(f func()) {
 	o.m.Lock()
 	defer o.m.Unlock()
 	if o.done == 0 {
-		defer atomic.StoreUint32(&o.done, 1)
+		atomic.StoreUint32(&o.done, 1)
 		f()
 	}
 }

+ 28 - 27
transport/hub/dialer.go

@@ -15,49 +15,50 @@ var (
 	globalCache = NewConnectionCache()
 )
 
-func Dial(dest v2net.Destination) (*Connection, error) {
-	destStr := dest.String()
+func Dial(src v2net.Address, dest v2net.Destination) (*Connection, error) {
+	if src == nil {
+		src = v2net.AnyIP
+	}
+	id := src.String() + "-" + dest.NetAddr()
 	var conn net.Conn
-	if transport.IsConnectionReusable() {
-		conn = globalCache.Get(destStr)
+	if dest.IsTCP() && transport.IsConnectionReusable() {
+		conn = globalCache.Get(id)
 	}
 	if conn == nil {
 		var err error
-		conn, err = DialWithoutCache(dest)
+		conn, err = DialWithoutCache(src, dest)
 		if err != nil {
 			return nil, err
 		}
 	}
 	return &Connection{
-		dest:     destStr,
+		dest:     id,
 		conn:     conn,
 		listener: globalCache,
 	}, nil
 }
 
-func DialWithoutCache(dest v2net.Destination) (net.Conn, error) {
-	if dest.Address().IsDomain() {
-		dialer := &net.Dialer{
-			Timeout:   time.Second * 60,
-			DualStack: true,
-		}
-		network := "tcp"
-		if dest.IsUDP() {
-			network = "udp"
-		}
-		return dialer.Dial(network, dest.NetAddr())
+func DialWithoutCache(src v2net.Address, dest v2net.Destination) (net.Conn, error) {
+	dialer := &net.Dialer{
+		Timeout:   time.Second * 60,
+		DualStack: true,
 	}
 
-	ip := dest.Address().IP()
-	if dest.IsTCP() {
-		return net.DialTCP("tcp", nil, &net.TCPAddr{
-			IP:   ip,
-			Port: int(dest.Port()),
-		})
+	if src != nil && src != v2net.AnyIP {
+		var addr net.Addr
+		if dest.IsTCP() {
+			addr = &net.TCPAddr{
+				IP:   src.IP(),
+				Port: 0,
+			}
+		} else {
+			addr = &net.UDPAddr{
+				IP:   src.IP(),
+				Port: 0,
+			}
+		}
+		dialer.LocalAddr = addr
 	}
 
-	return net.DialUDP("udp", nil, &net.UDPAddr{
-		IP:   ip,
-		Port: int(dest.Port()),
-	})
+	return dialer.Dial(dest.Network().String(), dest.NetAddr())
 }

+ 30 - 1
transport/hub/dialer_test.go

@@ -1,6 +1,7 @@
 package hub_test
 
 import (
+	"net"
 	"testing"
 
 	v2net "github.com/v2ray/v2ray-core/common/net"
@@ -20,7 +21,35 @@ func TestDialDomain(t *testing.T) {
 	assert.Error(err).IsNil()
 	defer server.Close()
 
-	conn, err := Dial(v2net.TCPDestination(v2net.DomainAddress("local.v2ray.com"), dest.Port()))
+	conn, err := Dial(nil, v2net.TCPDestination(v2net.DomainAddress("local.v2ray.com"), dest.Port()))
+	assert.Error(err).IsNil()
+	assert.String(conn.RemoteAddr().String()).Equals("127.0.0.1:" + dest.Port().String())
+	conn.Close()
+}
+
+func TestDialWithLocalAddr(t *testing.T) {
+	assert := assert.On(t)
+
+	server := &tcp.Server{
+		Port: v2nettesting.PickPort(),
+	}
+	dest, err := server.Start()
+	assert.Error(err).IsNil()
+	defer server.Close()
+
+	var localAddr net.IP
+	addrs, err := net.InterfaceAddrs()
+	assert.Error(err).IsNil()
+	for _, addr := range addrs {
+		str := addr.String()
+		ip := net.ParseIP(str)
+		if ip != nil && ip.To4() != nil {
+			localAddr = ip.To4()
+		}
+	}
+	assert.Pointer(localAddr).IsNotNil()
+
+	conn, err := Dial(v2net.IPAddress(localAddr), v2net.TCPDestination(v2net.LocalHostIP, dest.Port()))
 	assert.Error(err).IsNil()
 	assert.String(conn.RemoteAddr().String()).Equals("127.0.0.1:" + dest.Port().String())
 	conn.Close()

+ 1 - 1
transport/hub/udp_server.go

@@ -130,7 +130,7 @@ func (this *UDPServer) locateExistingAndDispatch(name string, payload *alloc.Buf
 }
 
 func (this *UDPServer) Dispatch(source v2net.Destination, destination v2net.Destination, payload *alloc.Buffer, callback UDPResponseCallback) {
-	destString := source.NetAddr() + "-" + destination.NetAddr()
+	destString := source.String() + "-" + destination.String()
 	log.Debug("UDP Server: Dispatch request: ", destString)
 	if this.locateExistingAndDispatch(destString, payload) {
 		return