Browse Source

split listening settings from inbound proxies and apply context

Darien Raymond 8 năm trước cách đây
mục cha
commit
ca721230e1
73 tập tin đã thay đổi với 2061 bổ sung2932 xóa
  1. 3 2
      app/dispatcher/dispatcher.go
  2. 11 9
      app/dispatcher/impl/default.go
  3. 7 10
      app/dns/server/nameserver.go
  4. 0 42
      app/proxy/config.pb.go
  5. 0 10
      app/proxy/config.proto
  6. 0 156
      app/proxy/proxy.go
  7. 0 74
      app/proxy/proxy_test.go
  8. 106 129
      app/proxyman/config.pb.go
  9. 8 16
      app/proxyman/config.proto
  10. 78 0
      app/proxyman/inbound/always.go
  11. 143 0
      app/proxyman/inbound/dynamic.go
  12. 94 0
      app/proxyman/inbound/inbound.go
  13. 269 0
      app/proxyman/inbound/worker.go
  14. 135 40
      app/proxyman/outbound/handler.go
  15. 18 15
      app/proxyman/outbound/outbound.go
  16. 13 6
      app/proxyman/proxyman.go
  17. 64 27
      app/router/condition.go
  18. 3 3
      app/router/config.go
  19. 13 22
      app/router/router.go
  20. 2 1
      app/router/router_test.go
  21. 4 0
      common/net/destination.go
  22. 23 0
      common/protocol/context.go
  23. 1 1
      common/type.go
  24. 0 55
      config.go
  25. 30 277
      config.pb.go
  26. 5 69
      config.proto
  27. 0 11
      inbound_detour.go
  28. 0 77
      inbound_detour_always.go
  29. 0 164
      inbound_detour_dynamic.go
  30. 1 1
      main/distro/all/all.go
  31. 5 5
      proxy/blackhole/blackhole.go
  32. 94 20
      proxy/context.go
  33. 21 144
      proxy/dokodemo/dokodemo.go
  34. 0 173
      proxy/dokodemo/dokodemo_test.go
  35. 11 11
      proxy/freedom/freedom.go
  36. 0 97
      proxy/freedom/freedom_test.go
  37. 23 66
      proxy/http/server.go
  38. 3 46
      proxy/proxy.go
  39. 26 23
      proxy/shadowsocks/client.go
  40. 59 91
      proxy/shadowsocks/server.go
  41. 12 13
      proxy/socks/client.go
  42. 2 3
      proxy/socks/protocol.go
  43. 75 87
      proxy/socks/server.go
  44. 0 66
      proxy/socks/server_udp.go
  45. 0 38
      proxy/vmess/inbound/command.go
  46. 49 69
      proxy/vmess/inbound/inbound.go
  47. 10 12
      proxy/vmess/outbound/outbound.go
  48. 33 24
      testing/scenarios/dokodemo_test.go
  49. 97 72
      testing/scenarios/feature_test.go
  50. 46 33
      testing/scenarios/socks_test.go
  51. 102 83
      testing/scenarios/tls_test.go
  52. 37 30
      testing/scenarios/transport_test.go
  53. 27 71
      tools/conf/router_test.go
  54. 80 61
      tools/conf/v2ray.go
  55. 18 0
      transport/internet/config.go
  56. 52 0
      transport/internet/context.go
  57. 20 19
      transport/internet/dialer.go
  58. 5 13
      transport/internet/kcp/dialer.go
  59. 3 11
      transport/internet/kcp/kcp_test.go
  60. 1 4
      transport/internet/kcp/listener.go
  61. 6 15
      transport/internet/tcp/dialer.go
  62. 1 0
      transport/internet/tcp/hub.go
  63. 1 1
      transport/internet/tcp/sockopt_linux.go
  64. 1 1
      transport/internet/tcp/sockopt_other.go
  65. 2 1
      transport/internet/tcp_hub.go
  66. 4 1
      transport/internet/udp/dialer.go
  67. 12 12
      transport/internet/udp/hub.go
  68. 27 121
      transport/internet/udp/udp_server.go
  69. 11 24
      transport/internet/websocket/dialer.go
  70. 16 57
      transport/internet/websocket/ws_test.go
  71. 20 4
      transport/ray/direct.go
  72. 4 2
      transport/ray/direct_test.go
  73. 14 91
      v2ray.go

+ 3 - 2
app/dispatcher/dispatcher.go

@@ -1,14 +1,15 @@
 package dispatcher
 
 import (
+	"context"
+
 	"v2ray.com/core/app"
-	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/ray"
 )
 
 // Interface dispatch a packet and possibly further network payload to its destination.
 type Interface interface {
-	DispatchToOutbound(session *proxy.SessionInfo) ray.InboundRay
+	DispatchToOutbound(ctx context.Context) ray.InboundRay
 }
 
 func FromSpace(space app.Space) Interface {

+ 11 - 9
app/dispatcher/impl/default.go

@@ -12,7 +12,6 @@ import (
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/ray"
 )
@@ -43,12 +42,15 @@ func (DefaultDispatcher) Interface() interface{} {
 	return (*dispatcher.Interface)(nil)
 }
 
-func (v *DefaultDispatcher) DispatchToOutbound(session *proxy.SessionInfo) ray.InboundRay {
+func (v *DefaultDispatcher) DispatchToOutbound(ctx context.Context) ray.InboundRay {
 	dispatcher := v.ohm.GetDefaultHandler()
-	destination := session.Destination
+	destination := proxy.DestinationFromContext(ctx)
+	if !destination.IsValid() {
+		panic("Dispatcher: Invalid destination.")
+	}
 
 	if v.router != nil {
-		if tag, err := v.router.TakeDetour(session); err == nil {
+		if tag, err := v.router.TakeDetour(ctx); err == nil {
 			if handler := v.ohm.GetHandler(tag); handler != nil {
 				log.Info("DefaultDispatcher: Taking detour [", tag, "] for [", destination, "].")
 				dispatcher = handler
@@ -60,9 +62,9 @@ func (v *DefaultDispatcher) DispatchToOutbound(session *proxy.SessionInfo) ray.I
 		}
 	}
 
-	direct := ray.NewRay()
+	direct := ray.NewRay(ctx)
 	var waitFunc func() error
-	if session.Inbound != nil && session.Inbound.AllowPassiveConnection {
+	if allowPassiveConnection, ok := proxy.AllowPassiveConnectionFromContext(ctx); ok && allowPassiveConnection {
 		waitFunc = noOpWait()
 	} else {
 		wdi := &waitDataInspector{
@@ -72,12 +74,12 @@ func (v *DefaultDispatcher) DispatchToOutbound(session *proxy.SessionInfo) ray.I
 		waitFunc = waitForData(wdi)
 	}
 
-	go v.waitAndDispatch(waitFunc, destination, direct, dispatcher)
+	go v.waitAndDispatch(ctx, waitFunc, direct, dispatcher)
 
 	return direct
 }
 
-func (v *DefaultDispatcher) waitAndDispatch(wait func() error, destination net.Destination, link ray.OutboundRay, dispatcher proxy.OutboundHandler) {
+func (v *DefaultDispatcher) waitAndDispatch(ctx context.Context, wait func() error, link ray.OutboundRay, dispatcher proxyman.OutboundHandler) {
 	if err := wait(); err != nil {
 		log.Info("DefaultDispatcher: Failed precondition: ", err)
 		link.OutboundInput().CloseError()
@@ -85,7 +87,7 @@ func (v *DefaultDispatcher) waitAndDispatch(wait func() error, destination net.D
 		return
 	}
 
-	dispatcher.Dispatch(destination, link)
+	dispatcher.Dispatch(ctx, link)
 }
 
 func init() {

+ 7 - 10
app/dns/server/nameserver.go

@@ -1,19 +1,18 @@
 package server
 
 import (
+	"context"
 	"net"
 	"sync"
 	"time"
 
+	"github.com/miekg/dns"
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet/udp"
-
-	"github.com/miekg/dns"
 )
 
 const (
@@ -101,7 +100,7 @@ func (v *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
 }
 
 // Private: Visible for testing.
-func (v *UDPNameServer) HandleResponse(dest v2net.Destination, payload *buf.Buffer) {
+func (v *UDPNameServer) HandleResponse(payload *buf.Buffer) {
 	msg := new(dns.Msg)
 	err := msg.Unpack(payload.Bytes())
 	if err != nil {
@@ -165,15 +164,12 @@ func (v *UDPNameServer) BuildQueryA(domain string, id uint16) *buf.Buffer {
 	return buffer
 }
 
-func (v *UDPNameServer) DispatchQuery(payload *buf.Buffer) {
-	v.udpServer.Dispatch(&proxy.SessionInfo{Source: pseudoDestination, Destination: v.address}, payload, v.HandleResponse)
-}
-
 func (v *UDPNameServer) QueryA(domain string) <-chan *ARecord {
 	response := make(chan *ARecord, 1)
 	id := v.AssignUnusedID(response)
 
-	v.DispatchQuery(v.BuildQueryA(domain, id))
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*8)
+	v.udpServer.Dispatch(ctx, v.address, v.BuildQueryA(domain, id), v.HandleResponse)
 
 	go func() {
 		for i := 0; i < 2; i++ {
@@ -182,11 +178,12 @@ func (v *UDPNameServer) QueryA(domain string) <-chan *ARecord {
 			_, found := v.requests[id]
 			v.Unlock()
 			if found {
-				v.DispatchQuery(v.BuildQueryA(domain, id))
+				v.udpServer.Dispatch(ctx, v.address, v.BuildQueryA(domain, id), v.HandleResponse)
 			} else {
 				break
 			}
 		}
+		cancel()
 	}()
 
 	return response

+ 0 - 42
app/proxy/config.pb.go

@@ -1,42 +0,0 @@
-package proxy
-
-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 {
-}
-
-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 init() {
-	proto.RegisterType((*Config)(nil), "v2ray.core.app.proxy.Config")
-}
-
-func init() { proto.RegisterFile("v2ray.com/core/app/proxy/config.proto", fileDescriptor0) }
-
-var fileDescriptor0 = []byte{
-	// 128 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x52, 0x2d, 0x33, 0x2a, 0x4a,
-	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x2f,
-	0x28, 0xca, 0xaf, 0xa8, 0xd4, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f,
-	0xc9, 0x17, 0x12, 0x81, 0x29, 0x2b, 0x4a, 0xd5, 0x4b, 0x2c, 0x28, 0xd0, 0x03, 0x2b, 0x51, 0xe2,
-	0xe0, 0x62, 0x73, 0x06, 0xab, 0x72, 0x72, 0xe5, 0x92, 0x48, 0xce, 0xcf, 0xd5, 0xc3, 0xa6, 0xca,
-	0x89, 0x1b, 0xa2, 0x26, 0x00, 0x64, 0x50, 0x14, 0x2b, 0x58, 0x6c, 0x15, 0x93, 0x48, 0x98, 0x51,
-	0x50, 0x62, 0xa5, 0x9e, 0x33, 0x48, 0xa9, 0x63, 0x41, 0x81, 0x5e, 0x00, 0x48, 0x38, 0x89, 0x0d,
-	0x6c, 0x9b, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x16, 0xbe, 0x54, 0x50, 0x96, 0x00, 0x00, 0x00,
-}

+ 0 - 10
app/proxy/config.proto

@@ -1,10 +0,0 @@
-syntax = "proto3";
-
-package v2ray.core.app.proxy;
-option csharp_namespace = "V2Ray.Core.App.Proxy";
-option go_package = "proxy";
-option java_package = "com.v2ray.core.app.proxy";
-option java_outer_classname = "ConfigProto";
-
-message Config {
-}

+ 0 - 156
app/proxy/proxy.go

@@ -1,156 +0,0 @@
-package proxy
-
-import (
-	"io"
-	"net"
-	"time"
-
-	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/proxyman"
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/buf"
-	"v2ray.com/core/common/errors"
-	"v2ray.com/core/common/log"
-	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/transport/internet"
-	"v2ray.com/core/transport/ray"
-)
-
-type OutboundProxy struct {
-	outboundManager proxyman.OutboundHandlerManager
-}
-
-func NewOutboundProxy(ctx context.Context, config *Config) (*OutboundProxy, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, errors.New("OutboundProxy: No space in context.")
-	}
-	proxy := new(OutboundProxy)
-	space.OnInitialize(func() error {
-		proxy.outboundManager = proxyman.OutboundHandlerManagerFromSpace(space)
-		if proxy.outboundManager == nil {
-			return errors.New("Proxy: Outbound handler manager not found in space.")
-		}
-		return nil
-	})
-	return proxy, nil
-}
-
-func (OutboundProxy) Interface() interface{} {
-	return (*OutboundProxy)(nil)
-}
-
-func (v *OutboundProxy) RegisterDialer() {
-	internet.ProxyDialer = v.Dial
-}
-
-// Dial implements internet.Dialer.
-func (v *OutboundProxy) Dial(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (internet.Connection, error) {
-	handler := v.outboundManager.GetHandler(options.Proxy.Tag)
-	if handler == nil {
-		log.Warning("Proxy: Failed to get outbound handler with tag: ", options.Proxy.Tag)
-		return internet.Dial(src, dest, internet.DialerOptions{
-			Stream: options.Stream,
-		})
-	}
-	log.Info("Proxy: Dialing to ", dest)
-	stream := ray.NewRay()
-	go handler.Dispatch(dest, stream)
-	return NewConnection(src, dest, stream), nil
-}
-
-type Connection struct {
-	stream     ray.Ray
-	closed     bool
-	localAddr  net.Addr
-	remoteAddr net.Addr
-
-	reader *buf.BufferToBytesReader
-	writer *buf.BytesToBufferWriter
-}
-
-func NewConnection(src v2net.Address, dest v2net.Destination, stream ray.Ray) *Connection {
-	return &Connection{
-		stream: stream,
-		localAddr: &net.TCPAddr{
-			IP:   []byte{0, 0, 0, 0},
-			Port: 0,
-		},
-		remoteAddr: &net.TCPAddr{
-			IP:   []byte{0, 0, 0, 0},
-			Port: 0,
-		},
-		reader: buf.NewBytesReader(stream.InboundOutput()),
-		writer: buf.NewBytesWriter(stream.InboundInput()),
-	}
-}
-
-// Read implements net.Conn.Read().
-func (v *Connection) Read(b []byte) (int, error) {
-	if v.closed {
-		return 0, io.EOF
-	}
-	return v.reader.Read(b)
-}
-
-// Write implements net.Conn.Write().
-func (v *Connection) Write(b []byte) (int, error) {
-	if v.closed {
-		return 0, io.ErrClosedPipe
-	}
-	return v.writer.Write(b)
-}
-
-// Close implements net.Conn.Close().
-func (v *Connection) Close() error {
-	v.closed = true
-	v.stream.InboundInput().Close()
-	v.stream.InboundOutput().CloseError()
-	return nil
-}
-
-// LocalAddr implements net.Conn.LocalAddr().
-func (v *Connection) LocalAddr() net.Addr {
-	return v.localAddr
-}
-
-// RemoteAddr implements net.Conn.RemoteAddr().
-func (v *Connection) RemoteAddr() net.Addr {
-	return v.remoteAddr
-}
-
-func (v *Connection) SetDeadline(t time.Time) error {
-	return nil
-}
-
-func (v *Connection) SetReadDeadline(t time.Time) error {
-	return nil
-}
-
-func (v *Connection) SetWriteDeadline(t time.Time) error {
-	return nil
-}
-
-func (v *Connection) Reusable() bool {
-	return false
-}
-
-func (v *Connection) SetReusable(bool) {
-
-}
-
-func OutboundProxyFromSpace(space app.Space) *OutboundProxy {
-	app := space.GetApplication((*OutboundProxy)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(*OutboundProxy)
-}
-
-func init() {
-	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
-		return NewOutboundProxy(ctx, config.(*Config))
-	}))
-}

+ 0 - 74
app/proxy/proxy_test.go

@@ -1,74 +0,0 @@
-package proxy_test
-
-import (
-	"context"
-	"testing"
-
-	"v2ray.com/core/app"
-	. "v2ray.com/core/app/proxy"
-	"v2ray.com/core/app/proxyman"
-	_ "v2ray.com/core/app/proxyman/outbound"
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
-	"v2ray.com/core/proxy/freedom"
-	"v2ray.com/core/testing/assert"
-	"v2ray.com/core/testing/servers/tcp"
-	"v2ray.com/core/transport/internet"
-	_ "v2ray.com/core/transport/internet/tcp"
-)
-
-func TestProxyDial(t *testing.T) {
-	assert := assert.On(t)
-
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	assert.Error(app.AddApplicationToSpace(ctx, new(proxyman.OutboundConfig))).IsNil()
-	outboundManager := proxyman.OutboundHandlerManagerFromSpace(space)
-	freedom, err := freedom.New(proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-		Tag: "tag",
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-	}), &freedom.Config{})
-	assert.Error(err).IsNil()
-	common.Must(outboundManager.SetHandler("tag", freedom))
-
-	assert.Error(app.AddApplicationToSpace(ctx, new(Config))).IsNil()
-	proxy := OutboundProxyFromSpace(space)
-	assert.Error(space.Initialize()).IsNil()
-
-	xor := func(b []byte) []byte {
-		for idx, x := range b {
-			b[idx] = x ^ 'c'
-		}
-		return b
-	}
-	tcpServer := &tcp.Server{
-		MsgProcessor: xor,
-	}
-	dest, err := tcpServer.Start()
-	assert.Error(err).IsNil()
-
-	conn, err := proxy.Dial(net.LocalHostIP, dest, internet.DialerOptions{
-		Stream: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-		Proxy: &internet.ProxyConfig{
-			Tag: "tag",
-		},
-	})
-	assert.Error(err).IsNil()
-
-	_, err = conn.Write([]byte{'a', 'b', 'c', 'd'})
-	assert.Error(err).IsNil()
-
-	b := make([]byte, 10)
-	nBytes, err := conn.Read(b)
-	assert.Error(err).IsNil()
-
-	assert.Bytes(xor(b[:nBytes])).Equals([]byte{'a', 'b', 'c', 'd'})
-
-	common.Must(conn.Close())
-	tcpServer.Close()
-}

+ 106 - 129
app/proxyman/config.pb.go

@@ -134,88 +134,72 @@ func (m *AllocationStrategy_AllocationStrategyRefresh) GetValue() uint32 {
 	return 0
 }
 
-type StreamReceiverConfig struct {
-	PortRange          *v2ray_core_common_net1.PortRange           `protobuf:"bytes,1,opt,name=port_range,json=portRange" json:"port_range,omitempty"`
-	Listen             *v2ray_core_common_net.IPOrDomain           `protobuf:"bytes,2,opt,name=listen" json:"listen,omitempty"`
-	AllocationStrategy *AllocationStrategy                         `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy" json:"allocation_strategy,omitempty"`
-	StreamSettings     *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,4,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
+type ReceiverConfig struct {
+	PortRange                  *v2ray_core_common_net1.PortRange           `protobuf:"bytes,1,opt,name=port_range,json=portRange" json:"port_range,omitempty"`
+	Listen                     *v2ray_core_common_net.IPOrDomain           `protobuf:"bytes,2,opt,name=listen" json:"listen,omitempty"`
+	AllocationStrategy         *AllocationStrategy                         `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy" json:"allocation_strategy,omitempty"`
+	StreamSettings             *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,4,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
+	ReceiveOriginalDestination bool                                        `protobuf:"varint,5,opt,name=receive_original_destination,json=receiveOriginalDestination" json:"receive_original_destination,omitempty"`
+	AllowPassiveConnection     bool                                        `protobuf:"varint,6,opt,name=allow_passive_connection,json=allowPassiveConnection" json:"allow_passive_connection,omitempty"`
 }
 
-func (m *StreamReceiverConfig) Reset()                    { *m = StreamReceiverConfig{} }
-func (m *StreamReceiverConfig) String() string            { return proto.CompactTextString(m) }
-func (*StreamReceiverConfig) ProtoMessage()               {}
-func (*StreamReceiverConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+func (m *ReceiverConfig) Reset()                    { *m = ReceiverConfig{} }
+func (m *ReceiverConfig) String() string            { return proto.CompactTextString(m) }
+func (*ReceiverConfig) ProtoMessage()               {}
+func (*ReceiverConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
 
-func (m *StreamReceiverConfig) GetPortRange() *v2ray_core_common_net1.PortRange {
+func (m *ReceiverConfig) GetPortRange() *v2ray_core_common_net1.PortRange {
 	if m != nil {
 		return m.PortRange
 	}
 	return nil
 }
 
-func (m *StreamReceiverConfig) GetListen() *v2ray_core_common_net.IPOrDomain {
+func (m *ReceiverConfig) GetListen() *v2ray_core_common_net.IPOrDomain {
 	if m != nil {
 		return m.Listen
 	}
 	return nil
 }
 
-func (m *StreamReceiverConfig) GetAllocationStrategy() *AllocationStrategy {
+func (m *ReceiverConfig) GetAllocationStrategy() *AllocationStrategy {
 	if m != nil {
 		return m.AllocationStrategy
 	}
 	return nil
 }
 
-func (m *StreamReceiverConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
+func (m *ReceiverConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
 	if m != nil {
 		return m.StreamSettings
 	}
 	return nil
 }
 
-type DatagramReceiverConfig struct {
-	PortRange          *v2ray_core_common_net1.PortRange `protobuf:"bytes,1,opt,name=port_range,json=portRange" json:"port_range,omitempty"`
-	Listen             *v2ray_core_common_net.IPOrDomain `protobuf:"bytes,2,opt,name=listen" json:"listen,omitempty"`
-	AllocationStrategy *AllocationStrategy               `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy" json:"allocation_strategy,omitempty"`
-}
-
-func (m *DatagramReceiverConfig) Reset()                    { *m = DatagramReceiverConfig{} }
-func (m *DatagramReceiverConfig) String() string            { return proto.CompactTextString(m) }
-func (*DatagramReceiverConfig) ProtoMessage()               {}
-func (*DatagramReceiverConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
-
-func (m *DatagramReceiverConfig) GetPortRange() *v2ray_core_common_net1.PortRange {
-	if m != nil {
-		return m.PortRange
-	}
-	return nil
-}
-
-func (m *DatagramReceiverConfig) GetListen() *v2ray_core_common_net.IPOrDomain {
+func (m *ReceiverConfig) GetReceiveOriginalDestination() bool {
 	if m != nil {
-		return m.Listen
+		return m.ReceiveOriginalDestination
 	}
-	return nil
+	return false
 }
 
-func (m *DatagramReceiverConfig) GetAllocationStrategy() *AllocationStrategy {
+func (m *ReceiverConfig) GetAllowPassiveConnection() bool {
 	if m != nil {
-		return m.AllocationStrategy
+		return m.AllowPassiveConnection
 	}
-	return nil
+	return false
 }
 
 type InboundHandlerConfig struct {
-	Tag              string                                   `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
-	ReceiverSettings []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,rep,name=receiver_settings,json=receiverSettings" json:"receiver_settings,omitempty"`
-	ProxySettings    *v2ray_core_common_serial.TypedMessage   `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
+	Tag              string                                 `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	ReceiverSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=receiver_settings,json=receiverSettings" json:"receiver_settings,omitempty"`
+	ProxySettings    *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
 }
 
 func (m *InboundHandlerConfig) Reset()                    { *m = InboundHandlerConfig{} }
 func (m *InboundHandlerConfig) String() string            { return proto.CompactTextString(m) }
 func (*InboundHandlerConfig) ProtoMessage()               {}
-func (*InboundHandlerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+func (*InboundHandlerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
 
 func (m *InboundHandlerConfig) GetTag() string {
 	if m != nil {
@@ -224,7 +208,7 @@ func (m *InboundHandlerConfig) GetTag() string {
 	return ""
 }
 
-func (m *InboundHandlerConfig) GetReceiverSettings() []*v2ray_core_common_serial.TypedMessage {
+func (m *InboundHandlerConfig) GetReceiverSettings() *v2ray_core_common_serial.TypedMessage {
 	if m != nil {
 		return m.ReceiverSettings
 	}
@@ -244,60 +228,35 @@ type OutboundConfig struct {
 func (m *OutboundConfig) Reset()                    { *m = OutboundConfig{} }
 func (m *OutboundConfig) String() string            { return proto.CompactTextString(m) }
 func (*OutboundConfig) ProtoMessage()               {}
-func (*OutboundConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+func (*OutboundConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
 
-type StreamSenderConfig struct {
+type SenderConfig struct {
 	// Send traffic through the given IP. Only IP is allowed.
 	Via            *v2ray_core_common_net.IPOrDomain           `protobuf:"bytes,1,opt,name=via" json:"via,omitempty"`
 	StreamSettings *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
 	ProxySettings  *v2ray_core_transport_internet.ProxyConfig  `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
 }
 
-func (m *StreamSenderConfig) Reset()                    { *m = StreamSenderConfig{} }
-func (m *StreamSenderConfig) String() string            { return proto.CompactTextString(m) }
-func (*StreamSenderConfig) ProtoMessage()               {}
-func (*StreamSenderConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
+func (m *SenderConfig) Reset()                    { *m = SenderConfig{} }
+func (m *SenderConfig) String() string            { return proto.CompactTextString(m) }
+func (*SenderConfig) ProtoMessage()               {}
+func (*SenderConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
 
-func (m *StreamSenderConfig) GetVia() *v2ray_core_common_net.IPOrDomain {
+func (m *SenderConfig) GetVia() *v2ray_core_common_net.IPOrDomain {
 	if m != nil {
 		return m.Via
 	}
 	return nil
 }
 
-func (m *StreamSenderConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
+func (m *SenderConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
 	if m != nil {
 		return m.StreamSettings
 	}
 	return nil
 }
 
-func (m *StreamSenderConfig) GetProxySettings() *v2ray_core_transport_internet.ProxyConfig {
-	if m != nil {
-		return m.ProxySettings
-	}
-	return nil
-}
-
-type DatagramSenderConfig struct {
-	// Send traffic through the given IP. Only IP is allowed.
-	Via           *v2ray_core_common_net.IPOrDomain          `protobuf:"bytes,1,opt,name=via" json:"via,omitempty"`
-	ProxySettings *v2ray_core_transport_internet.ProxyConfig `protobuf:"bytes,2,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
-}
-
-func (m *DatagramSenderConfig) Reset()                    { *m = DatagramSenderConfig{} }
-func (m *DatagramSenderConfig) String() string            { return proto.CompactTextString(m) }
-func (*DatagramSenderConfig) ProtoMessage()               {}
-func (*DatagramSenderConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
-
-func (m *DatagramSenderConfig) GetVia() *v2ray_core_common_net.IPOrDomain {
-	if m != nil {
-		return m.Via
-	}
-	return nil
-}
-
-func (m *DatagramSenderConfig) GetProxySettings() *v2ray_core_transport_internet.ProxyConfig {
+func (m *SenderConfig) GetProxySettings() *v2ray_core_transport_internet.ProxyConfig {
 	if m != nil {
 		return m.ProxySettings
 	}
@@ -305,15 +264,17 @@ func (m *DatagramSenderConfig) GetProxySettings() *v2ray_core_transport_internet
 }
 
 type OutboundHandlerConfig struct {
-	Tag            string                                   `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
-	SenderSettings []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,rep,name=sender_settings,json=senderSettings" json:"sender_settings,omitempty"`
-	ProxySettings  *v2ray_core_common_serial.TypedMessage   `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
+	Tag            string                                 `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	SenderSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=sender_settings,json=senderSettings" json:"sender_settings,omitempty"`
+	ProxySettings  *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
+	Expire         int64                                  `protobuf:"varint,4,opt,name=expire" json:"expire,omitempty"`
+	Comment        string                                 `protobuf:"bytes,5,opt,name=comment" json:"comment,omitempty"`
 }
 
 func (m *OutboundHandlerConfig) Reset()                    { *m = OutboundHandlerConfig{} }
 func (m *OutboundHandlerConfig) String() string            { return proto.CompactTextString(m) }
 func (*OutboundHandlerConfig) ProtoMessage()               {}
-func (*OutboundHandlerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
+func (*OutboundHandlerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
 
 func (m *OutboundHandlerConfig) GetTag() string {
 	if m != nil {
@@ -322,7 +283,7 @@ func (m *OutboundHandlerConfig) GetTag() string {
 	return ""
 }
 
-func (m *OutboundHandlerConfig) GetSenderSettings() []*v2ray_core_common_serial.TypedMessage {
+func (m *OutboundHandlerConfig) GetSenderSettings() *v2ray_core_common_serial.TypedMessage {
 	if m != nil {
 		return m.SenderSettings
 	}
@@ -336,17 +297,29 @@ func (m *OutboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.Typ
 	return nil
 }
 
+func (m *OutboundHandlerConfig) GetExpire() int64 {
+	if m != nil {
+		return m.Expire
+	}
+	return 0
+}
+
+func (m *OutboundHandlerConfig) GetComment() string {
+	if m != nil {
+		return m.Comment
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*InboundConfig)(nil), "v2ray.core.app.proxyman.InboundConfig")
 	proto.RegisterType((*AllocationStrategy)(nil), "v2ray.core.app.proxyman.AllocationStrategy")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyConcurrency)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyRefresh)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh")
-	proto.RegisterType((*StreamReceiverConfig)(nil), "v2ray.core.app.proxyman.StreamReceiverConfig")
-	proto.RegisterType((*DatagramReceiverConfig)(nil), "v2ray.core.app.proxyman.DatagramReceiverConfig")
+	proto.RegisterType((*ReceiverConfig)(nil), "v2ray.core.app.proxyman.ReceiverConfig")
 	proto.RegisterType((*InboundHandlerConfig)(nil), "v2ray.core.app.proxyman.InboundHandlerConfig")
 	proto.RegisterType((*OutboundConfig)(nil), "v2ray.core.app.proxyman.OutboundConfig")
-	proto.RegisterType((*StreamSenderConfig)(nil), "v2ray.core.app.proxyman.StreamSenderConfig")
-	proto.RegisterType((*DatagramSenderConfig)(nil), "v2ray.core.app.proxyman.DatagramSenderConfig")
+	proto.RegisterType((*SenderConfig)(nil), "v2ray.core.app.proxyman.SenderConfig")
 	proto.RegisterType((*OutboundHandlerConfig)(nil), "v2ray.core.app.proxyman.OutboundHandlerConfig")
 	proto.RegisterEnum("v2ray.core.app.proxyman.AllocationStrategy_Type", AllocationStrategy_Type_name, AllocationStrategy_Type_value)
 }
@@ -354,48 +327,52 @@ func init() {
 func init() { proto.RegisterFile("v2ray.com/core/app/proxyman/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 683 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe4, 0x55, 0xdd, 0x6e, 0xd3, 0x30,
-	0x18, 0x25, 0xe9, 0x18, 0xdb, 0x57, 0xd6, 0x15, 0x53, 0x58, 0x29, 0x42, 0x2a, 0x15, 0x82, 0x0a,
-	0x50, 0x32, 0x3a, 0x71, 0xc1, 0x15, 0xda, 0x9f, 0xc4, 0x2e, 0xc6, 0x8a, 0x3b, 0x71, 0x81, 0x90,
-	0x2a, 0x2f, 0xf1, 0x42, 0x44, 0x62, 0x47, 0xb6, 0x5b, 0x96, 0x97, 0xe1, 0x01, 0x78, 0x0a, 0xae,
-	0x90, 0x90, 0x78, 0x9a, 0x3d, 0x01, 0x72, 0x9c, 0x74, 0xd5, 0xda, 0xb2, 0x8d, 0x8a, 0x2b, 0xee,
-	0x5c, 0xf7, 0x3b, 0xc7, 0x3e, 0xe7, 0x7c, 0xfe, 0x02, 0xed, 0x61, 0x47, 0x90, 0xd4, 0xf1, 0x78,
-	0xec, 0x7a, 0x5c, 0x50, 0x97, 0x24, 0x89, 0x9b, 0x08, 0x7e, 0x92, 0xc6, 0x84, 0xb9, 0x1e, 0x67,
-	0xc7, 0x61, 0xe0, 0x24, 0x82, 0x2b, 0x8e, 0xd6, 0x8a, 0x4a, 0x41, 0x1d, 0x92, 0x24, 0x4e, 0x51,
-	0xd5, 0x58, 0x3f, 0x47, 0xe1, 0xf1, 0x38, 0xe6, 0xcc, 0x95, 0x54, 0x84, 0x24, 0x72, 0x55, 0x9a,
-	0x50, 0xbf, 0x1f, 0x53, 0x29, 0x49, 0x40, 0x0d, 0x55, 0xe3, 0xc9, 0x74, 0x04, 0xa3, 0xca, 0x25,
-	0xbe, 0x2f, 0xa8, 0x94, 0x79, 0xe1, 0xa3, 0xd9, 0x85, 0x09, 0x17, 0x2a, 0xaf, 0x72, 0xce, 0x55,
-	0x29, 0x41, 0x98, 0xd4, 0xff, 0xbb, 0x21, 0x53, 0x54, 0xe8, 0xea, 0x71, 0x25, 0xad, 0x55, 0x58,
-	0xd9, 0x63, 0x47, 0x7c, 0xc0, 0xfc, 0xed, 0x6c, 0xbb, 0xf5, 0xbd, 0x04, 0x68, 0x33, 0x8a, 0xb8,
-	0x47, 0x54, 0xc8, 0x59, 0x4f, 0x09, 0xa2, 0x68, 0x90, 0xa2, 0x1d, 0x58, 0xd0, 0xb7, 0xaf, 0x5b,
-	0x4d, 0xab, 0x5d, 0xe9, 0xac, 0x3b, 0x33, 0x0c, 0x70, 0x26, 0xa1, 0xce, 0x61, 0x9a, 0x50, 0x9c,
-	0xa1, 0xd1, 0x67, 0x28, 0x7b, 0x9c, 0x79, 0x03, 0x21, 0x28, 0xf3, 0xd2, 0xba, 0xdd, 0xb4, 0xda,
-	0xe5, 0xce, 0xde, 0x55, 0xc8, 0x26, 0xb7, 0xb6, 0xcf, 0x08, 0xf1, 0x38, 0x3b, 0xea, 0xc3, 0x0d,
-	0x41, 0x8f, 0x05, 0x95, 0x9f, 0xea, 0xa5, 0xec, 0xa0, 0xdd, 0xf9, 0x0e, 0xc2, 0x86, 0x0c, 0x17,
-	0xac, 0x8d, 0x97, 0xf0, 0xe0, 0x8f, 0xd7, 0x41, 0x35, 0xb8, 0x3e, 0x24, 0xd1, 0xc0, 0xb8, 0xb6,
-	0x82, 0xcd, 0x8f, 0xc6, 0x0b, 0xb8, 0x37, 0x93, 0x7c, 0x3a, 0xa4, 0xf5, 0x1c, 0x16, 0xb4, 0x8b,
-	0x08, 0x60, 0x71, 0x33, 0xfa, 0x42, 0x52, 0x59, 0xbd, 0xa6, 0xd7, 0x98, 0x30, 0x9f, 0xc7, 0x55,
-	0x0b, 0xdd, 0x84, 0xa5, 0xdd, 0x13, 0x1d, 0x2f, 0x89, 0xaa, 0x76, 0xeb, 0x87, 0x0d, 0xb5, 0x9e,
-	0x12, 0x94, 0xc4, 0x98, 0x7a, 0x34, 0x1c, 0x52, 0x61, 0xb2, 0x45, 0xaf, 0x01, 0x74, 0x2b, 0xf4,
-	0x05, 0x61, 0x81, 0x39, 0xa1, 0xdc, 0x69, 0x8e, 0x9b, 0x62, 0x7a, 0xca, 0x61, 0x54, 0x39, 0x5d,
-	0x2e, 0x14, 0xd6, 0x75, 0x78, 0x39, 0x29, 0x96, 0xe8, 0x15, 0x2c, 0x46, 0xa1, 0x54, 0x94, 0xe5,
-	0xd1, 0x3d, 0x9c, 0x01, 0xde, 0xeb, 0x1e, 0x88, 0x1d, 0x1e, 0x93, 0x90, 0xe1, 0x1c, 0x80, 0x3e,
-	0xc2, 0x6d, 0x32, 0x52, 0xdd, 0x97, 0xb9, 0xec, 0x3c, 0x99, 0x67, 0x57, 0x48, 0x06, 0x23, 0x32,
-	0xd9, 0x9e, 0x87, 0xb0, 0x2a, 0x33, 0xc5, 0x7d, 0x49, 0x95, 0x0a, 0x59, 0x20, 0xeb, 0x0b, 0x93,
-	0xcc, 0xa3, 0xc7, 0xe0, 0x14, 0x8f, 0xc1, 0x31, 0x3e, 0x19, 0x7f, 0x70, 0xc5, 0x70, 0xf4, 0x72,
-	0x8a, 0xd6, 0xa9, 0x05, 0x77, 0x77, 0x88, 0x22, 0x81, 0xf8, 0x7f, 0xac, 0x6c, 0xfd, 0xb2, 0xa0,
-	0x96, 0x8f, 0x84, 0x37, 0x84, 0xf9, 0xd1, 0x48, 0x72, 0x15, 0x4a, 0x8a, 0x04, 0x99, 0xd6, 0x65,
-	0xac, 0x97, 0xa8, 0x07, 0xb7, 0x44, 0x6e, 0xcb, 0x99, 0xef, 0x76, 0xb3, 0xd4, 0x2e, 0x77, 0x1e,
-	0x4f, 0x91, 0x63, 0xa6, 0x60, 0x36, 0x0f, 0xfc, 0x7d, 0x33, 0x04, 0x71, 0xb5, 0x20, 0x28, 0x4c,
-	0x47, 0xfb, 0x50, 0xc9, 0xae, 0x7c, 0xc6, 0x68, 0x84, 0x5d, 0x96, 0x71, 0x25, 0x43, 0x8f, 0x32,
-	0xac, 0x42, 0xe5, 0x60, 0xa0, 0xc6, 0x27, 0xdc, 0xa9, 0x05, 0xa8, 0x97, 0x07, 0xcd, 0xfc, 0x91,
-	0xbc, 0x0d, 0x28, 0x0d, 0x43, 0x92, 0x47, 0x79, 0x89, 0x34, 0x74, 0xf5, 0xb4, 0xbe, 0xb3, 0xe7,
-	0xee, 0x3b, 0xf4, 0x6e, 0x86, 0x05, 0x4f, 0x2f, 0x20, 0xed, 0x6a, 0x50, 0xce, 0x79, 0xce, 0x86,
-	0xaf, 0x16, 0xd4, 0x8a, 0x56, 0x9e, 0x5f, 0xf6, 0xe4, 0x05, 0xed, 0x79, 0x2f, 0xf8, 0xd3, 0x82,
-	0x3b, 0x45, 0x50, 0x17, 0xf5, 0xdd, 0x01, 0xac, 0xca, 0x4c, 0xc3, 0xdf, 0x76, 0x5d, 0xc5, 0xc0,
-	0xff, 0x51, 0xcf, 0x6d, 0xbd, 0x85, 0xfb, 0x1e, 0x8f, 0x67, 0x3d, 0xc4, 0xad, 0xb2, 0x11, 0xd6,
-	0xd5, 0x1f, 0xe0, 0x0f, 0x4b, 0xc5, 0xf6, 0x37, 0x7b, 0xed, 0x7d, 0x07, 0x93, 0xd4, 0xd9, 0xd6,
-	0x80, 0xcd, 0x24, 0x31, 0x6e, 0xc5, 0x84, 0x1d, 0x2d, 0x66, 0xdf, 0xea, 0x8d, 0xdf, 0x01, 0x00,
-	0x00, 0xff, 0xff, 0x18, 0xce, 0x1c, 0x24, 0xa1, 0x08, 0x00, 0x00,
+	// 737 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x55, 0xd1, 0x6e, 0xdb, 0x36,
+	0x14, 0x9d, 0xec, 0xc4, 0x49, 0xae, 0x13, 0xc7, 0xe3, 0xb2, 0xc4, 0xf3, 0x36, 0xc0, 0x33, 0x86,
+	0xcd, 0xd8, 0x06, 0x29, 0x73, 0x30, 0x60, 0x7b, 0xda, 0x12, 0x27, 0xc0, 0xf2, 0x90, 0xd9, 0xa3,
+	0x83, 0x3e, 0x14, 0x05, 0x04, 0x46, 0x62, 0x5c, 0xa1, 0x12, 0x29, 0x90, 0xb4, 0x13, 0xfd, 0x52,
+	0xbf, 0xa1, 0x0f, 0xfd, 0x80, 0x7e, 0x4a, 0xff, 0xa0, 0x2f, 0x05, 0x49, 0xc9, 0x36, 0x62, 0xbb,
+	0x69, 0x1a, 0xf4, 0x4d, 0x24, 0xcf, 0x39, 0xe4, 0x3d, 0x87, 0x97, 0x82, 0xce, 0xa4, 0x2b, 0x48,
+	0xe6, 0x06, 0x3c, 0xf1, 0x02, 0x2e, 0xa8, 0x47, 0xd2, 0xd4, 0x4b, 0x05, 0xbf, 0xcd, 0x12, 0xc2,
+	0xbc, 0x80, 0xb3, 0xeb, 0x68, 0xe4, 0xa6, 0x82, 0x2b, 0x8e, 0x0e, 0x0a, 0xa4, 0xa0, 0x2e, 0x49,
+	0x53, 0xb7, 0x40, 0x35, 0x0f, 0xef, 0x48, 0x04, 0x3c, 0x49, 0x38, 0xf3, 0x24, 0x15, 0x11, 0x89,
+	0x3d, 0x95, 0xa5, 0x34, 0xf4, 0x13, 0x2a, 0x25, 0x19, 0x51, 0x2b, 0xd5, 0xfc, 0x79, 0x39, 0x83,
+	0x51, 0xe5, 0x91, 0x30, 0x14, 0x54, 0xca, 0x1c, 0xf8, 0xe3, 0x6a, 0x60, 0xca, 0x85, 0xca, 0x51,
+	0xee, 0x1d, 0x94, 0x12, 0x84, 0x49, 0xbd, 0xee, 0x45, 0x4c, 0x51, 0xa1, 0xd1, 0xf3, 0x95, 0xb4,
+	0x77, 0x61, 0xe7, 0x9c, 0x5d, 0xf1, 0x31, 0x0b, 0x7b, 0x66, 0xba, 0xfd, 0xba, 0x0c, 0xe8, 0x38,
+	0x8e, 0x79, 0x40, 0x54, 0xc4, 0xd9, 0x50, 0x09, 0xa2, 0xe8, 0x28, 0x43, 0xa7, 0xb0, 0xa6, 0x4f,
+	0xdf, 0x70, 0x5a, 0x4e, 0xa7, 0xd6, 0x3d, 0x74, 0x57, 0x18, 0xe0, 0x2e, 0x52, 0xdd, 0xcb, 0x2c,
+	0xa5, 0xd8, 0xb0, 0xd1, 0x0b, 0xa8, 0x06, 0x9c, 0x05, 0x63, 0x21, 0x28, 0x0b, 0xb2, 0x46, 0xa9,
+	0xe5, 0x74, 0xaa, 0xdd, 0xf3, 0x87, 0x88, 0x2d, 0x4e, 0xf5, 0x66, 0x82, 0x78, 0x5e, 0x1d, 0xf9,
+	0xb0, 0x21, 0xe8, 0xb5, 0xa0, 0xf2, 0x79, 0xa3, 0x6c, 0x36, 0x3a, 0x7b, 0xdc, 0x46, 0xd8, 0x8a,
+	0xe1, 0x42, 0xb5, 0xf9, 0x07, 0x7c, 0xff, 0xc1, 0xe3, 0xa0, 0x3d, 0x58, 0x9f, 0x90, 0x78, 0x6c,
+	0x5d, 0xdb, 0xc1, 0x76, 0xd0, 0xfc, 0x1d, 0xbe, 0x59, 0x29, 0xbe, 0x9c, 0xd2, 0xfe, 0x0d, 0xd6,
+	0xb4, 0x8b, 0x08, 0xa0, 0x72, 0x1c, 0xdf, 0x90, 0x4c, 0xd6, 0xbf, 0xd0, 0xdf, 0x98, 0xb0, 0x90,
+	0x27, 0x75, 0x07, 0x6d, 0xc3, 0xe6, 0xd9, 0xad, 0x8e, 0x97, 0xc4, 0xf5, 0x52, 0xfb, 0x55, 0x19,
+	0x6a, 0x98, 0x06, 0x34, 0x9a, 0x50, 0x61, 0x53, 0x45, 0x7f, 0x03, 0xe8, 0x4b, 0xe0, 0x0b, 0xc2,
+	0x46, 0x56, 0xbb, 0xda, 0x6d, 0xcd, 0xdb, 0x61, 0x6f, 0x93, 0xcb, 0xa8, 0x72, 0x07, 0x5c, 0x28,
+	0xac, 0x71, 0x78, 0x2b, 0x2d, 0x3e, 0xd1, 0x5f, 0x50, 0x89, 0x23, 0xa9, 0x28, 0xcb, 0x43, 0xfb,
+	0x61, 0x05, 0xf9, 0x7c, 0xd0, 0x17, 0xa7, 0x3c, 0x21, 0x11, 0xc3, 0x39, 0x01, 0x3d, 0x83, 0xaf,
+	0xc8, 0xb4, 0x5e, 0x5f, 0xe6, 0x05, 0xe7, 0x99, 0xfc, 0xfa, 0x80, 0x4c, 0x30, 0x22, 0x8b, 0x17,
+	0xf3, 0x12, 0x76, 0xa5, 0x12, 0x94, 0x24, 0xbe, 0xa4, 0x4a, 0x45, 0x6c, 0x24, 0x1b, 0x6b, 0x8b,
+	0xca, 0xd3, 0x36, 0x70, 0x8b, 0x36, 0x70, 0x87, 0x86, 0x65, 0xfd, 0xc1, 0x35, 0xab, 0x31, 0xcc,
+	0x25, 0xd0, 0x3f, 0xf0, 0x9d, 0xb0, 0x0e, 0xfa, 0x5c, 0x44, 0xa3, 0x88, 0x91, 0xd8, 0x0f, 0xa9,
+	0x54, 0x11, 0x33, 0xbb, 0x37, 0xd6, 0x5b, 0x4e, 0x67, 0x13, 0x37, 0x73, 0x4c, 0x3f, 0x87, 0x9c,
+	0xce, 0x10, 0xe8, 0x4f, 0x68, 0xe8, 0xd3, 0xde, 0xf8, 0x29, 0x91, 0x52, 0xeb, 0x04, 0x9c, 0x31,
+	0x1a, 0x18, 0x76, 0xc5, 0xb0, 0xf7, 0xcd, 0xfa, 0xc0, 0x2e, 0xf7, 0xa6, 0xab, 0xed, 0x37, 0x0e,
+	0xec, 0xe5, 0x3d, 0xf9, 0x2f, 0x61, 0x61, 0x3c, 0x0d, 0xb1, 0x0e, 0x65, 0x45, 0x46, 0x26, 0xbd,
+	0x2d, 0xac, 0x3f, 0xd1, 0x10, 0xbe, 0xcc, 0x8f, 0x20, 0x66, 0xe5, 0xdb, 0x80, 0x7e, 0x5a, 0x12,
+	0x90, 0x7d, 0x86, 0x4c, 0x43, 0x86, 0x17, 0xf6, 0x15, 0xc2, 0xf5, 0x42, 0x60, 0x5a, 0xfb, 0x05,
+	0xd4, 0x4c, 0x08, 0x33, 0xc5, 0xf2, 0x83, 0x14, 0x77, 0x0c, 0xbb, 0x90, 0x6b, 0xd7, 0xa1, 0xd6,
+	0x1f, 0xab, 0xf9, 0x27, 0xe6, 0xad, 0x03, 0xdb, 0x43, 0xca, 0xc2, 0x69, 0x61, 0x47, 0x50, 0x9e,
+	0x44, 0x24, 0xbf, 0x96, 0x1f, 0x71, 0xb3, 0x34, 0x7a, 0x59, 0xf0, 0xa5, 0xc7, 0x07, 0xff, 0xff,
+	0x8a, 0xe2, 0x7f, 0xb9, 0x47, 0x74, 0xa0, 0x49, 0xb9, 0xe6, 0x1d, 0x03, 0xde, 0x39, 0xf0, 0x75,
+	0xe1, 0xc0, 0x7d, 0x81, 0xf6, 0x61, 0x57, 0x1a, 0x67, 0x3e, 0x35, 0xce, 0x9a, 0xa5, 0x7f, 0xa6,
+	0x30, 0xd1, 0x3e, 0x54, 0xe8, 0x6d, 0x1a, 0x09, 0x6a, 0x9a, 0xac, 0x8c, 0xf3, 0x11, 0x6a, 0xc0,
+	0x86, 0x16, 0xa1, 0x4c, 0x99, 0xd6, 0xd8, 0xc2, 0xc5, 0xf0, 0xe4, 0x3f, 0xf8, 0x36, 0xe0, 0xc9,
+	0xaa, 0x2e, 0x3f, 0xa9, 0x5a, 0x2b, 0x06, 0xfa, 0x67, 0xf4, 0x74, 0xb3, 0x98, 0x7e, 0x59, 0x3a,
+	0x78, 0xd2, 0xc5, 0x24, 0x73, 0x7b, 0x9a, 0x70, 0x9c, 0xa6, 0xd6, 0xdf, 0x84, 0xb0, 0xab, 0x8a,
+	0xf9, 0x6f, 0x1d, 0xbd, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x26, 0xa3, 0xe8, 0x2c, 0xad, 0x07, 0x00,
+	0x00,
 }

+ 8 - 16
app/proxyman/config.proto

@@ -46,22 +46,18 @@ message AllocationStrategy {
   AllocationStrategyRefresh refresh = 3;
 }
 
-message StreamReceiverConfig {
+message ReceiverConfig {
   v2ray.core.common.net.PortRange port_range = 1;
   v2ray.core.common.net.IPOrDomain listen = 2;
   AllocationStrategy allocation_strategy = 3;
   v2ray.core.transport.internet.StreamConfig stream_settings = 4;
-}
-
-message DatagramReceiverConfig {
-  v2ray.core.common.net.PortRange port_range = 1;
-  v2ray.core.common.net.IPOrDomain listen = 2;
-  AllocationStrategy allocation_strategy = 3;
+  bool receive_original_destination = 5;
+  bool allow_passive_connection = 6;
 }
 
 message InboundHandlerConfig {
   string tag = 1;
-  repeated v2ray.core.common.serial.TypedMessage receiver_settings = 2;
+  v2ray.core.common.serial.TypedMessage receiver_settings = 2;
   v2ray.core.common.serial.TypedMessage proxy_settings = 3;
 }
 
@@ -69,21 +65,17 @@ message OutboundConfig {
   
 }
 
-message StreamSenderConfig {
+message SenderConfig {
   // Send traffic through the given IP. Only IP is allowed.
   v2ray.core.common.net.IPOrDomain via = 1;
   v2ray.core.transport.internet.StreamConfig stream_settings = 2;
   v2ray.core.transport.internet.ProxyConfig proxy_settings = 3;
 }
 
-message DatagramSenderConfig {
-  // Send traffic through the given IP. Only IP is allowed.
-  v2ray.core.common.net.IPOrDomain via = 1;
-  v2ray.core.transport.internet.ProxyConfig proxy_settings = 2;
-}
-
 message OutboundHandlerConfig {
   string tag = 1;
-  repeated v2ray.core.common.serial.TypedMessage sender_settings = 2;
+  v2ray.core.common.serial.TypedMessage sender_settings = 2;
   v2ray.core.common.serial.TypedMessage proxy_settings = 3;
+  int64 expire = 4;
+  string comment = 5;
 }

+ 78 - 0
app/proxyman/inbound/always.go

@@ -0,0 +1,78 @@
+package inbound
+
+import (
+	"context"
+
+	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/common/dice"
+	"v2ray.com/core/common/log"
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/proxy"
+)
+
+type AlwaysOnInboundHandler struct {
+	proxy   proxy.InboundHandler
+	workers []worker
+}
+
+func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
+	p, err := proxy.CreateInboundHandler(ctx, proxyConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	h := &AlwaysOnInboundHandler{
+		proxy: p,
+	}
+
+	nl := p.Network()
+	pr := receiverConfig.PortRange
+	for port := pr.From; port <= pr.To; port++ {
+		if nl.HasNetwork(net.Network_TCP) {
+			log.Debug("Proxyman|DefaultInboundHandler: creating tcp worker on ", receiverConfig.Listen.AsAddress(), ":", port)
+			worker := &tcpWorker{
+				address:          receiverConfig.Listen.AsAddress(),
+				port:             net.Port(port),
+				proxy:            p,
+				stream:           receiverConfig.StreamSettings,
+				recvOrigDest:     receiverConfig.ReceiveOriginalDestination,
+				tag:              tag,
+				allowPassiveConn: receiverConfig.AllowPassiveConnection,
+			}
+			h.workers = append(h.workers, worker)
+		}
+
+		if nl.HasNetwork(net.Network_UDP) {
+			worker := &udpWorker{
+				tag:          tag,
+				proxy:        p,
+				address:      receiverConfig.Listen.AsAddress(),
+				port:         net.Port(port),
+				recvOrigDest: receiverConfig.ReceiveOriginalDestination,
+			}
+			h.workers = append(h.workers, worker)
+		}
+	}
+
+	return h, nil
+}
+
+func (h *AlwaysOnInboundHandler) Start() error {
+	for _, worker := range h.workers {
+		if err := worker.Start(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (h *AlwaysOnInboundHandler) Close() {
+	for _, worker := range h.workers {
+		worker.Close()
+	}
+}
+
+func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (proxy.InboundHandler, net.Port, int) {
+	w := h.workers[dice.Roll(len(h.workers))]
+	return w.Proxy(), w.Port(), 9999
+}

+ 143 - 0
app/proxyman/inbound/dynamic.go

@@ -0,0 +1,143 @@
+package inbound
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/common/dice"
+	"v2ray.com/core/common/log"
+	v2net "v2ray.com/core/common/net"
+	"v2ray.com/core/proxy"
+)
+
+type DynamicInboundHandler struct {
+	sync.Mutex
+	tag            string
+	ctx            context.Context
+	cancel         context.CancelFunc
+	proxyConfig    interface{}
+	receiverConfig *proxyman.ReceiverConfig
+	portsInUse     map[v2net.Port]bool
+	worker         []worker
+	worker2Recycle []worker
+	lastRefresh    time.Time
+}
+
+func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
+	ctx, cancel := context.WithCancel(ctx)
+	h := &DynamicInboundHandler{
+		ctx:            ctx,
+		tag:            tag,
+		cancel:         cancel,
+		proxyConfig:    proxyConfig,
+		receiverConfig: receiverConfig,
+		portsInUse:     make(map[v2net.Port]bool),
+	}
+
+	return h, nil
+}
+
+func (h *DynamicInboundHandler) allocatePort() v2net.Port {
+	from := int(h.receiverConfig.PortRange.From)
+	delta := int(h.receiverConfig.PortRange.To) - from + 1
+	h.Lock()
+	defer h.Unlock()
+
+	for {
+		r := dice.Roll(delta)
+		port := v2net.Port(from + r)
+		_, used := h.portsInUse[port]
+		if !used {
+			h.portsInUse[port] = true
+			return port
+		}
+	}
+}
+
+func (h *DynamicInboundHandler) refresh() error {
+	h.lastRefresh = time.Now()
+
+	ports2Del := make([]v2net.Port, 0, 16)
+	for _, worker := range h.worker2Recycle {
+		worker.Close()
+		ports2Del = append(ports2Del, worker.Port())
+	}
+
+	h.Lock()
+	for _, port := range ports2Del {
+		delete(h.portsInUse, port)
+	}
+	h.Unlock()
+
+	h.worker2Recycle, h.worker = h.worker, h.worker2Recycle[:0]
+
+	for i := uint32(0); i < h.receiverConfig.AllocationStrategy.GetConcurrencyValue(); i++ {
+		port := h.allocatePort()
+		p, err := proxy.CreateInboundHandler(h.ctx, h.proxyConfig)
+		if err != nil {
+			log.Warning("Proxyman|DefaultInboundHandler: Failed to create proxy instance: ", err)
+			continue
+		}
+		nl := p.Network()
+		if nl.HasNetwork(v2net.Network_TCP) {
+			worker := &tcpWorker{
+				tag:              h.tag,
+				address:          h.receiverConfig.Listen.AsAddress(),
+				port:             port,
+				proxy:            p,
+				stream:           h.receiverConfig.StreamSettings,
+				recvOrigDest:     h.receiverConfig.ReceiveOriginalDestination,
+				allowPassiveConn: h.receiverConfig.AllowPassiveConnection,
+			}
+			if err := worker.Start(); err != nil {
+				return err
+			}
+			h.worker = append(h.worker, worker)
+		}
+
+		if nl.HasNetwork(v2net.Network_UDP) {
+			worker := &udpWorker{
+				tag:          h.tag,
+				proxy:        p,
+				address:      h.receiverConfig.Listen.AsAddress(),
+				port:         port,
+				recvOrigDest: h.receiverConfig.ReceiveOriginalDestination,
+			}
+			if err := worker.Start(); err != nil {
+				return err
+			}
+			h.worker = append(h.worker, worker)
+		}
+	}
+
+	return nil
+}
+
+func (h *DynamicInboundHandler) monitor() {
+	for {
+		select {
+		case <-h.ctx.Done():
+			return
+		case <-time.After(time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue())):
+			h.refresh()
+		}
+	}
+}
+
+func (h *DynamicInboundHandler) Start() error {
+	err := h.refresh()
+	go h.monitor()
+	return err
+}
+
+func (h *DynamicInboundHandler) Close() {
+	h.cancel()
+}
+
+func (h *DynamicInboundHandler) GetRandomInboundProxy() (proxy.InboundHandler, v2net.Port, int) {
+	w := h.worker[dice.Roll(len(h.worker))]
+	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
+	return w.Proxy(), w.Port(), int(expire)
+}

+ 94 - 0
app/proxyman/inbound/inbound.go

@@ -0,0 +1,94 @@
+package inbound
+
+import (
+	"context"
+
+	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/errors"
+)
+
+type DefaultInboundHandlerManager struct {
+	handlers       []proxyman.InboundHandler
+	taggedHandlers map[string]proxyman.InboundHandler
+}
+
+func New(ctx context.Context, config *proxyman.InboundConfig) (*DefaultInboundHandlerManager, error) {
+	return &DefaultInboundHandlerManager{
+		taggedHandlers: make(map[string]proxyman.InboundHandler),
+	}, nil
+}
+
+func (m *DefaultInboundHandlerManager) AddHandler(ctx context.Context, config *proxyman.InboundHandlerConfig) error {
+	rawReceiverSettings, err := config.ReceiverSettings.GetInstance()
+	if err != nil {
+		return err
+	}
+	receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
+	if !ok {
+		return errors.New("Proxyman|DefaultInboundHandlerManager: Not a ReceiverConfig.")
+	}
+	proxySettings, err := config.ProxySettings.GetInstance()
+	if err != nil {
+		return err
+	}
+	var handler proxyman.InboundHandler
+	tag := config.Tag
+	allocStrategy := receiverSettings.AllocationStrategy
+	if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
+		h, err := NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
+		if err != nil {
+			return err
+		}
+		handler = h
+	} else if allocStrategy.Type == proxyman.AllocationStrategy_Random {
+		h, err := NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
+		if err != nil {
+			return err
+		}
+		handler = h
+	}
+
+	if handler == nil {
+		return errors.New("Proxyman|DefaultInboundHandlerManager: Unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type)
+	}
+
+	m.handlers = append(m.handlers, handler)
+	if len(tag) > 0 {
+		m.taggedHandlers[tag] = handler
+	}
+	return nil
+}
+
+func (m *DefaultInboundHandlerManager) GetHandler(ctx context.Context, tag string) (proxyman.InboundHandler, error) {
+	handler, found := m.taggedHandlers[tag]
+	if !found {
+		return nil, errors.New("Proxymand|DefaultInboundHandlerManager: Handler not found: ", tag)
+	}
+	return handler, nil
+}
+
+func (m *DefaultInboundHandlerManager) Start() error {
+	for _, handler := range m.handlers {
+		if err := handler.Start(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (m *DefaultInboundHandlerManager) Close() {
+	for _, handler := range m.handlers {
+		handler.Close()
+	}
+}
+
+func (m *DefaultInboundHandlerManager) Interface() interface{} {
+	return (*proxyman.InboundHandlerManager)(nil)
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return New(ctx, config.(*proxyman.InboundConfig))
+	}))
+}

+ 269 - 0
app/proxyman/inbound/worker.go

@@ -0,0 +1,269 @@
+package inbound
+
+import (
+	"context"
+	"io"
+	"net"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/log"
+	v2net "v2ray.com/core/common/net"
+	"v2ray.com/core/proxy"
+	"v2ray.com/core/transport/internet"
+	"v2ray.com/core/transport/internet/tcp"
+	"v2ray.com/core/transport/internet/udp"
+)
+
+type worker interface {
+	Start() error
+	Close()
+	Port() v2net.Port
+	Proxy() proxy.InboundHandler
+}
+
+type tcpWorker struct {
+	address          v2net.Address
+	port             v2net.Port
+	proxy            proxy.InboundHandler
+	stream           *internet.StreamConfig
+	recvOrigDest     bool
+	tag              string
+	allowPassiveConn bool
+
+	ctx    context.Context
+	cancel context.CancelFunc
+	hub    *internet.TCPHub
+}
+
+func (w *tcpWorker) callback(conn internet.Connection) {
+	ctx, cancel := context.WithCancel(w.ctx)
+	if w.recvOrigDest {
+		dest := tcp.GetOriginalDestination(conn)
+		if dest.IsValid() {
+			ctx = proxy.ContextWithOriginalDestination(ctx, dest)
+		}
+	}
+	if len(w.tag) > 0 {
+		ctx = proxy.ContextWithInboundTag(ctx, w.tag)
+	}
+	ctx = proxy.ContextWithAllowPassiveConnection(ctx, w.allowPassiveConn)
+	ctx = proxy.ContextWithInboundDestination(ctx, v2net.TCPDestination(w.address, w.port))
+	w.proxy.Process(ctx, v2net.Network_TCP, conn)
+	cancel()
+	conn.Close()
+}
+
+func (w *tcpWorker) Proxy() proxy.InboundHandler {
+	return w.proxy
+}
+
+func (w *tcpWorker) Start() error {
+	ctx, cancel := context.WithCancel(context.Background())
+	w.ctx = ctx
+	w.cancel = cancel
+	hub, err := internet.ListenTCP(w.address, w.port, w.callback, w.stream)
+	if err != nil {
+		return err
+	}
+	w.hub = hub
+	return nil
+}
+
+func (w *tcpWorker) Close() {
+	log.Debug("Proxyman|TCPWorker: Closed. ", w.port)
+	w.hub.Close()
+	w.cancel()
+}
+
+func (w *tcpWorker) Port() v2net.Port {
+	return w.port
+}
+
+type udpConn struct {
+	cancel           context.CancelFunc
+	lastActivityTime int64 // in seconds
+	input            chan []byte
+	output           func([]byte) (int, error)
+	closer           func() error
+	remote           net.Addr
+	local            net.Addr
+}
+
+func (c *udpConn) updateActivity() {
+	atomic.StoreInt64(&c.lastActivityTime, time.Now().Unix())
+}
+
+func (c *udpConn) Read(buf []byte) (int, error) {
+	in, open := <-c.input
+	if !open {
+		return 0, io.EOF
+	}
+	c.updateActivity()
+	return copy(buf, in), nil
+}
+
+func (c *udpConn) Write(buf []byte) (int, error) {
+	n, err := c.output(buf)
+	if err == nil {
+		c.updateActivity()
+	}
+	return n, err
+}
+
+func (c *udpConn) Close() error {
+	close(c.input)
+	c.cancel()
+	return nil
+}
+
+func (c *udpConn) RemoteAddr() net.Addr {
+	return c.remote
+}
+
+func (c *udpConn) LocalAddr() net.Addr {
+	return c.remote
+}
+
+func (*udpConn) SetDeadline(time.Time) error {
+	return nil
+}
+
+func (*udpConn) SetReadDeadline(time.Time) error {
+	return nil
+}
+
+func (*udpConn) SetWriteDeadline(time.Time) error {
+	return nil
+}
+
+func (*udpConn) Reusable() bool {
+	return false
+}
+
+func (*udpConn) SetReusable(bool) {}
+
+type udpWorker struct {
+	sync.RWMutex
+
+	proxy        proxy.InboundHandler
+	hub          *udp.Hub
+	address      v2net.Address
+	port         v2net.Port
+	recvOrigDest bool
+	tag          string
+
+	ctx        context.Context
+	cancel     context.CancelFunc
+	activeConn map[v2net.Destination]*udpConn
+}
+
+func (w *udpWorker) getConnection(src v2net.Destination) (*udpConn, bool) {
+	w.Lock()
+	defer w.Unlock()
+
+	if conn, found := w.activeConn[src]; found {
+		return conn, true
+	}
+
+	conn := &udpConn{
+		input: make(chan []byte, 32),
+		output: func(b []byte) (int, error) {
+			return w.hub.WriteTo(b, src)
+		},
+		closer: func() error {
+			w.Lock()
+			delete(w.activeConn, src)
+			w.Unlock()
+			return nil
+		},
+		remote: &net.UDPAddr{
+			IP:   src.Address.IP(),
+			Port: int(src.Port),
+		},
+		local: &net.UDPAddr{
+			IP:   w.address.IP(),
+			Port: int(w.port),
+		},
+	}
+
+	conn.updateActivity()
+	return conn, false
+}
+
+func (w *udpWorker) callback(b *buf.Buffer, source v2net.Destination, originalDest v2net.Destination) {
+	conn, existing := w.getConnection(source)
+	conn.input <- b.Bytes()
+
+	if !existing {
+		go func() {
+			ctx := w.ctx
+			ctx, cancel := context.WithCancel(ctx)
+			conn.cancel = cancel
+			if originalDest.IsValid() {
+				ctx = proxy.ContextWithOriginalDestination(ctx, originalDest)
+			}
+			if len(w.tag) > 0 {
+				ctx = proxy.ContextWithInboundTag(ctx, w.tag)
+			}
+			ctx = proxy.ContextWithSource(ctx, source)
+			ctx = proxy.ContextWithInboundDestination(ctx, v2net.UDPDestination(w.address, w.port))
+			w.proxy.Process(ctx, v2net.Network_UDP, conn)
+			conn.cancel()
+		}()
+	}
+}
+
+func (w *udpWorker) removeConn(src v2net.Destination) {
+	w.Lock()
+	delete(w.activeConn, src)
+	w.Unlock()
+}
+
+func (w *udpWorker) Start() error {
+	ctx, cancel := context.WithCancel(context.Background())
+	w.ctx = ctx
+	w.cancel = cancel
+	h, err := udp.ListenUDP(w.address, w.port, udp.ListenOption{
+		Callback:            w.callback,
+		ReceiveOriginalDest: w.recvOrigDest,
+	})
+	if err != nil {
+		return err
+	}
+	w.hub = h
+	return nil
+}
+
+func (w *udpWorker) Close() {
+	w.hub.Close()
+	w.cancel()
+}
+
+func (w *udpWorker) monitor() {
+	for {
+		select {
+		case <-w.ctx.Done():
+			return
+		case <-time.After(time.Second * 16):
+			nowSec := time.Now().Unix()
+			w.Lock()
+			for addr, conn := range w.activeConn {
+				if nowSec-conn.lastActivityTime > 8 {
+					w.removeConn(addr)
+					conn.Close()
+				}
+			}
+		}
+	}
+}
+
+func (w *udpWorker) Port() v2net.Port {
+	return w.port
+}
+
+func (w *udpWorker) Proxy() proxy.InboundHandler {
+	return w.proxy
+}

+ 135 - 40
app/proxyman/outbound/handler.go

@@ -2,39 +2,58 @@ package outbound
 
 import (
 	"context"
+	"errors"
+	"io"
+	"net"
+	"time"
 
+	"v2ray.com/core/app"
 	"v2ray.com/core/app/proxyman"
-	"v2ray.com/core/common/errors"
-	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/log"
+	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
+	"v2ray.com/core/transport/ray"
 )
 
 type Handler struct {
-	config           *proxyman.OutboundHandlerConfig
-	streamSettings   *proxyman.StreamSenderConfig
-	datagramSettings *proxyman.DatagramSenderConfig
-	proxy            proxy.OutboundHandler
+	config          *proxyman.OutboundHandlerConfig
+	senderSettings  *proxyman.SenderConfig
+	proxy           proxy.OutboundHandler
+	outboundManager proxyman.OutboundHandlerManager
 }
 
 func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*Handler, error) {
 	h := &Handler{
 		config: config,
 	}
-	for _, rawSettings := range config.SenderSettings {
-		settings, err := rawSettings.GetInstance()
+	space := app.SpaceFromContext(ctx)
+	if space == nil {
+		return nil, errors.New("Proxyman|OutboundHandler: No space in context.")
+	}
+	space.OnInitialize(func() error {
+		ohm := proxyman.OutboundHandlerManagerFromSpace(space)
+		if ohm == nil {
+			return errors.New("Proxyman|OutboundHandler: No OutboundManager in space.")
+		}
+		h.outboundManager = ohm
+		return nil
+	})
+
+	if config.SenderSettings != nil {
+		senderSettings, err := config.SenderSettings.GetInstance()
 		if err != nil {
 			return nil, err
 		}
-		switch ts := settings.(type) {
-		case *proxyman.StreamSenderConfig:
-			h.streamSettings = ts
-		case *proxyman.DatagramSenderConfig:
-			h.datagramSettings = ts
+		switch s := senderSettings.(type) {
+		case *proxyman.SenderConfig:
+			h.senderSettings = s
 		default:
-			return nil, errors.New("Proxyman|DefaultOutboundHandler: Unknown sender settings: ", rawSettings.Type)
+			return nil, errors.New("Proxyman|DefaultOutboundHandler: settings is not SenderConfig.")
 		}
 	}
+
 	proxyHandler, err := config.GetProxyHandler(proxy.ContextWithDialer(ctx, h))
 	if err != nil {
 		return nil, err
@@ -44,38 +63,114 @@ func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*H
 	return h, nil
 }
 
-func (h *Handler) Dial(ctx context.Context, destination net.Destination) (internet.Connection, error) {
-	switch destination.Network {
-	case net.Network_TCP:
-		return h.dialStream(ctx, destination)
-	case net.Network_UDP:
-		return h.dialDatagram(ctx, destination)
-	default:
-		panic("Proxyman|DefaultOutboundHandler: unexpected network.")
-	}
+func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
+	ctx = proxy.ContextWithDialer(ctx, h)
+	h.proxy.Process(ctx, outboundRay)
 }
 
-func (h *Handler) dialStream(ctx context.Context, destination net.Destination) (internet.Connection, error) {
-	var src net.Address
-	if h.streamSettings != nil {
-		src = h.streamSettings.Via.AsAddress()
+func (h *Handler) Dial(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
+	if h.senderSettings != nil {
+		if h.senderSettings.ProxySettings.HasTag() {
+			tag := h.senderSettings.ProxySettings.Tag
+			handler := h.outboundManager.GetHandler(tag)
+			if handler != nil {
+				log.Info("Proxyman|OutboundHandler: Proxying to ", dest)
+				ctx = proxy.ContextWithDestination(ctx, dest)
+				stream := ray.NewRay(ctx)
+				go handler.Dispatch(ctx, stream)
+				return NewConnection(stream), nil
+			}
+
+			log.Warning("Proxyman|OutboundHandler: Failed to get outbound handler with tag: ", tag)
+		}
+
+		if h.senderSettings.Via != nil {
+			ctx = internet.ContextWithDialerSource(ctx, h.senderSettings.Via.AsAddress())
+		}
+		if h.senderSettings != nil {
+			ctx = internet.ContextWithStreamSettings(ctx, h.senderSettings.StreamSettings)
+		}
 	}
-	var options internet.DialerOptions
-	if h.streamSettings != nil {
-		options.Proxy = h.streamSettings.ProxySettings
-		options.Stream = h.streamSettings.StreamSettings
+
+	return internet.Dial(ctx, dest)
+}
+
+type Connection struct {
+	stream     ray.Ray
+	closed     bool
+	localAddr  net.Addr
+	remoteAddr net.Addr
+
+	reader *buf.BufferToBytesReader
+	writer *buf.BytesToBufferWriter
+}
+
+func NewConnection(stream ray.Ray) *Connection {
+	return &Connection{
+		stream: stream,
+		localAddr: &net.TCPAddr{
+			IP:   []byte{0, 0, 0, 0},
+			Port: 0,
+		},
+		remoteAddr: &net.TCPAddr{
+			IP:   []byte{0, 0, 0, 0},
+			Port: 0,
+		},
+		reader: buf.NewBytesReader(stream.InboundOutput()),
+		writer: buf.NewBytesWriter(stream.InboundInput()),
 	}
-	return internet.Dial(src, destination, options)
 }
 
-func (h *Handler) dialDatagram(ctx context.Context, destination net.Destination) (internet.Connection, error) {
-	var src net.Address
-	if h.datagramSettings != nil {
-		src = h.datagramSettings.Via.AsAddress()
+// Read implements net.Conn.Read().
+func (v *Connection) Read(b []byte) (int, error) {
+	if v.closed {
+		return 0, io.EOF
 	}
-	var options internet.DialerOptions
-	if h.datagramSettings != nil {
-		options.Proxy = h.datagramSettings.ProxySettings
+	return v.reader.Read(b)
+}
+
+// Write implements net.Conn.Write().
+func (v *Connection) Write(b []byte) (int, error) {
+	if v.closed {
+		return 0, io.ErrClosedPipe
 	}
-	return internet.Dial(src, destination, options)
+	return v.writer.Write(b)
+}
+
+// Close implements net.Conn.Close().
+func (v *Connection) Close() error {
+	v.closed = true
+	v.stream.InboundInput().Close()
+	v.stream.InboundOutput().CloseError()
+	return nil
+}
+
+// LocalAddr implements net.Conn.LocalAddr().
+func (v *Connection) LocalAddr() net.Addr {
+	return v.localAddr
+}
+
+// RemoteAddr implements net.Conn.RemoteAddr().
+func (v *Connection) RemoteAddr() net.Addr {
+	return v.remoteAddr
+}
+
+func (v *Connection) SetDeadline(t time.Time) error {
+	return nil
+}
+
+func (v *Connection) SetReadDeadline(t time.Time) error {
+	return nil
+}
+
+func (v *Connection) SetWriteDeadline(t time.Time) error {
+	return nil
+}
+
+func (v *Connection) Reusable() bool {
+	return false
+}
+
+func (v *Connection) SetReusable(bool) {
+
 }

+ 18 - 15
app/proxyman/outbound/outbound.go

@@ -6,18 +6,17 @@ import (
 
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
-	"v2ray.com/core/proxy"
 )
 
 type DefaultOutboundHandlerManager struct {
 	sync.RWMutex
-	defaultHandler proxy.OutboundHandler
-	taggedHandler  map[string]proxy.OutboundHandler
+	defaultHandler *Handler
+	taggedHandler  map[string]*Handler
 }
 
 func New(ctx context.Context, config *proxyman.OutboundConfig) (*DefaultOutboundHandlerManager, error) {
 	return &DefaultOutboundHandlerManager{
-		taggedHandler: make(map[string]proxy.OutboundHandler),
+		taggedHandler: make(map[string]*Handler),
 	}, nil
 }
 
@@ -25,7 +24,7 @@ func (DefaultOutboundHandlerManager) Interface() interface{} {
 	return (*proxyman.OutboundHandlerManager)(nil)
 }
 
-func (v *DefaultOutboundHandlerManager) GetDefaultHandler() proxy.OutboundHandler {
+func (v *DefaultOutboundHandlerManager) GetDefaultHandler() proxyman.OutboundHandler {
 	v.RLock()
 	defer v.RUnlock()
 	if v.defaultHandler == nil {
@@ -34,14 +33,7 @@ func (v *DefaultOutboundHandlerManager) GetDefaultHandler() proxy.OutboundHandle
 	return v.defaultHandler
 }
 
-func (v *DefaultOutboundHandlerManager) SetDefaultHandler(handler proxy.OutboundHandler) error {
-	v.Lock()
-	defer v.Unlock()
-	v.defaultHandler = handler
-	return nil
-}
-
-func (v *DefaultOutboundHandlerManager) GetHandler(tag string) proxy.OutboundHandler {
+func (v *DefaultOutboundHandlerManager) GetHandler(tag string) proxyman.OutboundHandler {
 	v.RLock()
 	defer v.RUnlock()
 	if handler, found := v.taggedHandler[tag]; found {
@@ -50,11 +42,22 @@ func (v *DefaultOutboundHandlerManager) GetHandler(tag string) proxy.OutboundHan
 	return nil
 }
 
-func (v *DefaultOutboundHandlerManager) SetHandler(tag string, handler proxy.OutboundHandler) error {
+func (v *DefaultOutboundHandlerManager) AddHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) error {
 	v.Lock()
 	defer v.Unlock()
 
-	v.taggedHandler[tag] = handler
+	handler, err := NewHandler(ctx, config)
+	if err != nil {
+		return err
+	}
+	if v.defaultHandler == nil {
+		v.defaultHandler = handler
+	}
+
+	if len(config.Tag) > 0 {
+		v.taggedHandler[config.Tag] = handler
+	}
+
 	return nil
 }
 

+ 13 - 6
app/proxyman/proxyman.go

@@ -11,21 +11,28 @@ import (
 )
 
 type InboundHandlerManager interface {
-	GetHandler(tag string) (proxy.InboundHandler, int)
+	GetHandler(ctx context.Context, tag string) (InboundHandler, error)
+	AddHandler(ctx context.Context, config *InboundHandlerConfig) error
+	Start() error
+	Close()
 }
 
 type InboundHandler interface {
+	Start() error
+	Close()
+
+	// For migration
+	GetRandomInboundProxy() (proxy.InboundHandler, net.Port, int)
 }
 
 type OutboundHandlerManager interface {
-	GetHandler(tag string) proxy.OutboundHandler
-	GetDefaultHandler() proxy.OutboundHandler
-	SetDefaultHandler(handler proxy.OutboundHandler) error
-	SetHandler(tag string, handler proxy.OutboundHandler) error
+	GetHandler(tag string) OutboundHandler
+	GetDefaultHandler() OutboundHandler
+	AddHandler(ctx context.Context, config *OutboundHandlerConfig) error
 }
 
 type OutboundHandler interface {
-	Dispatch(ctx context.Context, destination net.Destination, outboundRay ray.OutboundRay)
+	Dispatch(ctx context.Context, outboundRay ray.OutboundRay)
 }
 
 func InboundHandlerManagerFromSpace(space app.Space) InboundHandlerManager {

+ 64 - 27
app/router/condition.go

@@ -1,16 +1,18 @@
 package router
 
 import (
+	"context"
 	"net"
 	"regexp"
 	"strings"
 
 	v2net "v2ray.com/core/common/net"
+	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/proxy"
 )
 
 type Condition interface {
-	Apply(session *proxy.SessionInfo) bool
+	Apply(ctx context.Context) bool
 }
 
 type ConditionChan []Condition
@@ -25,9 +27,9 @@ func (v *ConditionChan) Add(cond Condition) *ConditionChan {
 	return v
 }
 
-func (v *ConditionChan) Apply(session *proxy.SessionInfo) bool {
+func (v *ConditionChan) Apply(ctx context.Context) bool {
 	for _, cond := range *v {
-		if !cond.Apply(session) {
+		if !cond.Apply(ctx) {
 			return false
 		}
 	}
@@ -50,9 +52,9 @@ func (v *AnyCondition) Add(cond Condition) *AnyCondition {
 	return v
 }
 
-func (v *AnyCondition) Apply(session *proxy.SessionInfo) bool {
+func (v *AnyCondition) Apply(ctx context.Context) bool {
 	for _, cond := range *v {
-		if cond.Apply(session) {
+		if cond.Apply(ctx) {
 			return true
 		}
 	}
@@ -73,8 +75,8 @@ func NewPlainDomainMatcher(pattern string) *PlainDomainMatcher {
 	}
 }
 
-func (v *PlainDomainMatcher) Apply(session *proxy.SessionInfo) bool {
-	dest := session.Destination
+func (v *PlainDomainMatcher) Apply(ctx context.Context) bool {
+	dest := proxy.DestinationFromContext(ctx)
 	if !dest.Address.Family().IsDomain() {
 		return false
 	}
@@ -96,8 +98,8 @@ func NewRegexpDomainMatcher(pattern string) (*RegexpDomainMatcher, error) {
 	}, nil
 }
 
-func (v *RegexpDomainMatcher) Apply(session *proxy.SessionInfo) bool {
-	dest := session.Destination
+func (v *RegexpDomainMatcher) Apply(ctx context.Context) bool {
+	dest := proxy.DestinationFromContext(ctx)
 	if !dest.Address.Family().IsDomain() {
 		return false
 	}
@@ -121,15 +123,31 @@ func NewCIDRMatcher(ip []byte, mask uint32, onSource bool) (*CIDRMatcher, error)
 	}, nil
 }
 
-func (v *CIDRMatcher) Apply(session *proxy.SessionInfo) bool {
-	dest := session.Destination
+func (v *CIDRMatcher) Apply(ctx context.Context) bool {
+	var dest v2net.Destination
 	if v.onSource {
-		dest = session.Source
+		dest = proxy.SourceFromContext(ctx)
+	} else {
+		dest = proxy.DestinationFromContext(ctx)
 	}
+
 	if !dest.Address.Family().Either(v2net.AddressFamilyIPv4, v2net.AddressFamilyIPv6) {
 		return false
 	}
-	return v.cidr.Contains(dest.Address.IP())
+
+	ips := []net.IP{dest.Address.IP()}
+	if resolveIPs, ok := proxy.ResolvedIPsFromContext(ctx); ok {
+		for _, rip := range resolveIPs {
+			ips = append(ips, rip.IP())
+		}
+	}
+
+	for _, ip := range ips {
+		if v.cidr.Contains(ip) {
+			return true
+		}
+	}
+	return false
 }
 
 type IPv4Matcher struct {
@@ -144,15 +162,30 @@ func NewIPv4Matcher(ipnet *v2net.IPNet, onSource bool) *IPv4Matcher {
 	}
 }
 
-func (v *IPv4Matcher) Apply(session *proxy.SessionInfo) bool {
-	dest := session.Destination
+func (v *IPv4Matcher) Apply(ctx context.Context) bool {
+	var dest v2net.Destination
 	if v.onSource {
-		dest = session.Source
+		dest = proxy.SourceFromContext(ctx)
+	} else {
+		dest = proxy.DestinationFromContext(ctx)
 	}
 	if !dest.Address.Family().Either(v2net.AddressFamilyIPv4) {
 		return false
 	}
-	return v.ipv4net.Contains(dest.Address.IP())
+
+	ips := []net.IP{dest.Address.IP()}
+	if resolvedIPs, ok := proxy.ResolvedIPsFromContext(ctx); ok {
+		for _, rip := range resolvedIPs {
+			ips = append(ips, rip.IP())
+		}
+	}
+
+	for _, ip := range ips {
+		if v.ipv4net.Contains(ip) {
+			return true
+		}
+	}
+	return false
 }
 
 type PortMatcher struct {
@@ -165,8 +198,9 @@ func NewPortMatcher(portRange v2net.PortRange) *PortMatcher {
 	}
 }
 
-func (v *PortMatcher) Apply(session *proxy.SessionInfo) bool {
-	return v.port.Contains(session.Destination.Port)
+func (v *PortMatcher) Apply(ctx context.Context) bool {
+	dest := proxy.DestinationFromContext(ctx)
+	return v.port.Contains(dest.Port)
 }
 
 type NetworkMatcher struct {
@@ -179,8 +213,9 @@ func NewNetworkMatcher(network *v2net.NetworkList) *NetworkMatcher {
 	}
 }
 
-func (v *NetworkMatcher) Apply(session *proxy.SessionInfo) bool {
-	return v.network.HasNetwork(session.Destination.Network)
+func (v *NetworkMatcher) Apply(ctx context.Context) bool {
+	dest := proxy.DestinationFromContext(ctx)
+	return v.network.HasNetwork(dest.Network)
 }
 
 type UserMatcher struct {
@@ -193,12 +228,13 @@ func NewUserMatcher(users []string) *UserMatcher {
 	}
 }
 
-func (v *UserMatcher) Apply(session *proxy.SessionInfo) bool {
-	if session.User == nil {
+func (v *UserMatcher) Apply(ctx context.Context) bool {
+	user := protocol.UserFromContext(ctx)
+	if user == nil {
 		return false
 	}
 	for _, u := range v.user {
-		if u == session.User.Email {
+		if u == user.Email {
 			return true
 		}
 	}
@@ -215,13 +251,14 @@ func NewInboundTagMatcher(tags []string) *InboundTagMatcher {
 	}
 }
 
-func (v *InboundTagMatcher) Apply(session *proxy.SessionInfo) bool {
-	if session.Inbound == nil || len(session.Inbound.Tag) == 0 {
+func (v *InboundTagMatcher) Apply(ctx context.Context) bool {
+	tag := proxy.InboundTagFromContext(ctx)
+	if len(tag) == 0 {
 		return false
 	}
 
 	for _, t := range v.tags {
-		if t == session.Inbound.Tag {
+		if t == tag {
 			return true
 		}
 	}

+ 3 - 3
app/router/config.go

@@ -1,11 +1,11 @@
 package router
 
 import (
+	"context"
 	"net"
 
 	"v2ray.com/core/common/errors"
 	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
 )
 
 type Rule struct {
@@ -13,8 +13,8 @@ type Rule struct {
 	Condition Condition
 }
 
-func (v *Rule) Apply(session *proxy.SessionInfo) bool {
-	return v.Condition.Apply(session)
+func (v *Rule) Apply(ctx context.Context) bool {
+	return v.Condition.Apply(ctx)
 }
 
 func (v *RoutingRule) BuildCondition() (Condition, error) {

+ 13 - 22
app/router/router.go

@@ -55,43 +55,34 @@ func NewRouter(ctx context.Context, config *Config) (*Router, error) {
 }
 
 // Private: Visible for testing.
-func (v *Router) ResolveIP(dest net.Destination) []net.Destination {
+func (v *Router) ResolveIP(dest net.Destination) []net.Address {
 	ips := v.dnsServer.Get(dest.Address.Domain())
 	if len(ips) == 0 {
 		return nil
 	}
-	dests := make([]net.Destination, len(ips))
+	dests := make([]net.Address, len(ips))
 	for idx, ip := range ips {
-		if dest.Network == net.Network_TCP {
-			dests[idx] = net.TCPDestination(net.IPAddress(ip), dest.Port)
-		} else {
-			dests[idx] = net.UDPDestination(net.IPAddress(ip), dest.Port)
-		}
+		dests[idx] = net.IPAddress(ip)
 	}
 	return dests
 }
 
-func (v *Router) takeDetourWithoutCache(session *proxy.SessionInfo) (string, error) {
+func (v *Router) takeDetourWithoutCache(ctx context.Context) (string, error) {
 	for _, rule := range v.rules {
-		if rule.Apply(session) {
+		if rule.Apply(ctx) {
 			return rule.Tag, nil
 		}
 	}
-	dest := session.Destination
+
+	dest := proxy.DestinationFromContext(ctx)
 	if v.domainStrategy == Config_IpIfNonMatch && dest.Address.Family().IsDomain() {
 		log.Info("Router: Looking up IP for ", dest)
 		ipDests := v.ResolveIP(dest)
 		if ipDests != nil {
-			for _, ipDest := range ipDests {
-				log.Info("Router: Trying IP ", ipDest)
-				for _, rule := range v.rules {
-					if rule.Apply(&proxy.SessionInfo{
-						Source:      session.Source,
-						Destination: ipDest,
-						User:        session.User,
-					}) {
-						return rule.Tag, nil
-					}
+			ctx = proxy.ContextWithResolveIPs(ctx, ipDests)
+			for _, rule := range v.rules {
+				if rule.Apply(ctx) {
+					return rule.Tag, nil
 				}
 			}
 		}
@@ -100,11 +91,11 @@ func (v *Router) takeDetourWithoutCache(session *proxy.SessionInfo) (string, err
 	return "", ErrNoRuleApplicable
 }
 
-func (v *Router) TakeDetour(session *proxy.SessionInfo) (string, error) {
+func (v *Router) TakeDetour(ctx context.Context) (string, error) {
 	//destStr := dest.String()
 	//found, tag, err := v.cache.Get(destStr)
 	//if !found {
-	tag, err := v.takeDetourWithoutCache(session)
+	tag, err := v.takeDetourWithoutCache(ctx)
 	//v.cache.Set(destStr, tag, err)
 	return tag, err
 	//}

+ 2 - 1
app/router/router_test.go

@@ -41,7 +41,8 @@ func TestSimpleRouter(t *testing.T) {
 
 	r := FromSpace(space)
 
-	tag, err := r.TakeDetour(&proxy.SessionInfo{Destination: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
+	ctx = proxy.ContextWithDestination(ctx, net.TCPDestination(net.DomainAddress("v2ray.com"), 80))
+	tag, err := r.TakeDetour(ctx)
 	assert.Error(err).IsNil()
 	assert.String(tag).Equals("test")
 }

+ 4 - 0
common/net/destination.go

@@ -48,6 +48,10 @@ func (v Destination) String() string {
 	return v.Network.URLPrefix() + ":" + v.NetAddr()
 }
 
+func (v Destination) IsValid() bool {
+	return v.Network != Network_Unknown
+}
+
 func (v *Endpoint) AsDestination() Destination {
 	return Destination{
 		Network: v.Network,

+ 23 - 0
common/protocol/context.go

@@ -0,0 +1,23 @@
+package protocol
+
+import (
+	"context"
+)
+
+type key int
+
+const (
+	userKey key = iota
+)
+
+func ContextWithUser(ctx context.Context, user *User) context.Context {
+	return context.WithValue(ctx, userKey, user)
+}
+
+func UserFromContext(ctx context.Context) *User {
+	v := ctx.Value(userKey)
+	if v == nil {
+		return nil
+	}
+	return v.(*User)
+}

+ 1 - 1
common/type.go

@@ -25,7 +25,7 @@ func CreateObject(ctx context.Context, config interface{}) (interface{}, error)
 	configType := reflect.TypeOf(config)
 	creator, found := typeCreatorRegistry[configType]
 	if !found {
-		return nil, errors.New("Common: " + configType.Name() + " is not registered.")
+		return nil, errors.New("Common: " + configType.String() + " is not registered.")
 	}
 	return creator(ctx, config)
 }

+ 0 - 55
config.go

@@ -1,55 +0,0 @@
-package core
-
-import (
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/net"
-)
-
-func (v *AllocationStrategy) GetConcurrencyValue() uint32 {
-	if v == nil || v.Concurrency == nil {
-		return 3
-	}
-	return v.Concurrency.Value
-}
-
-func (v *AllocationStrategy) GetRefreshValue() uint32 {
-	if v == nil || v.Refresh == nil {
-		return 5
-	}
-	return v.Refresh.Value
-}
-
-func (v *InboundConnectionConfig) GetAllocationStrategyValue() *AllocationStrategy {
-	if v.AllocationStrategy == nil {
-		return &AllocationStrategy{}
-	}
-	return v.AllocationStrategy
-}
-
-func (v *InboundConnectionConfig) GetListenOnValue() net.Address {
-	if v.GetListenOn() == nil {
-		return net.AnyIP
-	}
-	return v.ListenOn.AsAddress()
-}
-
-func (v *InboundConnectionConfig) GetTypedSettings() (interface{}, error) {
-	if v.GetSettings() == nil {
-		return nil, common.ErrBadConfiguration
-	}
-	return v.GetSettings().GetInstance()
-}
-
-func (v *OutboundConnectionConfig) GetTypedSettings() (interface{}, error) {
-	if v.GetSettings() == nil {
-		return nil, common.ErrBadConfiguration
-	}
-	return v.GetSettings().GetInstance()
-}
-
-func (v *OutboundConnectionConfig) GetSendThroughValue() net.Address {
-	if v.GetSendThrough() == nil {
-		return net.AnyIP
-	}
-	return v.SendThrough.AsAddress()
-}

+ 30 - 277
config.pb.go

@@ -3,11 +3,9 @@ package core
 import proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
 import math "math"
+import v2ray_core_app_proxyman "v2ray.com/core/app/proxyman"
 import v2ray_core_common_serial "v2ray.com/core/common/serial"
-import v2ray_core_common_net "v2ray.com/core/common/net"
-import v2ray_core_common_net1 "v2ray.com/core/common/net"
 import v2ray_core_common_log "v2ray.com/core/common/log"
-import v2ray_core_transport_internet "v2ray.com/core/transport/internet"
 import v2ray_core_transport "v2ray.com/core/transport"
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -43,226 +41,12 @@ func (x ConfigFormat) String() string {
 }
 func (ConfigFormat) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-type AllocationStrategy_Type int32
-
-const (
-	// Always allocate all connection handlers.
-	AllocationStrategy_Always AllocationStrategy_Type = 0
-	// Randomly allocate specific range of handlers.
-	AllocationStrategy_Random AllocationStrategy_Type = 1
-	// External. Not supported yet.
-	AllocationStrategy_External AllocationStrategy_Type = 2
-)
-
-var AllocationStrategy_Type_name = map[int32]string{
-	0: "Always",
-	1: "Random",
-	2: "External",
-}
-var AllocationStrategy_Type_value = map[string]int32{
-	"Always":   0,
-	"Random":   1,
-	"External": 2,
-}
-
-func (x AllocationStrategy_Type) String() string {
-	return proto.EnumName(AllocationStrategy_Type_name, int32(x))
-}
-func (AllocationStrategy_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} }
-
-type AllocationStrategyConcurrency struct {
-	Value uint32 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"`
-}
-
-func (m *AllocationStrategyConcurrency) Reset()                    { *m = AllocationStrategyConcurrency{} }
-func (m *AllocationStrategyConcurrency) String() string            { return proto.CompactTextString(m) }
-func (*AllocationStrategyConcurrency) ProtoMessage()               {}
-func (*AllocationStrategyConcurrency) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
-
-func (m *AllocationStrategyConcurrency) GetValue() uint32 {
-	if m != nil {
-		return m.Value
-	}
-	return 0
-}
-
-type AllocationStrategyRefresh struct {
-	Value uint32 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"`
-}
-
-func (m *AllocationStrategyRefresh) Reset()                    { *m = AllocationStrategyRefresh{} }
-func (m *AllocationStrategyRefresh) String() string            { return proto.CompactTextString(m) }
-func (*AllocationStrategyRefresh) ProtoMessage()               {}
-func (*AllocationStrategyRefresh) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
-
-func (m *AllocationStrategyRefresh) GetValue() uint32 {
-	if m != nil {
-		return m.Value
-	}
-	return 0
-}
-
-type AllocationStrategy struct {
-	Type AllocationStrategy_Type `protobuf:"varint,1,opt,name=type,enum=v2ray.core.AllocationStrategy_Type" json:"type,omitempty"`
-	// Number of handlers (ports) running in parallel.
-	// Default value is 3 if unset.
-	Concurrency *AllocationStrategyConcurrency `protobuf:"bytes,2,opt,name=concurrency" json:"concurrency,omitempty"`
-	// Number of minutes before a handler is regenerated.
-	// Default value is 5 if unset.
-	Refresh *AllocationStrategyRefresh `protobuf:"bytes,3,opt,name=refresh" json:"refresh,omitempty"`
-}
-
-func (m *AllocationStrategy) Reset()                    { *m = AllocationStrategy{} }
-func (m *AllocationStrategy) String() string            { return proto.CompactTextString(m) }
-func (*AllocationStrategy) ProtoMessage()               {}
-func (*AllocationStrategy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
-
-func (m *AllocationStrategy) GetType() AllocationStrategy_Type {
-	if m != nil {
-		return m.Type
-	}
-	return AllocationStrategy_Always
-}
-
-func (m *AllocationStrategy) GetConcurrency() *AllocationStrategyConcurrency {
-	if m != nil {
-		return m.Concurrency
-	}
-	return nil
-}
-
-func (m *AllocationStrategy) GetRefresh() *AllocationStrategyRefresh {
-	if m != nil {
-		return m.Refresh
-	}
-	return nil
-}
-
-// Config for an inbound connection handler.
-type InboundConnectionConfig struct {
-	// Protocol specific settings. Must be one of the supported protocols.
-	Settings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,1,opt,name=settings" json:"settings,omitempty"`
-	// Range of port number to run on. Both inclusive.
-	PortRange *v2ray_core_common_net.PortRange `protobuf:"bytes,2,opt,name=port_range,json=portRange" json:"port_range,omitempty"`
-	// IP address to listen on. 0.0.0.0 if unset.
-	ListenOn *v2ray_core_common_net1.IPOrDomain `protobuf:"bytes,3,opt,name=listen_on,json=listenOn" json:"listen_on,omitempty"`
-	// Tag of this handler.
-	Tag                    string                                      `protobuf:"bytes,4,opt,name=tag" json:"tag,omitempty"`
-	AllocationStrategy     *AllocationStrategy                         `protobuf:"bytes,5,opt,name=allocation_strategy,json=allocationStrategy" json:"allocation_strategy,omitempty"`
-	StreamSettings         *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,6,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
-	AllowPassiveConnection bool                                        `protobuf:"varint,7,opt,name=allow_passive_connection,json=allowPassiveConnection" json:"allow_passive_connection,omitempty"`
-}
-
-func (m *InboundConnectionConfig) Reset()                    { *m = InboundConnectionConfig{} }
-func (m *InboundConnectionConfig) String() string            { return proto.CompactTextString(m) }
-func (*InboundConnectionConfig) ProtoMessage()               {}
-func (*InboundConnectionConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
-
-func (m *InboundConnectionConfig) GetSettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.Settings
-	}
-	return nil
-}
-
-func (m *InboundConnectionConfig) GetPortRange() *v2ray_core_common_net.PortRange {
-	if m != nil {
-		return m.PortRange
-	}
-	return nil
-}
-
-func (m *InboundConnectionConfig) GetListenOn() *v2ray_core_common_net1.IPOrDomain {
-	if m != nil {
-		return m.ListenOn
-	}
-	return nil
-}
-
-func (m *InboundConnectionConfig) GetTag() string {
-	if m != nil {
-		return m.Tag
-	}
-	return ""
-}
-
-func (m *InboundConnectionConfig) GetAllocationStrategy() *AllocationStrategy {
-	if m != nil {
-		return m.AllocationStrategy
-	}
-	return nil
-}
-
-func (m *InboundConnectionConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
-	if m != nil {
-		return m.StreamSettings
-	}
-	return nil
-}
-
-func (m *InboundConnectionConfig) GetAllowPassiveConnection() bool {
-	if m != nil {
-		return m.AllowPassiveConnection
-	}
-	return false
-}
-
-// Config for an outbound connection handler.
-type OutboundConnectionConfig struct {
-	Settings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,1,opt,name=settings" json:"settings,omitempty"`
-	// IP address to send data through. 0.0.0.0 if unset.
-	SendThrough    *v2ray_core_common_net1.IPOrDomain          `protobuf:"bytes,2,opt,name=send_through,json=sendThrough" json:"send_through,omitempty"`
-	StreamSettings *v2ray_core_transport_internet.StreamConfig `protobuf:"bytes,3,opt,name=stream_settings,json=streamSettings" json:"stream_settings,omitempty"`
-	ProxySettings  *v2ray_core_transport_internet.ProxyConfig  `protobuf:"bytes,5,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
-	Tag            string                                      `protobuf:"bytes,4,opt,name=tag" json:"tag,omitempty"`
-}
-
-func (m *OutboundConnectionConfig) Reset()                    { *m = OutboundConnectionConfig{} }
-func (m *OutboundConnectionConfig) String() string            { return proto.CompactTextString(m) }
-func (*OutboundConnectionConfig) ProtoMessage()               {}
-func (*OutboundConnectionConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
-
-func (m *OutboundConnectionConfig) GetSettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.Settings
-	}
-	return nil
-}
-
-func (m *OutboundConnectionConfig) GetSendThrough() *v2ray_core_common_net1.IPOrDomain {
-	if m != nil {
-		return m.SendThrough
-	}
-	return nil
-}
-
-func (m *OutboundConnectionConfig) GetStreamSettings() *v2ray_core_transport_internet.StreamConfig {
-	if m != nil {
-		return m.StreamSettings
-	}
-	return nil
-}
-
-func (m *OutboundConnectionConfig) GetProxySettings() *v2ray_core_transport_internet.ProxyConfig {
-	if m != nil {
-		return m.ProxySettings
-	}
-	return nil
-}
-
-func (m *OutboundConnectionConfig) GetTag() string {
-	if m != nil {
-		return m.Tag
-	}
-	return ""
-}
-
 type Config struct {
 	// Inbound handler configurations. Must have at least one item.
-	Inbound []*InboundConnectionConfig `protobuf:"bytes,1,rep,name=inbound" json:"inbound,omitempty"`
+	Inbound []*v2ray_core_app_proxyman.InboundHandlerConfig `protobuf:"bytes,1,rep,name=inbound" json:"inbound,omitempty"`
 	// Outbound handler configurations. Must have at least one item. The first item is used as default for routing.
-	Outbound []*OutboundConnectionConfig   `protobuf:"bytes,2,rep,name=outbound" json:"outbound,omitempty"`
-	Log      *v2ray_core_common_log.Config `protobuf:"bytes,3,opt,name=log" json:"log,omitempty"`
+	Outbound []*v2ray_core_app_proxyman.OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound" json:"outbound,omitempty"`
+	Log      *v2ray_core_common_log.Config                    `protobuf:"bytes,3,opt,name=log" json:"log,omitempty"`
 	// App configuration. Must be one in the app directory.
 	App       []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=app" json:"app,omitempty"`
 	Transport *v2ray_core_transport.Config             `protobuf:"bytes,5,opt,name=transport" json:"transport,omitempty"`
@@ -271,16 +55,16 @@ type Config struct {
 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{5} }
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-func (m *Config) GetInbound() []*InboundConnectionConfig {
+func (m *Config) GetInbound() []*v2ray_core_app_proxyman.InboundHandlerConfig {
 	if m != nil {
 		return m.Inbound
 	}
 	return nil
 }
 
-func (m *Config) GetOutbound() []*OutboundConnectionConfig {
+func (m *Config) GetOutbound() []*v2ray_core_app_proxyman.OutboundHandlerConfig {
 	if m != nil {
 		return m.Outbound
 	}
@@ -309,65 +93,34 @@ func (m *Config) GetTransport() *v2ray_core_transport.Config {
 }
 
 func init() {
-	proto.RegisterType((*AllocationStrategyConcurrency)(nil), "v2ray.core.AllocationStrategyConcurrency")
-	proto.RegisterType((*AllocationStrategyRefresh)(nil), "v2ray.core.AllocationStrategyRefresh")
-	proto.RegisterType((*AllocationStrategy)(nil), "v2ray.core.AllocationStrategy")
-	proto.RegisterType((*InboundConnectionConfig)(nil), "v2ray.core.InboundConnectionConfig")
-	proto.RegisterType((*OutboundConnectionConfig)(nil), "v2ray.core.OutboundConnectionConfig")
 	proto.RegisterType((*Config)(nil), "v2ray.core.Config")
 	proto.RegisterEnum("v2ray.core.ConfigFormat", ConfigFormat_name, ConfigFormat_value)
-	proto.RegisterEnum("v2ray.core.AllocationStrategy_Type", AllocationStrategy_Type_name, AllocationStrategy_Type_value)
 }
 
 func init() { proto.RegisterFile("v2ray.com/core/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 745 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x95, 0xd1, 0x6e, 0xd3, 0x3a,
-	0x1c, 0xc6, 0x97, 0xb6, 0xeb, 0xda, 0x7f, 0xb7, 0x9e, 0xca, 0xe7, 0xe8, 0x9c, 0x9c, 0xc1, 0x50,
-	0x29, 0xdb, 0x28, 0x03, 0xa5, 0xa3, 0x08, 0x31, 0x21, 0xc1, 0xd8, 0x3a, 0x90, 0x06, 0x82, 0x16,
-	0x77, 0xe2, 0x82, 0x9b, 0xc8, 0x4b, 0xbd, 0x2c, 0x52, 0x62, 0x47, 0xb6, 0xbb, 0x2d, 0x8f, 0xc0,
-	0xab, 0x70, 0xc5, 0xab, 0xf0, 0x04, 0xbc, 0x0a, 0x72, 0x92, 0xa6, 0x1d, 0x6d, 0xb7, 0x49, 0x88,
-	0xbb, 0x34, 0xf9, 0x7e, 0x9f, 0x9d, 0xef, 0xfb, 0xc7, 0x85, 0x5b, 0x67, 0x6d, 0x41, 0x22, 0xcb,
-	0xe1, 0x41, 0xcb, 0xe1, 0x82, 0xb6, 0x1c, 0xce, 0x4e, 0x3c, 0xd7, 0x0a, 0x05, 0x57, 0x1c, 0xc1,
-	0xe8, 0xa1, 0xa0, 0xab, 0xdb, 0x53, 0xc2, 0x20, 0xe0, 0xac, 0x25, 0xa9, 0xf0, 0x88, 0xdf, 0x52,
-	0x51, 0x48, 0x07, 0x76, 0x40, 0xa5, 0x24, 0x2e, 0x4d, 0xe8, 0xd5, 0xf5, 0xd9, 0x04, 0xa3, 0xaa,
-	0x15, 0x72, 0xa1, 0x52, 0xd5, 0xfd, 0xf9, 0x2a, 0x32, 0x18, 0x08, 0x2a, 0x65, 0x2a, 0xdc, 0x9c,
-	0x2d, 0xf4, 0xb9, 0x7b, 0x69, 0xd3, 0xab, 0xd6, 0x2f, 0x3a, 0x25, 0x08, 0x93, 0x7a, 0xc1, 0x96,
-	0xc7, 0x14, 0x15, 0xda, 0xf8, 0x92, 0x7e, 0x63, 0xae, 0x7e, 0x52, 0xd6, 0x78, 0x0a, 0x6b, 0x7b,
-	0xbe, 0xcf, 0x1d, 0xa2, 0x3c, 0xce, 0xfa, 0x4a, 0x10, 0x45, 0xdd, 0xa8, 0xc3, 0x99, 0x33, 0x14,
-	0x82, 0x32, 0x27, 0x42, 0xff, 0xc0, 0xe2, 0x19, 0xf1, 0x87, 0xd4, 0x34, 0xea, 0x46, 0x73, 0x05,
-	0x27, 0x3f, 0x1a, 0x8f, 0xe1, 0xff, 0x69, 0x0c, 0xd3, 0x13, 0x41, 0xe5, 0xe9, 0x1c, 0xe4, 0x4b,
-	0x0e, 0xd0, 0x34, 0x83, 0x9e, 0x41, 0x41, 0xa7, 0x1c, 0x6b, 0xab, 0xed, 0x7b, 0xd6, 0xb8, 0x1b,
-	0x6b, 0x5a, 0x6d, 0x1d, 0x45, 0x21, 0xc5, 0x31, 0x80, 0xde, 0x41, 0xc5, 0x19, 0xef, 0xd3, 0xcc,
-	0xd5, 0x8d, 0x66, 0xa5, 0xfd, 0xe0, 0x6a, 0x7e, 0xe2, 0xc5, 0xf0, 0x24, 0x8d, 0x76, 0x61, 0x49,
-	0x24, 0xbb, 0x37, 0xf3, 0xb1, 0xd1, 0xc6, 0xd5, 0x46, 0xe9, 0xab, 0xe2, 0x11, 0xd5, 0x78, 0x04,
-	0x05, 0xbd, 0x37, 0x04, 0x50, 0xdc, 0xf3, 0xcf, 0x49, 0x24, 0x6b, 0x0b, 0xfa, 0x1a, 0x13, 0x36,
-	0xe0, 0x41, 0xcd, 0x40, 0xcb, 0x50, 0x7a, 0x7d, 0xa1, 0x7b, 0x22, 0x7e, 0x2d, 0xd7, 0xf8, 0x9e,
-	0x87, 0xff, 0x0e, 0xd9, 0x31, 0x1f, 0xb2, 0x41, 0x87, 0x33, 0x46, 0x1d, 0xed, 0xdd, 0x89, 0x7b,
-	0x41, 0xfb, 0x50, 0x92, 0x54, 0x29, 0x8f, 0xb9, 0x32, 0x0e, 0xa5, 0xd2, 0xde, 0x9c, 0xdc, 0x4b,
-	0x32, 0x1f, 0x56, 0x32, 0xa0, 0x71, 0x1e, 0x83, 0xf7, 0xc9, 0x7c, 0xe2, 0x8c, 0x43, 0xbb, 0x00,
-	0xba, 0x6a, 0x5b, 0x10, 0xe6, 0xd2, 0x34, 0x9a, 0xfa, 0x0c, 0x17, 0x46, 0x95, 0xd5, 0xe3, 0x42,
-	0x61, 0xad, 0xc3, 0xe5, 0x70, 0x74, 0x89, 0x5e, 0x42, 0xd9, 0xf7, 0xa4, 0xa2, 0xcc, 0xe6, 0x2c,
-	0x4d, 0xe4, 0xee, 0x1c, 0xfe, 0xb0, 0xd7, 0x15, 0x07, 0x3c, 0x20, 0x1e, 0xc3, 0xa5, 0x84, 0xe9,
-	0x32, 0x54, 0x83, 0xbc, 0x22, 0xae, 0x59, 0xa8, 0x1b, 0xcd, 0x32, 0xd6, 0x97, 0xa8, 0x0b, 0x7f,
-	0x93, 0x2c, 0x46, 0x5b, 0xa6, 0x39, 0x9a, 0x8b, 0xb1, 0xf7, 0x9d, 0x6b, 0xd2, 0x46, 0x64, 0x7a,
-	0x70, 0x8e, 0xe0, 0x2f, 0xa9, 0x04, 0x25, 0x81, 0x9d, 0xc5, 0x55, 0x8c, 0xcd, 0x1e, 0x4e, 0x9a,
-	0x65, 0x63, 0x6f, 0x8d, 0x3e, 0x13, 0xab, 0x1f, 0x53, 0x49, 0xda, 0xb8, 0x9a, 0x78, 0xf4, 0x47,
-	0xc9, 0xed, 0x80, 0xa9, 0xd7, 0x3a, 0xb7, 0x43, 0x22, 0xa5, 0x77, 0x46, 0x6d, 0x27, 0xeb, 0xc7,
-	0x5c, 0xaa, 0x1b, 0xcd, 0x12, 0xfe, 0x37, 0x7e, 0xde, 0x4b, 0x1e, 0x8f, 0xdb, 0x6b, 0xfc, 0xc8,
-	0x81, 0xd9, 0x1d, 0xaa, 0x3f, 0x57, 0xea, 0x01, 0x2c, 0x4b, 0xca, 0x06, 0xb6, 0x3a, 0x15, 0x7c,
-	0xe8, 0x9e, 0xa6, 0xb5, 0xde, 0xa0, 0x96, 0x8a, 0xc6, 0x8e, 0x12, 0x6a, 0x56, 0x6c, 0xf9, 0xdf,
-	0x8f, 0xed, 0x23, 0x54, 0x43, 0xc1, 0x2f, 0xa2, 0xb1, 0x69, 0x52, 0xec, 0xd6, 0x35, 0xa6, 0x3d,
-	0x0d, 0xa5, 0x9e, 0x2b, 0xb1, 0x43, 0x66, 0x39, 0x35, 0x42, 0x8d, 0x6f, 0x39, 0x28, 0xa6, 0x79,
-	0xbe, 0x80, 0x25, 0x2f, 0xf9, 0x7e, 0x4c, 0xa3, 0x9e, 0x6f, 0x56, 0x2e, 0x1f, 0x1c, 0x73, 0x3e,
-	0x2d, 0x3c, 0x62, 0xd0, 0x2b, 0x28, 0xf1, 0xb4, 0x2a, 0x33, 0x17, 0xf3, 0xeb, 0x93, 0xfc, 0xbc,
-	0x1a, 0x71, 0x46, 0xa1, 0x16, 0xe4, 0x7d, 0xee, 0xa6, 0xd1, 0xad, 0xcd, 0xe8, 0xc0, 0xe7, 0xae,
-	0x95, 0x52, 0x5a, 0x89, 0x76, 0x20, 0x4f, 0xc2, 0xd0, 0x2c, 0xc4, 0xab, 0xdd, 0xb4, 0x7c, 0x8d,
-	0xa0, 0xe7, 0x50, 0xce, 0x92, 0x4b, 0x63, 0xbd, 0x3d, 0x3b, 0xd6, 0x74, 0xbd, 0xb1, 0x7c, 0x6b,
-	0x13, 0x96, 0x93, 0x9b, 0x6f, 0xb8, 0x08, 0x88, 0xd2, 0xc7, 0x50, 0x4f, 0x9f, 0xfb, 0xc7, 0xc3,
-	0x93, 0xda, 0x02, 0x2a, 0x41, 0xe1, 0x6d, 0xbf, 0xfb, 0xa1, 0x66, 0xec, 0x6f, 0x43, 0xd5, 0xe1,
-	0xc1, 0x84, 0xeb, 0x7e, 0x25, 0xe1, 0x62, 0xf5, 0xe7, 0x82, 0xbe, 0xf5, 0x35, 0x07, 0x9f, 0xda,
-	0x98, 0x44, 0x56, 0x87, 0x0b, 0x7a, 0x5c, 0x8c, 0xff, 0x3f, 0x9e, 0xfc, 0x0c, 0x00, 0x00, 0xff,
-	0xff, 0xff, 0x44, 0xf3, 0xc8, 0x6a, 0x07, 0x00, 0x00,
+	// 338 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x91, 0xdf, 0x4a, 0x02, 0x41,
+	0x14, 0xc6, 0xd3, 0x35, 0xb3, 0xa3, 0x84, 0xcc, 0xd5, 0x62, 0x05, 0x12, 0x24, 0x12, 0x34, 0x2b,
+	0xdb, 0x4d, 0x74, 0xa9, 0xd0, 0x1f, 0xa1, 0x0c, 0x8b, 0x2e, 0xba, 0x89, 0x71, 0x1d, 0x17, 0x61,
+	0x67, 0xce, 0x30, 0xbb, 0x46, 0xfb, 0x4a, 0x3d, 0x47, 0x0f, 0x16, 0x3b, 0xe3, 0xaa, 0x6b, 0x76,
+	0x7b, 0xe6, 0xfb, 0xfd, 0x0e, 0xe7, 0x1b, 0x38, 0xfe, 0xf4, 0x35, 0x4b, 0x69, 0x80, 0xc2, 0x0b,
+	0x50, 0x73, 0x2f, 0x40, 0x39, 0x9b, 0x87, 0x54, 0x69, 0x4c, 0x90, 0x40, 0xfe, 0xa8, 0x79, 0xab,
+	0xbb, 0x15, 0x64, 0x4a, 0x79, 0x4a, 0xe3, 0x57, 0x2a, 0x98, 0x2c, 0x50, 0xad, 0xde, 0x1f, 0xa5,
+	0x10, 0x28, 0xbd, 0x98, 0xeb, 0x39, 0x8b, 0xbc, 0x24, 0x55, 0x7c, 0xfa, 0x21, 0x78, 0x1c, 0xb3,
+	0x90, 0x2f, 0x89, 0xce, 0x6e, 0x22, 0xc2, 0xb0, 0x68, 0x3e, 0xdf, 0xca, 0x25, 0x9a, 0xc9, 0x58,
+	0xa1, 0x4e, 0x0a, 0xb1, 0xb3, 0x9f, 0x32, 0x54, 0x07, 0x66, 0x40, 0xee, 0xe0, 0x60, 0x2e, 0x27,
+	0xb8, 0x90, 0x53, 0xb7, 0xd4, 0x76, 0xba, 0x75, 0xff, 0x92, 0xae, 0x6f, 0xa2, 0x4c, 0x29, 0x9a,
+	0xdf, 0x40, 0x1f, 0x6c, 0xee, 0x9e, 0xc9, 0x69, 0xc4, 0xb5, 0xe5, 0xc7, 0x39, 0x4d, 0x86, 0x50,
+	0xc3, 0x45, 0x62, 0x4d, 0x65, 0x63, 0xa2, 0xff, 0x9a, 0x46, 0xcb, 0x60, 0x51, 0xb5, 0xe2, 0x89,
+	0x07, 0x4e, 0x84, 0xa1, 0xeb, 0xb4, 0x4b, 0xdd, 0xba, 0x7f, 0xba, 0xa9, 0xb1, 0x87, 0xd3, 0x08,
+	0x43, 0xba, 0xa4, 0xb2, 0x24, 0xb9, 0x06, 0x87, 0x29, 0xe5, 0x56, 0xcc, 0xde, 0xce, 0x0e, 0xc0,
+	0x76, 0x4b, 0x5f, 0xb3, 0x6e, 0x1f, 0x6d, 0xb5, 0xe3, 0x0c, 0x21, 0x37, 0x70, 0xb8, 0x2a, 0xc9,
+	0xdd, 0x37, 0x0b, 0x4f, 0x36, 0xf9, 0xd5, 0x63, 0xbe, 0x6f, 0x1d, 0xbf, 0xe8, 0x40, 0xc3, 0x0e,
+	0x6f, 0x51, 0x0b, 0x96, 0x90, 0x06, 0xd4, 0x9e, 0xb3, 0x7e, 0x27, 0x8b, 0x59, 0x73, 0x8f, 0xd4,
+	0xa0, 0x32, 0x7c, 0x19, 0x3d, 0x35, 0x4b, 0xfd, 0x1e, 0x1c, 0x05, 0x28, 0x36, 0xac, 0xfd, 0xba,
+	0xe5, 0x4c, 0xfa, 0xbd, 0x92, 0x8d, 0xbe, 0xcb, 0xf0, 0xe6, 0x8f, 0x59, 0x4a, 0x07, 0xa8, 0xf9,
+	0xa4, 0x6a, 0xfe, 0xe9, 0xea, 0x37, 0x00, 0x00, 0xff, 0xff, 0xa7, 0x35, 0x52, 0xbd, 0x7d, 0x02,
+	0x00, 0x00,
 }

+ 5 - 69
config.proto

@@ -6,11 +6,9 @@ option go_package = "core";
 option java_package = "com.v2ray.core";
 option java_outer_classname = "ConfigProto";
 
+import "v2ray.com/core/app/proxyman/config.proto";
 import "v2ray.com/core/common/serial/typed_message.proto";
-import "v2ray.com/core/common/net/port.proto";
-import "v2ray.com/core/common/net/address.proto";
 import "v2ray.com/core/common/log/config.proto";
-import "v2ray.com/core/transport/internet/config.proto";
 import "v2ray.com/core/transport/config.proto";
 
 // Configuration serialization format.
@@ -19,78 +17,16 @@ enum ConfigFormat {
   JSON = 1;
 }
 
-message AllocationStrategyConcurrency {
-  uint32 value = 1;
-}
-
-message AllocationStrategyRefresh {
-  uint32 value = 1;
-}
-
-message AllocationStrategy {
-  enum Type {
-    // Always allocate all connection handlers.
-    Always = 0;
-
-    // Randomly allocate specific range of handlers.
-    Random = 1;
-
-    // External. Not supported yet.
-    External = 2;
-  }
-
-  Type type = 1;
-
-  // Number of handlers (ports) running in parallel.
-  // Default value is 3 if unset.
-  AllocationStrategyConcurrency concurrency = 2;
-
-  // Number of minutes before a handler is regenerated.
-  // Default value is 5 if unset.
-  AllocationStrategyRefresh refresh = 3;
-}
-
-// Config for an inbound connection handler.
-message InboundConnectionConfig {
-  // Protocol specific settings. Must be one of the supported protocols.
-  v2ray.core.common.serial.TypedMessage settings = 1;
-
-  // Range of port number to run on. Both inclusive.
-  v2ray.core.common.net.PortRange port_range = 2;
-
-  // IP address to listen on. 0.0.0.0 if unset.
-  v2ray.core.common.net.IPOrDomain listen_on = 3;
-
-  // Tag of this handler.
-  string tag = 4;
-
-  AllocationStrategy allocation_strategy = 5;
-
-  v2ray.core.transport.internet.StreamConfig stream_settings = 6;
-
-  bool allow_passive_connection = 7;
-}
-
-// Config for an outbound connection handler.
-message OutboundConnectionConfig {
-  v2ray.core.common.serial.TypedMessage settings = 1;
-
-  // IP address to send data through. 0.0.0.0 if unset.
-  v2ray.core.common.net.IPOrDomain send_through = 2;
-  v2ray.core.transport.internet.StreamConfig stream_settings = 3;
-  v2ray.core.transport.internet.ProxyConfig proxy_settings = 5;
-  string tag = 4;
-}
-
 message Config {
   // Inbound handler configurations. Must have at least one item.
-  repeated InboundConnectionConfig inbound = 1;
+  repeated v2ray.core.app.proxyman.InboundHandlerConfig inbound = 1;
 
   // Outbound handler configurations. Must have at least one item. The first item is used as default for routing.
-  repeated OutboundConnectionConfig outbound = 2;
+  repeated v2ray.core.app.proxyman.OutboundHandlerConfig outbound = 2;
+
   v2ray.core.common.log.Config log = 3;
 
   // App configuration. Must be one in the app directory.
   repeated v2ray.core.common.serial.TypedMessage app = 4;
   v2ray.core.transport.Config transport = 5;
-}
+}

+ 0 - 11
inbound_detour.go

@@ -1,11 +0,0 @@
-package core
-
-import (
-	"v2ray.com/core/proxy"
-)
-
-type InboundDetourHandler interface {
-	Start() error
-	Close()
-	GetConnectionHandler() (proxy.InboundHandler, int)
-}

+ 0 - 77
inbound_detour_always.go

@@ -1,77 +0,0 @@
-package core
-
-import (
-	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/common/dice"
-	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/retry"
-	"v2ray.com/core/proxy"
-)
-
-// InboundDetourHandlerAlways is a handler for inbound detour connections.
-type InboundDetourHandlerAlways struct {
-	space  app.Space
-	config *InboundConnectionConfig
-	ich    []proxy.InboundHandler
-}
-
-func NewInboundDetourHandlerAlways(ctx context.Context, config *InboundConnectionConfig) (*InboundDetourHandlerAlways, error) {
-	space := app.SpaceFromContext(ctx)
-	handler := &InboundDetourHandlerAlways{
-		space:  space,
-		config: config,
-	}
-	ports := config.PortRange
-	handler.ich = make([]proxy.InboundHandler, 0, ports.To-ports.From+1)
-	for i := ports.FromPort(); i <= ports.ToPort(); i++ {
-		ichConfig, err := config.GetTypedSettings()
-		if err != nil {
-			return nil, err
-		}
-		ich, err := proxy.CreateInboundHandler(proxy.ContextWithInboundMeta(ctx, &proxy.InboundHandlerMeta{
-			Address:                config.GetListenOnValue(),
-			Port:                   i,
-			Tag:                    config.Tag,
-			StreamSettings:         config.StreamSettings,
-			AllowPassiveConnection: config.AllowPassiveConnection,
-		}), ichConfig)
-
-		if err != nil {
-			log.Error("Failed to create inbound connection handler: ", err)
-			return nil, err
-		}
-		handler.ich = append(handler.ich, ich)
-	}
-	return handler, nil
-}
-
-func (v *InboundDetourHandlerAlways) GetConnectionHandler() (proxy.InboundHandler, int) {
-	ich := v.ich[dice.Roll(len(v.ich))]
-	return ich, int(v.config.GetAllocationStrategyValue().GetRefreshValue())
-}
-
-func (v *InboundDetourHandlerAlways) Close() {
-	for _, ich := range v.ich {
-		ich.Close()
-	}
-}
-
-// Start starts the inbound connection handler.
-func (v *InboundDetourHandlerAlways) Start() error {
-	for _, ich := range v.ich {
-		err := retry.ExponentialBackoff(10 /* times */, 200 /* ms */).On(func() error {
-			err := ich.Start()
-			if err != nil {
-				log.Error("Failed to start inbound detour:", err)
-				return err
-			}
-			return nil
-		})
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}

+ 0 - 164
inbound_detour_dynamic.go

@@ -1,164 +0,0 @@
-package core
-
-import (
-	"context"
-	"sync"
-	"time"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/common/dice"
-	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/common/retry"
-	"v2ray.com/core/proxy"
-)
-
-type InboundDetourHandlerDynamic struct {
-	sync.RWMutex
-	space       app.Space
-	config      *InboundConnectionConfig
-	portsInUse  map[net.Port]bool
-	ichs        []proxy.InboundHandler
-	ich2Recyle  []proxy.InboundHandler
-	lastRefresh time.Time
-	ctx         context.Context
-}
-
-func NewInboundDetourHandlerDynamic(ctx context.Context, config *InboundConnectionConfig) (*InboundDetourHandlerDynamic, error) {
-	space := app.SpaceFromContext(ctx)
-	handler := &InboundDetourHandlerDynamic{
-		space:      space,
-		config:     config,
-		portsInUse: make(map[net.Port]bool),
-		ctx:        ctx,
-	}
-	handler.ichs = make([]proxy.InboundHandler, config.GetAllocationStrategyValue().GetConcurrencyValue())
-
-	// To test configuration
-	ichConfig, err := config.GetTypedSettings()
-	if err != nil {
-		return nil, err
-	}
-	ich, err := proxy.CreateInboundHandler(proxy.ContextWithInboundMeta(ctx, &proxy.InboundHandlerMeta{
-		Address:                config.GetListenOnValue(),
-		Port:                   0,
-		Tag:                    config.Tag,
-		StreamSettings:         config.StreamSettings,
-		AllowPassiveConnection: config.AllowPassiveConnection,
-	}), ichConfig)
-	if err != nil {
-		log.Error("Point: Failed to create inbound connection handler: ", err)
-		return nil, err
-	}
-	ich.Close()
-
-	return handler, nil
-}
-
-func (v *InboundDetourHandlerDynamic) pickUnusedPort() net.Port {
-	delta := int(v.config.PortRange.To) - int(v.config.PortRange.From) + 1
-	for {
-		r := dice.Roll(delta)
-		port := v.config.PortRange.FromPort() + net.Port(r)
-		_, used := v.portsInUse[port]
-		if !used {
-			return port
-		}
-	}
-}
-
-func (v *InboundDetourHandlerDynamic) GetConnectionHandler() (proxy.InboundHandler, int) {
-	v.RLock()
-	defer v.RUnlock()
-	ich := v.ichs[dice.Roll(len(v.ichs))]
-	until := int(v.config.GetAllocationStrategyValue().GetRefreshValue()) - int((time.Now().Unix()-v.lastRefresh.Unix())/60/1000)
-	if until < 0 {
-		until = 0
-	}
-	return ich, int(until)
-}
-
-func (v *InboundDetourHandlerDynamic) Close() {
-	v.Lock()
-	defer v.Unlock()
-	for _, ich := range v.ichs {
-		ich.Close()
-	}
-}
-
-func (v *InboundDetourHandlerDynamic) RecyleHandles() {
-	if v.ich2Recyle != nil {
-		for _, ich := range v.ich2Recyle {
-			if ich == nil {
-				continue
-			}
-			port := ich.Port()
-			ich.Close()
-			delete(v.portsInUse, port)
-		}
-		v.ich2Recyle = nil
-	}
-}
-
-func (v *InboundDetourHandlerDynamic) refresh() error {
-	v.lastRefresh = time.Now()
-
-	config := v.config
-	v.ich2Recyle = v.ichs
-	newIchs := make([]proxy.InboundHandler, config.GetAllocationStrategyValue().GetConcurrencyValue())
-
-	for idx := range newIchs {
-		err := retry.Timed(5, 100).On(func() error {
-			port := v.pickUnusedPort()
-			ichConfig, _ := config.GetTypedSettings()
-			ich, err := proxy.CreateInboundHandler(proxy.ContextWithInboundMeta(v.ctx, &proxy.InboundHandlerMeta{
-				Address: config.GetListenOnValue(),
-				Port:    port, Tag: config.Tag,
-				StreamSettings: config.StreamSettings}), ichConfig)
-			if err != nil {
-				delete(v.portsInUse, port)
-				return err
-			}
-			err = ich.Start()
-			if err != nil {
-				delete(v.portsInUse, port)
-				return err
-			}
-			v.portsInUse[port] = true
-			newIchs[idx] = ich
-			return nil
-		})
-		if err != nil {
-			log.Error("Point: Failed to create inbound connection handler: ", err)
-			return err
-		}
-	}
-
-	v.Lock()
-	v.ichs = newIchs
-	v.Unlock()
-
-	return nil
-}
-
-func (v *InboundDetourHandlerDynamic) Start() error {
-	err := v.refresh()
-	if err != nil {
-		log.Error("Point: Failed to refresh dynamic allocations: ", err)
-		return err
-	}
-
-	go func() {
-		for {
-			time.Sleep(time.Duration(v.config.GetAllocationStrategyValue().GetRefreshValue())*time.Minute - 1)
-			v.RecyleHandles()
-			err := v.refresh()
-			if err != nil {
-				log.Error("Point: Failed to refresh dynamic allocations: ", err)
-			}
-			time.Sleep(time.Minute)
-		}
-	}()
-
-	return nil
-}

+ 1 - 1
main/distro/all/all.go

@@ -4,7 +4,7 @@ import (
 	// The following are necessary as they register handlers in their init functions.
 	_ "v2ray.com/core/app/dispatcher/impl"
 	_ "v2ray.com/core/app/dns/server"
-	_ "v2ray.com/core/app/proxy"
+	_ "v2ray.com/core/app/proxyman/inbound"
 	_ "v2ray.com/core/app/proxyman/outbound"
 	_ "v2ray.com/core/app/router"
 

+ 5 - 5
proxy/blackhole/blackhole.go

@@ -6,7 +6,6 @@ import (
 	"time"
 
 	"v2ray.com/core/common"
-	"v2ray.com/core/common/net"
 	"v2ray.com/core/transport/ray"
 )
 
@@ -27,13 +26,14 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 }
 
 // Dispatch implements OutboundHandler.Dispatch().
-func (v *Handler) Dispatch(destination net.Destination, ray ray.OutboundRay) {
-	v.response.WriteTo(ray.OutboundOutput())
+func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay) error {
+	v.response.WriteTo(outboundRay.OutboundOutput())
 	// CloseError() will immediately close the connection.
 	// Sleep a little here to make sure the response is sent to client.
 	time.Sleep(time.Millisecond * 500)
-	ray.OutboundInput().CloseError()
-	ray.OutboundOutput().CloseError()
+	outboundRay.OutboundInput().CloseError()
+	outboundRay.OutboundOutput().CloseError()
+	return nil
 }
 
 func init() {

+ 94 - 20
proxy/context.go

@@ -2,48 +2,122 @@ package proxy
 
 import (
 	"context"
+
+	"v2ray.com/core/common/net"
 )
 
 type key int
 
 const (
-	inboundMetaKey key = iota
-	outboundMetaKey
-	dialerKey
+	dialerKey key = iota
+	sourceKey
+	destinationKey
+	originalDestinationKey
+	inboundDestinationKey
+	inboundTagKey
+	outboundTagKey
+	resolvedIPsKey
+	allowPassiveConnKey
 )
 
-func ContextWithInboundMeta(ctx context.Context, meta *InboundHandlerMeta) context.Context {
-	return context.WithValue(ctx, inboundMetaKey, meta)
+func ContextWithDialer(ctx context.Context, dialer Dialer) context.Context {
+	return context.WithValue(ctx, dialerKey, dialer)
 }
 
-func InboundMetaFromContext(ctx context.Context) *InboundHandlerMeta {
-	v := ctx.Value(inboundMetaKey)
+func DialerFromContext(ctx context.Context) Dialer {
+	v := ctx.Value(dialerKey)
 	if v == nil {
 		return nil
 	}
-	return v.(*InboundHandlerMeta)
+	return v.(Dialer)
 }
 
-func ContextWithOutboundMeta(ctx context.Context, meta *OutboundHandlerMeta) context.Context {
-	return context.WithValue(ctx, outboundMetaKey, meta)
+func ContextWithSource(ctx context.Context, src net.Destination) context.Context {
+	return context.WithValue(ctx, sourceKey, src)
 }
 
-func OutboundMetaFromContext(ctx context.Context) *OutboundHandlerMeta {
-	v := ctx.Value(outboundMetaKey)
+func SourceFromContext(ctx context.Context) net.Destination {
+	v := ctx.Value(sourceKey)
 	if v == nil {
-		return nil
+		return net.Destination{}
 	}
-	return v.(*OutboundHandlerMeta)
+	return v.(net.Destination)
 }
 
-func ContextWithDialer(ctx context.Context, dialer Dialer) context.Context {
-	return context.WithValue(ctx, dialerKey, dialer)
+func ContextWithOriginalDestination(ctx context.Context, dest net.Destination) context.Context {
+	return context.WithValue(ctx, originalDestinationKey, dest)
 }
 
-func DialerFromContext(ctx context.Context) Dialer {
-	v := ctx.Value(dialerKey)
+func OriginalDestinationFromContext(ctx context.Context) net.Destination {
+	v := ctx.Value(originalDestinationKey)
 	if v == nil {
-		return nil
+		return net.Destination{}
 	}
-	return v.(Dialer)
+	return v.(net.Destination)
+}
+
+func ContextWithDestination(ctx context.Context, dest net.Destination) context.Context {
+	return context.WithValue(ctx, destinationKey, dest)
+}
+
+func DestinationFromContext(ctx context.Context) net.Destination {
+	v := ctx.Value(destinationKey)
+	if v == nil {
+		return net.Destination{}
+	}
+	return v.(net.Destination)
+}
+
+func ContextWithInboundDestination(ctx context.Context, dest net.Destination) context.Context {
+	return context.WithValue(ctx, inboundDestinationKey, dest)
+}
+
+func InboundDestinationFromContext(ctx context.Context) net.Destination {
+	v := ctx.Value(inboundDestinationKey)
+	if v == nil {
+		return net.Destination{}
+	}
+	return v.(net.Destination)
+}
+
+func ContextWithInboundTag(ctx context.Context, tag string) context.Context {
+	return context.WithValue(ctx, inboundTagKey, tag)
+}
+
+func InboundTagFromContext(ctx context.Context) string {
+	v := ctx.Value(inboundTagKey)
+	if v == nil {
+		return ""
+	}
+	return v.(string)
+}
+
+func ContextWithOutboundTag(ctx context.Context, tag string) context.Context {
+	return context.WithValue(ctx, outboundTagKey, tag)
+}
+
+func OutboundTagFromContext(ctx context.Context) string {
+	v := ctx.Value(outboundTagKey)
+	if v == nil {
+		return ""
+	}
+	return v.(string)
+}
+
+func ContextWithResolveIPs(ctx context.Context, ips []net.Address) context.Context {
+	return context.WithValue(ctx, resolvedIPsKey, ips)
+}
+
+func ResolvedIPsFromContext(ctx context.Context) ([]net.Address, bool) {
+	ips, ok := ctx.Value(resolvedIPsKey).([]net.Address)
+	return ips, ok
+}
+
+func ContextWithAllowPassiveConnection(ctx context.Context, allowPassiveConnection bool) context.Context {
+	return context.WithValue(ctx, allowPassiveConnKey, allowPassiveConnection)
+}
+
+func AllowPassiveConnectionFromContext(ctx context.Context) (bool, bool) {
+	allow, ok := ctx.Value(allowPassiveConnKey).(bool)
+	return allow, ok
 }

+ 21 - 144
proxy/dokodemo/dokodemo.go

@@ -2,7 +2,6 @@ package dokodemo
 
 import (
 	"context"
-	"sync"
 
 	"v2ray.com/core/app"
 	"v2ray.com/core/app/dispatcher"
@@ -14,21 +13,13 @@ import (
 	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
-	"v2ray.com/core/transport/internet/udp"
 )
 
 type DokodemoDoor struct {
-	tcpMutex         sync.RWMutex
-	udpMutex         sync.RWMutex
 	config           *Config
-	accepting        bool
 	address          net.Address
 	port             net.Port
 	packetDispatcher dispatcher.Interface
-	tcpListener      *internet.TCPHub
-	udpHub           *udp.Hub
-	udpServer        *udp.Server
-	meta             *proxy.InboundHandlerMeta
 }
 
 func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
@@ -36,10 +27,6 @@ func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
 	if space == nil {
 		return nil, errors.New("Dokodemo: No space in context.")
 	}
-	meta := proxy.InboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Dokodemo: No outbound meta in context.")
-	}
 	if config.NetworkList == nil || config.NetworkList.Size() == 0 {
 		return nil, errors.New("DokodemoDoor: No network specified.")
 	}
@@ -47,7 +34,6 @@ func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
 		config:  config,
 		address: config.GetPredefinedAddress(),
 		port:    net.Port(config.Port),
-		meta:    meta,
 	}
 	space.OnInitialize(func() error {
 		d.packetDispatcher = dispatcher.FromSpace(space)
@@ -59,140 +45,28 @@ func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
 	return d, nil
 }
 
-func (v *DokodemoDoor) Port() net.Port {
-	return v.meta.Port
-}
-
-func (v *DokodemoDoor) Close() {
-	v.accepting = false
-	if v.tcpListener != nil {
-		v.tcpMutex.Lock()
-		v.tcpListener.Close()
-		v.tcpListener = nil
-		v.tcpMutex.Unlock()
-	}
-	if v.udpHub != nil {
-		v.udpMutex.Lock()
-		v.udpHub.Close()
-		v.udpHub = nil
-		v.udpMutex.Unlock()
-	}
-}
-
-func (v *DokodemoDoor) Network() net.NetworkList {
-	return *(v.config.NetworkList)
-}
-
-func (v *DokodemoDoor) Start() error {
-	if v.accepting {
-		return nil
-	}
-	v.accepting = true
-
-	if v.config.NetworkList.HasNetwork(net.Network_TCP) {
-		err := v.ListenTCP()
-		if err != nil {
-			return err
-		}
-	}
-	if v.config.NetworkList.HasNetwork(net.Network_UDP) {
-		err := v.ListenUDP()
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (v *DokodemoDoor) ListenUDP() error {
-	v.udpServer = udp.NewServer(v.packetDispatcher)
-	udpHub, err := udp.ListenUDP(
-		v.meta.Address, v.meta.Port, udp.ListenOption{
-			Callback:            v.handleUDPPackets,
-			ReceiveOriginalDest: v.config.FollowRedirect,
-			Concurrency:         2,
-		})
-	if err != nil {
-		log.Error("Dokodemo failed to listen on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.udpMutex.Lock()
-	v.udpHub = udpHub
-	v.udpMutex.Unlock()
-	return nil
+func (d *DokodemoDoor) Network() net.NetworkList {
+	return *(d.config.NetworkList)
 }
 
-func (v *DokodemoDoor) handleUDPPackets(payload *buf.Buffer, session *proxy.SessionInfo) {
-	if session.Destination.Network == net.Network_Unknown && v.address != nil && v.port > 0 {
-		session.Destination = net.UDPDestination(v.address, v.port)
-	}
-	if session.Destination.Network == net.Network_Unknown {
-		log.Info("Dokodemo: Unknown destination, stop forwarding...")
-		return
-	}
-	session.Inbound = v.meta
-	v.udpServer.Dispatch(session, payload, v.handleUDPResponse)
-}
-
-func (v *DokodemoDoor) handleUDPResponse(dest net.Destination, payload *buf.Buffer) {
-	defer payload.Release()
-	v.udpMutex.RLock()
-	defer v.udpMutex.RUnlock()
-	if !v.accepting {
-		return
-	}
-	v.udpHub.WriteTo(payload.Bytes(), dest)
-}
-
-func (v *DokodemoDoor) ListenTCP() error {
-	tcpListener, err := internet.ListenTCP(v.meta.Address, v.meta.Port, v.HandleTCPConnection, v.meta.StreamSettings)
-	if err != nil {
-		log.Error("Dokodemo: Failed to listen on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.tcpMutex.Lock()
-	v.tcpListener = tcpListener
-	v.tcpMutex.Unlock()
-	return nil
-}
-
-func (v *DokodemoDoor) HandleTCPConnection(conn internet.Connection) {
-	defer conn.Close()
+func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection) error {
+	log.Debug("Dokodemo: processing connection from: ", conn.RemoteAddr())
 	conn.SetReusable(false)
-
-	var dest net.Destination
-	if v.config.FollowRedirect {
-		originalDest := GetOriginalDestination(conn)
-		if originalDest.Network != net.Network_Unknown {
-			log.Info("Dokodemo: Following redirect to: ", originalDest)
-			dest = originalDest
-		}
-	}
-	if dest.Network == net.Network_Unknown && v.address != nil && v.port > net.Port(0) {
-		dest = net.TCPDestination(v.address, v.port)
-	}
-
-	if dest.Network == net.Network_Unknown {
-		log.Info("Dokodemo: Unknown destination, stop forwarding...")
-		return
-	}
-	log.Info("Dokodemo: Handling request to ", dest)
-
-	ray := v.packetDispatcher.DispatchToOutbound(&proxy.SessionInfo{
-		Source:      net.DestinationFromAddr(conn.RemoteAddr()),
-		Destination: dest,
-		Inbound:     v.meta,
+	ctx = proxy.ContextWithDestination(ctx, net.Destination{
+		Network: network,
+		Address: d.address,
+		Port:    d.port,
 	})
-
-	reader := net.NewTimeOutReader(v.config.Timeout, conn)
+	inboundRay := d.packetDispatcher.DispatchToOutbound(ctx)
 
 	requestDone := signal.ExecuteAsync(func() error {
-		defer ray.InboundInput().Close()
+		defer inboundRay.InboundInput().Close()
 
-		v2reader := buf.NewReader(reader)
+		timedReader := net.NewTimeOutReader(d.config.Timeout, conn)
+		chunkReader := buf.NewReader(timedReader)
 
-		if err := buf.PipeUntilEOF(v2reader, ray.InboundInput()); err != nil {
-			log.Info("Dokodemo: Failed to transport all TCP request: ", err)
+		if err := buf.PipeUntilEOF(chunkReader, inboundRay.InboundInput()); err != nil {
+			log.Info("Dokodemo: Failed to transport request: ", err)
 			return err
 		}
 
@@ -202,18 +76,21 @@ func (v *DokodemoDoor) HandleTCPConnection(conn internet.Connection) {
 	responseDone := signal.ExecuteAsync(func() error {
 		v2writer := buf.NewWriter(conn)
 
-		if err := buf.PipeUntilEOF(ray.InboundOutput(), v2writer); err != nil {
-			log.Info("Dokodemo: Failed to transport all TCP response: ", err)
+		if err := buf.PipeUntilEOF(inboundRay.InboundOutput(), v2writer); err != nil {
+			log.Info("Dokodemo: Failed to transport response: ", err)
 			return err
 		}
 		return nil
 	})
 
 	if err := signal.ErrorOrFinish2(requestDone, responseDone); err != nil {
-		ray.InboundInput().CloseError()
-		ray.InboundOutput().CloseError()
+		inboundRay.InboundInput().CloseError()
+		inboundRay.InboundOutput().CloseError()
 		log.Info("Dokodemo: Connection ends with ", err)
+		return err
 	}
+
+	return nil
 }
 
 func init() {

+ 0 - 173
proxy/dokodemo/dokodemo_test.go

@@ -1,173 +0,0 @@
-package dokodemo_test
-
-import (
-	"net"
-	"testing"
-
-	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	_ "v2ray.com/core/app/dispatcher/impl"
-	"v2ray.com/core/app/proxyman"
-	_ "v2ray.com/core/app/proxyman/outbound"
-	"v2ray.com/core/common/dice"
-	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
-	. "v2ray.com/core/proxy/dokodemo"
-	"v2ray.com/core/proxy/freedom"
-	"v2ray.com/core/testing/assert"
-	"v2ray.com/core/testing/servers/tcp"
-	"v2ray.com/core/testing/servers/udp"
-	"v2ray.com/core/transport/internet"
-	_ "v2ray.com/core/transport/internet/tcp"
-)
-
-func TestDokodemoTCP(t *testing.T) {
-	assert := assert.On(t)
-
-	tcpServer := &tcp.Server{
-		MsgProcessor: func(data []byte) []byte {
-			buffer := make([]byte, 0, 2048)
-			buffer = append(buffer, []byte("Processed: ")...)
-			buffer = append(buffer, data...)
-			return buffer
-		},
-	}
-	_, err := tcpServer.Start()
-	assert.Error(err).IsNil()
-
-	defer tcpServer.Close()
-
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	app.AddApplicationToSpace(ctx, new(dispatcher.Config))
-	app.AddApplicationToSpace(ctx, new(proxyman.OutboundConfig))
-
-	ohm := proxyman.OutboundHandlerManagerFromSpace(space)
-	freedom, err := freedom.New(proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-		Address: v2net.LocalHostIP,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-	}), &freedom.Config{})
-	assert.Error(err).IsNil()
-	ohm.SetDefaultHandler(freedom)
-
-	data2Send := "Data to be sent to remote."
-
-	port := v2net.Port(dice.Roll(20000) + 10000)
-
-	ctx = proxy.ContextWithInboundMeta(ctx, &proxy.InboundHandlerMeta{
-		Address: v2net.LocalHostIP,
-		Port:    port,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		}})
-
-	dokodemo, err := New(ctx, &Config{
-		Address:     v2net.NewIPOrDomain(v2net.LocalHostIP),
-		Port:        uint32(tcpServer.Port),
-		NetworkList: v2net.Network_TCP.AsList(),
-		Timeout:     600,
-	})
-	assert.Error(err).IsNil()
-	defer dokodemo.Close()
-
-	assert.Error(space.Initialize()).IsNil()
-
-	err = dokodemo.Start()
-	assert.Error(err).IsNil()
-	assert.Port(port).Equals(dokodemo.Port())
-
-	tcpClient, err := net.DialTCP("tcp", nil, &net.TCPAddr{
-		IP:   []byte{127, 0, 0, 1},
-		Port: int(port),
-		Zone: "",
-	})
-	assert.Error(err).IsNil()
-
-	tcpClient.Write([]byte(data2Send))
-	tcpClient.CloseWrite()
-
-	response := make([]byte, 1024)
-	nBytes, err := tcpClient.Read(response)
-	assert.Error(err).IsNil()
-	tcpClient.Close()
-
-	assert.String("Processed: " + data2Send).Equals(string(response[:nBytes]))
-}
-
-func TestDokodemoUDP(t *testing.T) {
-	assert := assert.On(t)
-
-	udpServer := &udp.Server{
-		MsgProcessor: func(data []byte) []byte {
-			buffer := make([]byte, 0, 2048)
-			buffer = append(buffer, []byte("Processed: ")...)
-			buffer = append(buffer, data...)
-			return buffer
-		},
-	}
-	_, err := udpServer.Start()
-	assert.Error(err).IsNil()
-
-	defer udpServer.Close()
-
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	app.AddApplicationToSpace(ctx, new(dispatcher.Config))
-	app.AddApplicationToSpace(ctx, new(proxyman.OutboundConfig))
-
-	ohm := proxyman.OutboundHandlerManagerFromSpace(space)
-	freedom, err := freedom.New(proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-		Address: v2net.AnyIP,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-	}), &freedom.Config{})
-	assert.Error(err).IsNil()
-	ohm.SetDefaultHandler(freedom)
-
-	data2Send := "Data to be sent to remote."
-
-	port := v2net.Port(dice.Roll(20000) + 10000)
-
-	ctx = proxy.ContextWithInboundMeta(ctx, &proxy.InboundHandlerMeta{
-		Address: v2net.LocalHostIP,
-		Port:    port,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		}})
-
-	dokodemo, err := New(ctx, &Config{
-		Address:     v2net.NewIPOrDomain(v2net.LocalHostIP),
-		Port:        uint32(udpServer.Port),
-		NetworkList: v2net.Network_UDP.AsList(),
-		Timeout:     600,
-	})
-	assert.Error(err).IsNil()
-	defer dokodemo.Close()
-
-	assert.Error(space.Initialize()).IsNil()
-
-	err = dokodemo.Start()
-	assert.Error(err).IsNil()
-	assert.Port(port).Equals(dokodemo.Port())
-
-	udpClient, err := net.DialUDP("udp", nil, &net.UDPAddr{
-		IP:   []byte{127, 0, 0, 1},
-		Port: int(port),
-		Zone: "",
-	})
-	assert.Error(err).IsNil()
-	defer udpClient.Close()
-
-	udpClient.Write([]byte(data2Send))
-
-	response := make([]byte, 1024)
-	nBytes, addr, err := udpClient.ReadFromUDP(response)
-	assert.Error(err).IsNil()
-	assert.IP(addr.IP).Equals(v2net.LocalHostIP.IP())
-	assert.Bytes(response[:nBytes]).Equals([]byte("Processed: " + data2Send))
-}

+ 11 - 11
proxy/freedom/freedom.go

@@ -23,7 +23,6 @@ type Handler struct {
 	domainStrategy Config_DomainStrategy
 	timeout        uint32
 	dns            dns.Server
-	meta           *proxy.OutboundHandlerMeta
 }
 
 func New(ctx context.Context, config *Config) (*Handler, error) {
@@ -31,14 +30,9 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 	if space == nil {
 		return nil, errors.New("Freedom: No space in context.")
 	}
-	meta := proxy.OutboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Freedom: No outbound meta in context.")
-	}
 	f := &Handler{
 		domainStrategy: config.DomainStrategy,
 		timeout:        config.Timeout,
-		meta:           meta,
 	}
 	space.OnInitialize(func() error {
 		if config.DomainStrategy == Config_USE_IP {
@@ -75,18 +69,21 @@ func (v *Handler) ResolveIP(destination net.Destination) net.Destination {
 	return newDest
 }
 
-func (v *Handler) Dispatch(destination net.Destination, ray ray.OutboundRay) {
+func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay) error {
+	destination := proxy.DestinationFromContext(ctx)
 	log.Info("Freedom: Opening connection to ", destination)
 
-	input := ray.OutboundInput()
-	output := ray.OutboundOutput()
+	input := outboundRay.OutboundInput()
+	output := outboundRay.OutboundOutput()
 
 	var conn internet.Connection
 	if v.domainStrategy == Config_USE_IP && destination.Address.Family().IsDomain() {
 		destination = v.ResolveIP(destination)
 	}
+
+	dialer := proxy.DialerFromContext(ctx)
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
-		rawConn, err := internet.Dial(v.meta.Address, destination, v.meta.GetDialerOptions())
+		rawConn, err := dialer.Dial(ctx, destination)
 		if err != nil {
 			return err
 		}
@@ -95,7 +92,7 @@ func (v *Handler) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 	})
 	if err != nil {
 		log.Warning("Freedom: Failed to open connection to ", destination, ": ", err)
-		return
+		return err
 	}
 	defer conn.Close()
 
@@ -133,7 +130,10 @@ func (v *Handler) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 		log.Info("Freedom: Connection ending with ", err)
 		input.CloseError()
 		output.CloseError()
+		return err
 	}
+
+	return nil
 }
 
 func init() {

+ 0 - 97
proxy/freedom/freedom_test.go

@@ -1,97 +0,0 @@
-package freedom_test
-
-import (
-	"testing"
-
-	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	_ "v2ray.com/core/app/dispatcher/impl"
-	"v2ray.com/core/app/dns"
-	_ "v2ray.com/core/app/dns/server"
-	"v2ray.com/core/app/proxyman"
-	_ "v2ray.com/core/app/proxyman/outbound"
-	"v2ray.com/core/app/router"
-	"v2ray.com/core/common/buf"
-	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
-	. "v2ray.com/core/proxy/freedom"
-	"v2ray.com/core/testing/assert"
-	"v2ray.com/core/testing/servers/tcp"
-	"v2ray.com/core/transport/internet"
-	_ "v2ray.com/core/transport/internet/tcp"
-	"v2ray.com/core/transport/ray"
-)
-
-func TestSinglePacket(t *testing.T) {
-	assert := assert.On(t)
-
-	tcpServer := &tcp.Server{
-		MsgProcessor: func(data []byte) []byte {
-			buffer := make([]byte, 0, 2048)
-			buffer = append(buffer, []byte("Processed: ")...)
-			buffer = append(buffer, data...)
-			return buffer
-		},
-	}
-	tcpServerAddr, err := tcpServer.Start()
-	assert.Error(err).IsNil()
-
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	ctx = proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-		Address: v2net.AnyIP,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-	})
-	freedom, err := New(ctx, &Config{})
-	assert.Error(err).IsNil()
-	assert.Error(space.Initialize()).IsNil()
-
-	traffic := ray.NewRay()
-	data2Send := "Data to be sent to remote"
-	payload := buf.NewLocal(2048)
-	payload.Append([]byte(data2Send))
-	traffic.InboundInput().Write(payload)
-
-	go freedom.Dispatch(tcpServerAddr, traffic)
-	traffic.InboundInput().Close()
-
-	respPayload, err := traffic.InboundOutput().Read()
-	assert.Error(err).IsNil()
-	assert.String(respPayload.String()).Equals("Processed: Data to be sent to remote")
-
-	tcpServer.Close()
-}
-
-func TestIPResolution(t *testing.T) {
-	assert := assert.On(t)
-
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	assert.Error(app.AddApplicationToSpace(ctx, new(proxyman.OutboundConfig))).IsNil()
-	assert.Error(app.AddApplicationToSpace(ctx, new(dispatcher.Config))).IsNil()
-	assert.Error(app.AddApplicationToSpace(ctx, new(router.Config))).IsNil()
-	assert.Error(app.AddApplicationToSpace(ctx, &dns.Config{
-		Hosts: map[string]*v2net.IPOrDomain{
-			"v2ray.com": v2net.NewIPOrDomain(v2net.LocalHostIP),
-		},
-	})).IsNil()
-
-	ctx = proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-		Address: v2net.AnyIP,
-		StreamSettings: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_TCP,
-		},
-	})
-	freedom, err := New(ctx, &Config{DomainStrategy: Config_USE_IP})
-	assert.Error(err).IsNil()
-
-	assert.Error(space.Initialize()).IsNil()
-
-	ipDest := freedom.ResolveIP(v2net.TCPDestination(v2net.DomainAddress("v2ray.com"), v2net.Port(80)))
-	assert.Destination(ipDest).IsTCP()
-	assert.Address(ipDest.Address).Equals(v2net.LocalHostIP)
-}

+ 23 - 66
proxy/http/server.go

@@ -25,11 +25,8 @@ import (
 // Server is a HTTP proxy server.
 type Server struct {
 	sync.Mutex
-	accepting        bool
 	packetDispatcher dispatcher.Interface
 	config           *ServerConfig
-	tcpListener      *internet.TCPHub
-	meta             *proxy.InboundHandlerMeta
 }
 
 // NewServer creates a new HTTP inbound handler.
@@ -38,13 +35,8 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	if space == nil {
 		return nil, errors.New("HTTP|Server: No space in context.")
 	}
-	meta := proxy.InboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("HTTP|Server: No inbound meta from context.")
-	}
 	s := &Server{
 		config: config,
-		meta:   meta,
 	}
 	space.OnInitialize(func() error {
 		s.packetDispatcher = dispatcher.FromSpace(space)
@@ -56,46 +48,12 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	return s, nil
 }
 
-// Port implements InboundHandler.Port().
-func (v *Server) Port() v2net.Port {
-	return v.meta.Port
-}
-
-func (v *Server) Network() v2net.NetworkList {
+func (*Server) Network() v2net.NetworkList {
 	return v2net.NetworkList{
 		Network: []v2net.Network{v2net.Network_TCP},
 	}
 }
 
-// Close implements InboundHandler.Close().
-func (v *Server) Close() {
-	v.accepting = false
-	if v.tcpListener != nil {
-		v.Lock()
-		v.tcpListener.Close()
-		v.tcpListener = nil
-		v.Unlock()
-	}
-}
-
-// Start implements InboundHandler.Start().
-func (v *Server) Start() error {
-	if v.accepting {
-		return nil
-	}
-
-	tcpListener, err := internet.ListenTCP(v.meta.Address, v.meta.Port, v.handleConnection, v.meta.StreamSettings)
-	if err != nil {
-		log.Error("HTTP: Failed listen on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.Lock()
-	v.tcpListener = tcpListener
-	v.Unlock()
-	v.accepting = true
-	return nil
-}
-
 func parseHost(rawHost string, defaultPort v2net.Port) (v2net.Destination, error) {
 	port := defaultPort
 	host, rawPort, err := net.SplitHostPort(rawHost)
@@ -119,11 +77,10 @@ func parseHost(rawHost string, defaultPort v2net.Port) (v2net.Destination, error
 	return v2net.TCPDestination(v2net.DomainAddress(host), port), nil
 }
 
-func (v *Server) handleConnection(conn internet.Connection) {
-	defer conn.Close()
+func (s *Server) Process(ctx context.Context, network v2net.Network, conn internet.Connection) error {
 	conn.SetReusable(false)
 
-	timedReader := v2net.NewTimeOutReader(v.config.Timeout, conn)
+	timedReader := v2net.NewTimeOutReader(s.config.Timeout, conn)
 	reader := bufio.OriginalReaderSize(timedReader, 2048)
 
 	request, err := http.ReadRequest(reader)
@@ -131,7 +88,7 @@ func (v *Server) handleConnection(conn internet.Connection) {
 		if errors.Cause(err) != io.EOF {
 			log.Warning("HTTP: Failed to read http request: ", err)
 		}
-		return
+		return err
 	}
 	log.Info("HTTP: Request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]")
 	defaultPort := v2net.Port(80)
@@ -145,22 +102,18 @@ func (v *Server) handleConnection(conn internet.Connection) {
 	dest, err := parseHost(host, defaultPort)
 	if err != nil {
 		log.Warning("HTTP: Malformed proxy host (", host, "): ", err)
-		return
+		return err
 	}
 	log.Access(conn.RemoteAddr(), request.URL, log.AccessAccepted, "")
-	session := &proxy.SessionInfo{
-		Source:      v2net.DestinationFromAddr(conn.RemoteAddr()),
-		Destination: dest,
-		Inbound:     v.meta,
-	}
+	ctx = proxy.ContextWithDestination(ctx, dest)
 	if strings.ToUpper(request.Method) == "CONNECT" {
-		v.handleConnect(request, session, reader, conn)
+		return s.handleConnect(ctx, request, reader, conn)
 	} else {
-		v.handlePlainHTTP(request, session, reader, conn)
+		return s.handlePlainHTTP(ctx, request, reader, conn)
 	}
 }
 
-func (v *Server) handleConnect(request *http.Request, session *proxy.SessionInfo, reader io.Reader, writer io.Writer) {
+func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader io.Reader, writer io.Writer) error {
 	response := &http.Response{
 		Status:        "200 OK",
 		StatusCode:    200,
@@ -174,10 +127,10 @@ func (v *Server) handleConnect(request *http.Request, session *proxy.SessionInfo
 	}
 	if err := response.Write(writer); err != nil {
 		log.Warning("HTTP|Server: failed to write back OK response: ", err)
-		return
+		return err
 	}
 
-	ray := v.packetDispatcher.DispatchToOutbound(session)
+	ray := s.packetDispatcher.DispatchToOutbound(ctx)
 
 	requestDone := signal.ExecuteAsync(func() error {
 		defer ray.InboundInput().Close()
@@ -201,7 +154,10 @@ func (v *Server) handleConnect(request *http.Request, session *proxy.SessionInfo
 		log.Info("HTTP|Server: Connection ends with: ", err)
 		ray.InboundInput().CloseError()
 		ray.InboundOutput().CloseError()
+		return err
 	}
+
+	return nil
 }
 
 // @VisibleForTesting
@@ -229,7 +185,7 @@ func StripHopByHopHeaders(request *http.Request) {
 	}
 }
 
-func (v *Server) GenerateResponse(statusCode int, status string) *http.Response {
+func generateResponse(statusCode int, status string) *http.Response {
 	hdr := http.Header(make(map[string][]string))
 	hdr.Set("Connection", "close")
 	return &http.Response{
@@ -245,18 +201,16 @@ func (v *Server) GenerateResponse(statusCode int, status string) *http.Response
 	}
 }
 
-func (v *Server) handlePlainHTTP(request *http.Request, session *proxy.SessionInfo, reader io.Reader, writer io.Writer) {
+func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, reader io.Reader, writer io.Writer) error {
 	if len(request.URL.Host) <= 0 {
-		response := v.GenerateResponse(400, "Bad Request")
-		response.Write(writer)
-
-		return
+		response := generateResponse(400, "Bad Request")
+		return response.Write(writer)
 	}
 
 	request.Host = request.URL.Host
 	StripHopByHopHeaders(request)
 
-	ray := v.packetDispatcher.DispatchToOutbound(session)
+	ray := s.packetDispatcher.DispatchToOutbound(ctx)
 	input := ray.InboundInput()
 	output := ray.InboundOutput()
 
@@ -279,7 +233,7 @@ func (v *Server) handlePlainHTTP(request *http.Request, session *proxy.SessionIn
 		response, err := http.ReadResponse(responseReader, request)
 		if err != nil {
 			log.Warning("HTTP: Failed to read response: ", err)
-			response = v.GenerateResponse(503, "Service Unavailable")
+			response = generateResponse(503, "Service Unavailable")
 		}
 		responseWriter := bufio.NewWriter(writer)
 		if err := response.Write(responseWriter); err != nil {
@@ -296,7 +250,10 @@ func (v *Server) handlePlainHTTP(request *http.Request, session *proxy.SessionIn
 		log.Info("HTTP|Server: Connecton ending with ", err)
 		input.CloseError()
 		output.CloseError()
+		return err
 	}
+
+	return nil
 }
 
 func init() {

+ 3 - 46
proxy/proxy.go

@@ -5,63 +5,20 @@ import (
 	"context"
 
 	"v2ray.com/core/common/net"
-	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/ray"
 )
 
-type HandlerState int
-
-const (
-	HandlerStateStopped = HandlerState(0)
-	HandlerStateRunning = HandlerState(1)
-)
-
-type SessionInfo struct {
-	Source      net.Destination
-	Destination net.Destination
-	User        *protocol.User
-	Inbound     *InboundHandlerMeta
-}
-
-type InboundHandlerMeta struct {
-	Tag                    string
-	Address                net.Address
-	Port                   net.Port
-	AllowPassiveConnection bool
-	StreamSettings         *internet.StreamConfig
-}
-
-type OutboundHandlerMeta struct {
-	Tag            string
-	Address        net.Address
-	StreamSettings *internet.StreamConfig
-	ProxySettings  *internet.ProxyConfig
-}
-
-func (v *OutboundHandlerMeta) GetDialerOptions() internet.DialerOptions {
-	return internet.DialerOptions{
-		Stream: v.StreamSettings,
-		Proxy:  v.ProxySettings,
-	}
-}
-
 // An InboundHandler handles inbound network connections to V2Ray.
 type InboundHandler interface {
-	// 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.
-	Port() net.Port
-
 	Network() net.NetworkList
+
+	Process(context.Context, net.Network, internet.Connection) error
 }
 
 // An OutboundHandler handles outbound network connection for V2Ray.
 type OutboundHandler interface {
-	// Dispatch sends one or more Packets to its destination.
-	Dispatch(destination net.Destination, ray ray.OutboundRay)
+	Process(context.Context, ray.OutboundRay) error
 }
 
 // Dialer is used by OutboundHandler for creating outbound connections.

+ 26 - 23
proxy/shadowsocks/client.go

@@ -2,7 +2,6 @@ package shadowsocks
 
 import (
 	"context"
-	"errors"
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
@@ -20,39 +19,35 @@ import (
 // Client is a inbound handler for Shadowsocks protocol
 type Client struct {
 	serverPicker protocol.ServerPicker
-	meta         *proxy.OutboundHandlerMeta
 }
 
 // NewClient create a new Shadowsocks client.
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	meta := proxy.OutboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Shadowsocks|Client: No outbound meta in context.")
-	}
 	serverList := protocol.NewServerList()
 	for _, rec := range config.Server {
 		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
 	}
 	client := &Client{
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
-		meta:         meta,
 	}
 
 	return client, nil
 }
 
-// Dispatch implements OutboundHandler.Dispatch().
-func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
+// Process implements OutboundHandler.Process().
+func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay) error {
+	destination := proxy.DestinationFromContext(ctx)
 	network := destination.Network
 
 	var server *protocol.ServerSpec
 	var conn internet.Connection
 
+	dialer := proxy.DialerFromContext(ctx)
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
 		server = v.serverPicker.PickServer()
 		dest := server.Destination()
 		dest.Network = network
-		rawConn, err := internet.Dial(v.meta.Address, dest, v.meta.GetDialerOptions())
+		rawConn, err := dialer.Dial(ctx, dest)
 		if err != nil {
 			return err
 		}
@@ -62,7 +57,7 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 	})
 	if err != nil {
 		log.Warning("Shadowsocks|Client: Failed to find an available destination:", err)
-		return
+		return err
 	}
 	log.Info("Shadowsocks|Client: Tunneling request to ", destination, " via ", server.Destination())
 
@@ -83,7 +78,7 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 	rawAccount, err := user.GetTypedAccount()
 	if err != nil {
 		log.Warning("Shadowsocks|Client: Failed to get a valid user account: ", err)
-		return
+		return err
 	}
 	account := rawAccount.(*ShadowsocksAccount)
 	request.User = user
@@ -97,27 +92,27 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 		bodyWriter, err := WriteTCPRequest(request, bufferedWriter)
 		if err != nil {
 			log.Info("Shadowsocks|Client: Failed to write request: ", err)
-			return
+			return err
 		}
 
 		bufferedWriter.SetBuffered(false)
 
 		requestDone := signal.ExecuteAsync(func() error {
-			if err := buf.PipeUntilEOF(ray.OutboundInput(), bodyWriter); err != nil {
+			if err := buf.PipeUntilEOF(outboundRay.OutboundInput(), bodyWriter); err != nil {
 				return err
 			}
 			return nil
 		})
 
 		responseDone := signal.ExecuteAsync(func() error {
-			defer ray.OutboundOutput().Close()
+			defer outboundRay.OutboundOutput().Close()
 
 			responseReader, err := ReadTCPResponse(user, conn)
 			if err != nil {
 				return err
 			}
 
-			if err := buf.PipeUntilEOF(responseReader, ray.OutboundOutput()); err != nil {
+			if err := buf.PipeUntilEOF(responseReader, outboundRay.OutboundOutput()); err != nil {
 				return err
 			}
 
@@ -126,9 +121,12 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 
 		if err := signal.ErrorOrFinish2(requestDone, responseDone); err != nil {
 			log.Info("Shadowsocks|Client: Connection ends with ", err)
-			ray.OutboundInput().CloseError()
-			ray.OutboundOutput().CloseError()
+			outboundRay.OutboundInput().CloseError()
+			outboundRay.OutboundOutput().CloseError()
+			return err
 		}
+
+		return nil
 	}
 
 	if request.Command == protocol.RequestCommandUDP {
@@ -139,7 +137,7 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 		}
 
 		requestDone := signal.ExecuteAsync(func() error {
-			if err := buf.PipeUntilEOF(ray.OutboundInput(), writer); err != nil {
+			if err := buf.PipeUntilEOF(outboundRay.OutboundInput(), writer); err != nil {
 				log.Info("Shadowsocks|Client: Failed to transport all UDP request: ", err)
 				return err
 			}
@@ -149,14 +147,14 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 		timedReader := net.NewTimeOutReader(16, conn)
 
 		responseDone := signal.ExecuteAsync(func() error {
-			defer ray.OutboundOutput().Close()
+			defer outboundRay.OutboundOutput().Close()
 
 			reader := &UDPReader{
 				Reader: timedReader,
 				User:   user,
 			}
 
-			if err := buf.PipeUntilEOF(reader, ray.OutboundOutput()); err != nil {
+			if err := buf.PipeUntilEOF(reader, outboundRay.OutboundOutput()); err != nil {
 				log.Info("Shadowsocks|Client: Failed to transport all UDP response: ", err)
 				return err
 			}
@@ -165,10 +163,15 @@ func (v *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 
 		if err := signal.ErrorOrFinish2(requestDone, responseDone); err != nil {
 			log.Info("Shadowsocks|Client: Connection ends with ", err)
-			ray.OutboundInput().CloseError()
-			ray.OutboundOutput().CloseError()
+			outboundRay.OutboundInput().CloseError()
+			outboundRay.OutboundOutput().CloseError()
+			return err
 		}
+
+		return nil
 	}
+
+	return nil
 }
 
 func init() {

+ 59 - 91
proxy/shadowsocks/server.go

@@ -23,10 +23,6 @@ type Server struct {
 	config           *ServerConfig
 	user             *protocol.User
 	account          *ShadowsocksAccount
-	meta             *proxy.InboundHandlerMeta
-	accepting        bool
-	tcpHub           *internet.TCPHub
-	udpHub           *udp.Hub
 	udpServer        *udp.Server
 }
 
@@ -35,10 +31,6 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	if space == nil {
 		return nil, errors.New("Shadowsocks|Server: No space in context.")
 	}
-	meta := proxy.InboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Shadowsocks|Server: No inbound meta in context.")
-	}
 	if config.GetUser() == nil {
 		return nil, protocol.ErrUserMissing
 	}
@@ -51,7 +43,6 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 
 	s := &Server{
 		config:  config,
-		meta:    meta,
 		user:    config.GetUser(),
 		account: account,
 	}
@@ -67,129 +58,103 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	return s, nil
 }
 
-func (v *Server) Network() net.NetworkList {
+func (s *Server) Network() net.NetworkList {
 	list := net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
 	}
-	if v.config.UdpEnabled {
+	if s.config.UdpEnabled {
 		list.Network = append(list.Network, net.Network_UDP)
 	}
 	return list
 }
 
-func (v *Server) Port() net.Port {
-	return v.meta.Port
-}
-
-func (v *Server) Close() {
-	v.accepting = false
-	// TODO: synchronization
-	if v.tcpHub != nil {
-		v.tcpHub.Close()
-		v.tcpHub = nil
-	}
-
-	if v.udpHub != nil {
-		v.udpHub.Close()
-		v.udpHub = nil
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection) error {
+	switch network {
+	case net.Network_TCP:
+		return s.handleConnection(ctx, conn)
+	case net.Network_UDP:
+		return s.handlerUDPPayload(ctx, conn)
+	default:
+		return errors.New("Shadowsocks|Server: Unknown network: ", network)
 	}
 }
 
-func (v *Server) Start() error {
-	if v.accepting {
-		return nil
-	}
-
-	tcpHub, err := internet.ListenTCP(v.meta.Address, v.meta.Port, v.handleConnection, v.meta.StreamSettings)
-	if err != nil {
-		log.Error("Shadowsocks: Failed to listen TCP on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.tcpHub = tcpHub
+func (v *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection) error {
+	source := proxy.SourceFromContext(ctx)
 
-	if v.config.UdpEnabled {
-		v.udpServer = udp.NewServer(v.packetDispatcher)
-		udpHub, err := udp.ListenUDP(v.meta.Address, v.meta.Port, udp.ListenOption{Callback: v.handlerUDPPayload})
+	reader := buf.NewReader(conn)
+	for {
+		payload, err := reader.Read()
 		if err != nil {
-			log.Error("Shadowsocks: Failed to listen UDP on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-			return err
+			break
 		}
-		v.udpHub = udpHub
-	}
 
-	v.accepting = true
-
-	return nil
-}
+		request, data, err := DecodeUDPPacket(v.user, payload)
+		if err != nil {
+			log.Info("Shadowsocks|Server: Skipping invalid UDP packet from: ", source, ": ", err)
+			log.Access(source, "", log.AccessRejected, err)
+			payload.Release()
+			continue
+		}
 
-func (v *Server) handlerUDPPayload(payload *buf.Buffer, session *proxy.SessionInfo) {
-	source := session.Source
-	request, data, err := DecodeUDPPacket(v.user, payload)
-	if err != nil {
-		log.Info("Shadowsocks|Server: Skipping invalid UDP packet from: ", source, ": ", err)
-		log.Access(source, "", log.AccessRejected, err)
-		payload.Release()
-		return
-	}
+		if request.Option.Has(RequestOptionOneTimeAuth) && v.account.OneTimeAuth == Account_Disabled {
+			log.Info("Shadowsocks|Server: Client payload enables OTA but server doesn't allow it.")
+			payload.Release()
+			continue
+		}
 
-	if request.Option.Has(RequestOptionOneTimeAuth) && v.account.OneTimeAuth == Account_Disabled {
-		log.Info("Shadowsocks|Server: Client payload enables OTA but server doesn't allow it.")
-		payload.Release()
-		return
-	}
+		if !request.Option.Has(RequestOptionOneTimeAuth) && v.account.OneTimeAuth == Account_Enabled {
+			log.Info("Shadowsocks|Server: Client payload disables OTA but server forces it.")
+			payload.Release()
+			continue
+		}
 
-	if !request.Option.Has(RequestOptionOneTimeAuth) && v.account.OneTimeAuth == Account_Enabled {
-		log.Info("Shadowsocks|Server: Client payload disables OTA but server forces it.")
-		payload.Release()
-		return
-	}
+		dest := request.Destination()
+		log.Access(source, dest, log.AccessAccepted, "")
+		log.Info("Shadowsocks|Server: Tunnelling request to ", dest)
 
-	dest := request.Destination()
-	log.Access(source, dest, log.AccessAccepted, "")
-	log.Info("Shadowsocks|Server: Tunnelling request to ", dest)
+		ctx = protocol.ContextWithUser(ctx, request.User)
+		v.udpServer.Dispatch(ctx, dest, data, func(payload *buf.Buffer) {
+			defer payload.Release()
 
-	v.udpServer.Dispatch(&proxy.SessionInfo{Source: source, Destination: dest, User: request.User, Inbound: v.meta}, data, func(destination net.Destination, payload *buf.Buffer) {
-		defer payload.Release()
+			data, err := EncodeUDPPacket(request, payload)
+			if err != nil {
+				log.Warning("Shadowsocks|Server: Failed to encode UDP packet: ", err)
+				return
+			}
+			defer data.Release()
 
-		data, err := EncodeUDPPacket(request, payload)
-		if err != nil {
-			log.Warning("Shadowsocks|Server: Failed to encode UDP packet: ", err)
-			return
-		}
-		defer data.Release()
+			conn.Write(data.Bytes())
+		})
+	}
 
-		v.udpHub.WriteTo(data.Bytes(), source)
-	})
+	return nil
 }
 
-func (v *Server) handleConnection(conn internet.Connection) {
-	defer conn.Close()
+func (s *Server) handleConnection(ctx context.Context, conn internet.Connection) error {
 	conn.SetReusable(false)
 
 	timedReader := net.NewTimeOutReader(16, conn)
 	bufferedReader := bufio.NewReader(timedReader)
-	request, bodyReader, err := ReadTCPSession(v.user, bufferedReader)
+	request, bodyReader, err := ReadTCPSession(s.user, bufferedReader)
 	if err != nil {
 		log.Access(conn.RemoteAddr(), "", log.AccessRejected, err)
 		log.Info("Shadowsocks|Server: Failed to create request from: ", conn.RemoteAddr(), ": ", err)
-		return
+		return err
 	}
 
 	bufferedReader.SetBuffered(false)
 
-	userSettings := v.user.GetSettings()
+	userSettings := s.user.GetSettings()
 	timedReader.SetTimeOut(userSettings.PayloadReadTimeout)
 
 	dest := request.Destination()
 	log.Access(conn.RemoteAddr(), dest, log.AccessAccepted, "")
 	log.Info("Shadowsocks|Server: Tunnelling request to ", dest)
 
-	ray := v.packetDispatcher.DispatchToOutbound(&proxy.SessionInfo{
-		Source:      net.DestinationFromAddr(conn.RemoteAddr()),
-		Destination: dest,
-		User:        request.User,
-		Inbound:     v.meta,
-	})
+	ctx = proxy.ContextWithDestination(ctx, dest)
+	ctx = protocol.ContextWithUser(ctx, request.User)
+	ray := s.packetDispatcher.DispatchToOutbound(ctx)
 
 	requestDone := signal.ExecuteAsync(func() error {
 		bufferedWriter := bufio.NewWriter(conn)
@@ -234,7 +199,10 @@ func (v *Server) handleConnection(conn internet.Connection) {
 		log.Info("Shadowsocks|Server: Connection ends with ", err)
 		ray.InboundInput().CloseError()
 		ray.InboundOutput().CloseError()
+		return err
 	}
+
+	return nil
 }
 
 func init() {

+ 12 - 13
proxy/socks/client.go

@@ -2,7 +2,6 @@ package socks
 
 import (
 	"context"
-	"errors"
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
@@ -18,34 +17,31 @@ import (
 
 type Client struct {
 	serverPicker protocol.ServerPicker
-	meta         *proxy.OutboundHandlerMeta
 }
 
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	meta := proxy.OutboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Socks|Client: No outbound meta in context.")
-	}
 	serverList := protocol.NewServerList()
 	for _, rec := range config.Server {
 		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
 	}
 	client := &Client{
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
-		meta:         meta,
 	}
 
 	return client, nil
 }
 
-func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
+func (c *Client) Process(ctx context.Context, ray ray.OutboundRay) error {
+	destination := proxy.DestinationFromContext(ctx)
+
 	var server *protocol.ServerSpec
 	var conn internet.Connection
 
+	dialer := proxy.DialerFromContext(ctx)
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
 		server = c.serverPicker.PickServer()
 		dest := server.Destination()
-		rawConn, err := internet.Dial(c.meta.Address, dest, c.meta.GetDialerOptions())
+		rawConn, err := dialer.Dial(ctx, dest)
 		if err != nil {
 			return err
 		}
@@ -56,7 +52,7 @@ func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 
 	if err != nil {
 		log.Warning("Socks|Client: Failed to find an available destination.")
-		return
+		return err
 	}
 
 	defer conn.Close()
@@ -80,7 +76,7 @@ func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 	udpRequest, err := ClientHandshake(request, conn, conn)
 	if err != nil {
 		log.Warning("Socks|Client: Failed to establish connection to server: ", err)
-		return
+		return err
 	}
 
 	var requestFunc func() error
@@ -94,10 +90,10 @@ func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 			return buf.PipeUntilEOF(buf.NewReader(conn), ray.OutboundOutput())
 		}
 	} else if request.Command == protocol.RequestCommandUDP {
-		udpConn, err := internet.Dial(c.meta.Address, udpRequest.Destination(), c.meta.GetDialerOptions())
+		udpConn, err := dialer.Dial(ctx, udpRequest.Destination())
 		if err != nil {
 			log.Info("Socks|Client: Failed to create UDP connection: ", err)
-			return
+			return err
 		}
 		defer udpConn.Close()
 		requestFunc = func() error {
@@ -116,7 +112,10 @@ func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) {
 		log.Info("Socks|Client: Connection ends with ", err)
 		ray.OutboundInput().CloseError()
 		ray.OutboundOutput().CloseError()
+		return err
 	}
+
+	return nil
 }
 
 func init() {

+ 2 - 3
proxy/socks/protocol.go

@@ -8,7 +8,6 @@ import (
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
-	"v2ray.com/core/proxy"
 )
 
 const (
@@ -37,7 +36,7 @@ const (
 
 type ServerSession struct {
 	config *ServerConfig
-	meta   *proxy.InboundHandlerMeta
+	port   v2net.Port
 }
 
 func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
@@ -178,7 +177,7 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol
 				addr = v2net.LocalHostIP
 			}
 			responseAddress = addr
-			responsePort = s.meta.Port
+			responsePort = s.port
 		}
 		if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {
 			return nil, err

+ 75 - 87
proxy/socks/server.go

@@ -3,7 +3,6 @@ package socks
 import (
 	"context"
 	"io"
-	"sync"
 	"time"
 
 	"v2ray.com/core/app"
@@ -23,16 +22,9 @@ import (
 
 // Server is a SOCKS 5 proxy server
 type Server struct {
-	tcpMutex         sync.RWMutex
-	udpMutex         sync.RWMutex
-	accepting        bool
 	packetDispatcher dispatcher.Interface
 	config           *ServerConfig
-	tcpListener      *internet.TCPHub
-	udpHub           *udp.Hub
-	udpAddress       net.Destination
 	udpServer        *udp.Server
-	meta             *proxy.InboundHandlerMeta
 }
 
 // NewServer creates a new Server object.
@@ -41,135 +33,89 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	if space == nil {
 		return nil, errors.New("Socks|Server: No space in context.")
 	}
-	meta := proxy.InboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("Socks|Server: No inbound meta in context.")
-	}
 	s := &Server{
 		config: config,
-		meta:   meta,
 	}
 	space.OnInitialize(func() error {
 		s.packetDispatcher = dispatcher.FromSpace(space)
 		if s.packetDispatcher == nil {
 			return errors.New("Socks|Server: Dispatcher is not found in the space.")
 		}
+		s.udpServer = udp.NewServer(s.packetDispatcher)
 		return nil
 	})
 	return s, nil
 }
 
-func (v *Server) Network() net.NetworkList {
+func (s *Server) Network() net.NetworkList {
 	list := net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
 	}
-	if v.config.UdpEnabled {
+	if s.config.UdpEnabled {
 		list.Network = append(list.Network, net.Network_UDP)
 	}
 	return list
 }
 
-// Port implements InboundHandler.Port().
-func (v *Server) Port() net.Port {
-	return v.meta.Port
-}
-
-// Close implements InboundHandler.Close().
-func (v *Server) Close() {
-	v.accepting = false
-	if v.tcpListener != nil {
-		v.tcpMutex.Lock()
-		v.tcpListener.Close()
-		v.tcpListener = nil
-		v.tcpMutex.Unlock()
-	}
-	if v.udpHub != nil {
-		v.udpMutex.Lock()
-		v.udpHub.Close()
-		v.udpHub = nil
-		v.udpMutex.Unlock()
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection) error {
+	switch network {
+	case net.Network_TCP:
+		return s.processTCP(ctx, conn)
+	case net.Network_UDP:
+		return s.handleUDPPayload(ctx, conn)
+	default:
+		return errors.New("Socks|Server: Unknown network: ", network)
 	}
 }
 
-// Start implements InboundHandler.Start().
-func (v *Server) Start() error {
-	if v.accepting {
-		return nil
-	}
+func (s *Server) processTCP(ctx context.Context, conn internet.Connection) error {
+	conn.SetReusable(false)
 
-	listener, err := internet.ListenTCP(
-		v.meta.Address,
-		v.meta.Port,
-		v.handleConnection,
-		v.meta.StreamSettings)
-	if err != nil {
-		log.Error("Socks: failed to listen on ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.accepting = true
-	v.tcpMutex.Lock()
-	v.tcpListener = listener
-	v.tcpMutex.Unlock()
-	if v.config.UdpEnabled {
-		if err := v.listenUDP(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (v *Server) handleConnection(connection internet.Connection) {
-	defer connection.Close()
-
-	connection.SetReusable(false)
-
-	timedReader := net.NewTimeOutReader(16 /* seconds, for handshake */, connection)
+	timedReader := net.NewTimeOutReader(16 /* seconds, for handshake */, conn)
 	reader := bufio.NewReader(timedReader)
 
+	inboundDest := proxy.InboundDestinationFromContext(ctx)
 	session := &ServerSession{
-		config: v.config,
-		meta:   v.meta,
+		config: s.config,
+		port:   inboundDest.Port,
 	}
 
-	clientAddr := net.DestinationFromAddr(connection.RemoteAddr())
-
-	request, err := session.Handshake(reader, connection)
+	source := proxy.SourceFromContext(ctx)
+	request, err := session.Handshake(reader, conn)
 	if err != nil {
-		log.Access(clientAddr, "", log.AccessRejected, err)
+		log.Access(source, "", log.AccessRejected, err)
 		log.Info("Socks|Server: Failed to read request: ", err)
-		return
+		return err
 	}
 
 	if request.Command == protocol.RequestCommandTCP {
 		dest := request.Destination()
-		session := &proxy.SessionInfo{
-			Source:      clientAddr,
-			Destination: dest,
-			Inbound:     v.meta,
-		}
 		log.Info("Socks|Server: TCP Connect request to ", dest)
-		log.Access(clientAddr, dest, log.AccessAccepted, "")
+		log.Access(source, dest, log.AccessAccepted, "")
 
-		timedReader.SetTimeOut(v.config.Timeout)
-		v.transport(reader, connection, session)
-		return
+		timedReader.SetTimeOut(s.config.Timeout)
+		ctx = proxy.ContextWithDestination(ctx, dest)
+		return s.transport(ctx, reader, conn)
 	}
 
 	if request.Command == protocol.RequestCommandUDP {
-		v.handleUDP()
-		return
+		return s.handleUDP()
 	}
+
+	return nil
 }
 
-func (v *Server) handleUDP() {
+func (*Server) handleUDP() error {
 	// The TCP connection closes after v method returns. We need to wait until
 	// the client closes it.
 	// TODO: get notified from UDP part
 	<-time.After(5 * time.Minute)
+
+	return nil
 }
 
-func (v *Server) transport(reader io.Reader, writer io.Writer, session *proxy.SessionInfo) {
-	ray := v.packetDispatcher.DispatchToOutbound(session)
+func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer) error {
+	ray := v.packetDispatcher.DispatchToOutbound(ctx)
 	input := ray.InboundInput()
 	output := ray.InboundOutput()
 
@@ -198,6 +144,48 @@ func (v *Server) transport(reader io.Reader, writer io.Writer, session *proxy.Se
 		log.Info("Socks|Server: Connection ends with ", err)
 		input.CloseError()
 		output.CloseError()
+		return err
+	}
+
+	return nil
+}
+
+func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection) error {
+	source := proxy.SourceFromContext(ctx)
+	log.Info("Socks|Server: Client UDP connection from ", source)
+
+	reader := buf.NewReader(conn)
+	for {
+		payload, err := reader.Read()
+		if err != nil {
+			return err
+		}
+		request, data, err := DecodeUDPPacket(payload.Bytes())
+
+		if err != nil {
+			log.Info("Socks|Server: Failed to parse UDP request: ", err)
+			continue
+		}
+
+		if len(data) == 0 {
+			continue
+		}
+
+		log.Info("Socks: Send packet to ", request.Destination(), " with ", len(data), " bytes")
+		log.Access(source, request.Destination, log.AccessAccepted, "")
+
+		dataBuf := buf.NewSmall()
+		dataBuf.Append(data)
+		v.udpServer.Dispatch(ctx, request.Destination(), dataBuf, func(payload *buf.Buffer) {
+			defer payload.Release()
+
+			log.Info("Socks|Server: Writing back UDP response with ", payload.Len(), " bytes")
+
+			udpMessage := EncodeUDPPacket(request, payload.Bytes())
+			defer udpMessage.Release()
+
+			conn.Write(udpMessage.Bytes())
+		})
 	}
 }
 

+ 0 - 66
proxy/socks/server_udp.go

@@ -1,66 +0,0 @@
-package socks
-
-import (
-	"v2ray.com/core/common/buf"
-	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
-	"v2ray.com/core/transport/internet/udp"
-)
-
-func (v *Server) listenUDP() error {
-	v.udpServer = udp.NewServer(v.packetDispatcher)
-	udpHub, err := udp.ListenUDP(v.meta.Address, v.meta.Port, udp.ListenOption{Callback: v.handleUDPPayload})
-	if err != nil {
-		log.Error("Socks: Failed to listen on udp (", v.meta.Address, ":", v.meta.Port, "): ", err)
-		return err
-	}
-	v.udpMutex.Lock()
-	v.udpAddress = net.UDPDestination(v.config.GetNetAddress(), v.meta.Port)
-	v.udpHub = udpHub
-	v.udpMutex.Unlock()
-	return nil
-}
-
-func (v *Server) handleUDPPayload(payload *buf.Buffer, session *proxy.SessionInfo) {
-	defer payload.Release()
-
-	source := session.Source
-	log.Info("Socks: Client UDP connection from ", source)
-	request, data, err := DecodeUDPPacket(payload.Bytes())
-
-	if err != nil {
-		log.Error("Socks|Server: Failed to parse UDP request: ", err)
-		return
-	}
-
-	if len(data) == 0 {
-		return
-	}
-
-	log.Info("Socks: Send packet to ", request.Destination(), " with ", len(data), " bytes")
-	log.Access(source, request.Destination, log.AccessAccepted, "")
-
-	dataBuf := buf.NewSmall()
-	dataBuf.Append(data)
-	v.udpServer.Dispatch(&proxy.SessionInfo{Source: source, Destination: request.Destination(), Inbound: v.meta}, dataBuf, func(destination net.Destination, payload *buf.Buffer) {
-		defer payload.Release()
-
-		log.Info("Socks: Writing back UDP response with ", payload.Len(), " bytes to ", destination)
-
-		udpMessage := EncodeUDPPacket(request, payload.Bytes())
-		defer udpMessage.Release()
-
-		v.udpMutex.RLock()
-		if !v.accepting {
-			v.udpMutex.RUnlock()
-			return
-		}
-		nBytes, err := v.udpHub.WriteTo(udpMessage.Bytes(), destination)
-		v.udpMutex.RUnlock()
-
-		if err != nil {
-			log.Warning("Socks: failed to write UDP message (", nBytes, " bytes) to ", destination, ": ", err)
-		}
-	})
-}

+ 0 - 38
proxy/vmess/inbound/command.go

@@ -1,38 +0,0 @@
-package inbound
-
-import (
-	"v2ray.com/core/common/log"
-	"v2ray.com/core/common/protocol"
-	"v2ray.com/core/proxy/vmess"
-)
-
-func (v *VMessInboundHandler) generateCommand(request *protocol.RequestHeader) protocol.ResponseCommand {
-	if v.detours != nil {
-		tag := v.detours.To
-		if v.inboundHandlerManager != nil {
-			handler, availableMin := v.inboundHandlerManager.GetHandler(tag)
-			inboundHandler, ok := handler.(*VMessInboundHandler)
-			if ok {
-				if availableMin > 255 {
-					availableMin = 255
-				}
-
-				log.Info("VMessIn: Pick detour handler for port ", inboundHandler.Port(), " for ", availableMin, " minutes.")
-				user := inboundHandler.GetUser(request.User.Email)
-				if user == nil {
-					return nil
-				}
-				account, _ := user.GetTypedAccount()
-				return &protocol.CommandSwitchAccount{
-					Port:     inboundHandler.Port(),
-					ID:       account.(*vmess.InternalAccount).ID.UUID(),
-					AlterIds: uint16(len(account.(*vmess.InternalAccount).AlterIDs)),
-					Level:    user.Level,
-					ValidMin: byte(availableMin),
-				}
-			}
-		}
-	}
-
-	return nil
-}

+ 49 - 69
proxy/vmess/inbound/inbound.go

@@ -77,10 +77,7 @@ type VMessInboundHandler struct {
 	inboundHandlerManager proxyman.InboundHandlerManager
 	clients               protocol.UserValidator
 	usersByEmail          *userByEmail
-	accepting             bool
-	listener              *internet.TCPHub
 	detours               *DetourConfig
-	meta                  *proxy.InboundHandlerMeta
 }
 
 func New(ctx context.Context, config *Config) (*VMessInboundHandler, error) {
@@ -88,10 +85,6 @@ func New(ctx context.Context, config *Config) (*VMessInboundHandler, error) {
 	if space == nil {
 		return nil, errors.New("VMess|Inbound: No space in context.")
 	}
-	meta := proxy.InboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("VMess|Inbound: No inbound meta in context.")
-	}
 
 	allowedClients := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
 	for _, user := range config.User {
@@ -102,7 +95,6 @@ func New(ctx context.Context, config *Config) (*VMessInboundHandler, error) {
 		clients:      allowedClients,
 		detours:      config.Detour,
 		usersByEmail: NewUserByEmail(config.User, config.GetDefaultValue()),
-		meta:         meta,
 	}
 
 	space.OnInitialize(func() error {
@@ -120,36 +112,16 @@ func New(ctx context.Context, config *Config) (*VMessInboundHandler, error) {
 	return handler, nil
 }
 
-func (v *VMessInboundHandler) Network() net.NetworkList {
+func (*VMessInboundHandler) Network() net.NetworkList {
 	return net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
 	}
 }
 
-func (v *VMessInboundHandler) Port() net.Port {
-	return v.meta.Port
-}
-
-func (v *VMessInboundHandler) Close() {
-	v.Lock()
-	v.accepting = false
-	if v.listener != nil {
-		v.listener.Close()
-		v.listener = nil
-		v.clients.Release()
-		v.clients = nil
-	}
-	v.Unlock()
-}
-
 func (v *VMessInboundHandler) GetUser(email string) *protocol.User {
 	v.RLock()
 	defer v.RUnlock()
 
-	if !v.accepting {
-		return nil
-	}
-
 	user, existing := v.usersByEmail.Get(email)
 	if !existing {
 		v.clients.Add(user)
@@ -157,23 +129,6 @@ func (v *VMessInboundHandler) GetUser(email string) *protocol.User {
 	return user
 }
 
-func (v *VMessInboundHandler) Start() error {
-	if v.accepting {
-		return nil
-	}
-
-	tcpListener, err := internet.ListenTCP(v.meta.Address, v.meta.Port, v.HandleConnection, v.meta.StreamSettings)
-	if err != nil {
-		log.Error("VMess|Inbound: Unable to listen tcp ", v.meta.Address, ":", v.meta.Port, ": ", err)
-		return err
-	}
-	v.accepting = true
-	v.Lock()
-	v.listener = tcpListener
-	v.Unlock()
-	return nil
-}
-
 func transferRequest(session *encoding.ServerSession, request *protocol.RequestHeader, input io.Reader, output ray.OutputStream) error {
 	defer output.Close()
 
@@ -219,23 +174,12 @@ func transferResponse(session *encoding.ServerSession, request *protocol.Request
 	return nil
 }
 
-func (v *VMessInboundHandler) HandleConnection(connection internet.Connection) {
-	defer connection.Close()
-
-	if !v.accepting {
-		return
-	}
-
+func (v *VMessInboundHandler) Process(ctx context.Context, network net.Network, connection internet.Connection) error {
 	connReader := net.NewTimeOutReader(8, connection)
 	reader := bufio.NewReader(connReader)
-	v.RLock()
-	if !v.accepting {
-		v.RUnlock()
-		return
-	}
+
 	session := encoding.NewServerSession(v.clients)
 	request, err := session.DecodeRequestHeader(reader)
-	v.RUnlock()
 
 	if err != nil {
 		if errors.Cause(err) != io.EOF {
@@ -243,19 +187,17 @@ func (v *VMessInboundHandler) HandleConnection(connection internet.Connection) {
 			log.Info("VMess|Inbound: Invalid request from ", connection.RemoteAddr(), ": ", err)
 		}
 		connection.SetReusable(false)
-		return
+		return err
 	}
 	log.Access(connection.RemoteAddr(), request.Destination(), log.AccessAccepted, "")
 	log.Info("VMess|Inbound: Received request for ", request.Destination())
 
 	connection.SetReusable(request.Option.Has(protocol.RequestOptionConnectionReuse))
 
-	ray := v.packetDispatcher.DispatchToOutbound(&proxy.SessionInfo{
-		Source:      net.DestinationFromAddr(connection.RemoteAddr()),
-		Destination: request.Destination(),
-		User:        request.User,
-		Inbound:     v.meta,
-	})
+	ctx = proxy.ContextWithDestination(ctx, request.Destination())
+	ctx = protocol.ContextWithUser(ctx, request.User)
+	ray := v.packetDispatcher.DispatchToOutbound(ctx)
+
 	input := ray.InboundInput()
 	output := ray.InboundOutput()
 
@@ -269,7 +211,7 @@ func (v *VMessInboundHandler) HandleConnection(connection internet.Connection) {
 
 	writer := bufio.NewWriter(connection)
 	response := &protocol.ResponseHeader{
-		Command: v.generateCommand(request),
+		Command: v.generateCommand(ctx, request),
 	}
 
 	if connection.Reusable() {
@@ -285,14 +227,52 @@ func (v *VMessInboundHandler) HandleConnection(connection internet.Connection) {
 		connection.SetReusable(false)
 		input.CloseError()
 		output.CloseError()
-		return
+		return err
 	}
 
 	if err := writer.Flush(); err != nil {
 		log.Info("VMess|Inbound: Failed to flush remain data: ", err)
 		connection.SetReusable(false)
-		return
+		return err
 	}
+
+	return nil
+}
+
+func (v *VMessInboundHandler) generateCommand(ctx context.Context, request *protocol.RequestHeader) protocol.ResponseCommand {
+	if v.detours != nil {
+		tag := v.detours.To
+		if v.inboundHandlerManager != nil {
+			handler, err := v.inboundHandlerManager.GetHandler(ctx, tag)
+			if err != nil {
+				log.Warning("VMess|Inbound: Failed to get detour handler: ", tag, err)
+				return nil
+			}
+			proxyHandler, port, availableMin := handler.GetRandomInboundProxy()
+			inboundHandler, ok := proxyHandler.(*VMessInboundHandler)
+			if ok {
+				if availableMin > 255 {
+					availableMin = 255
+				}
+
+				log.Info("VMessIn: Pick detour handler for port ", port, " for ", availableMin, " minutes.")
+				user := inboundHandler.GetUser(request.User.Email)
+				if user == nil {
+					return nil
+				}
+				account, _ := user.GetTypedAccount()
+				return &protocol.CommandSwitchAccount{
+					Port:     port,
+					ID:       account.(*vmess.InternalAccount).ID.UUID(),
+					AlterIds: uint16(len(account.(*vmess.InternalAccount).AlterIDs)),
+					Level:    user.Level,
+					ValidMin: byte(availableMin),
+				}
+			}
+		}
+	}
+
+	return nil
 }
 
 func init() {

+ 10 - 12
proxy/vmess/outbound/outbound.go

@@ -25,7 +25,6 @@ import (
 type VMessOutboundHandler struct {
 	serverList   *protocol.ServerList
 	serverPicker protocol.ServerPicker
-	meta         *proxy.OutboundHandlerMeta
 }
 
 func New(ctx context.Context, config *Config) (*VMessOutboundHandler, error) {
@@ -33,10 +32,6 @@ func New(ctx context.Context, config *Config) (*VMessOutboundHandler, error) {
 	if space == nil {
 		return nil, errors.New("VMess|Outbound: No space in context.")
 	}
-	meta := proxy.OutboundMetaFromContext(ctx)
-	if meta == nil {
-		return nil, errors.New("VMess|Outbound: No outbound meta in context.")
-	}
 
 	serverList := protocol.NewServerList()
 	for _, rec := range config.Receiver {
@@ -45,20 +40,20 @@ func New(ctx context.Context, config *Config) (*VMessOutboundHandler, error) {
 	handler := &VMessOutboundHandler{
 		serverList:   serverList,
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
-		meta:         meta,
 	}
 
 	return handler, nil
 }
 
 // Dispatch implements OutboundHandler.Dispatch().
-func (v *VMessOutboundHandler) Dispatch(target net.Destination, outboundRay ray.OutboundRay) {
+func (v *VMessOutboundHandler) Process(ctx context.Context, outboundRay ray.OutboundRay) error {
 	var rec *protocol.ServerSpec
 	var conn internet.Connection
 
+	dialer := proxy.DialerFromContext(ctx)
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
 		rec = v.serverPicker.PickServer()
-		rawConn, err := internet.Dial(v.meta.Address, rec.Destination(), v.meta.GetDialerOptions())
+		rawConn, err := dialer.Dial(ctx, rec.Destination())
 		if err != nil {
 			return err
 		}
@@ -68,8 +63,11 @@ func (v *VMessOutboundHandler) Dispatch(target net.Destination, outboundRay ray.
 	})
 	if err != nil {
 		log.Warning("VMess|Outbound: Failed to find an available destination:", err)
-		return
+		return err
 	}
+	defer conn.Close()
+
+	target := proxy.DestinationFromContext(ctx)
 	log.Info("VMess|Outbound: Tunneling request to ", target, " via ", rec.Destination())
 
 	command := protocol.RequestCommandTCP
@@ -88,12 +86,11 @@ func (v *VMessOutboundHandler) Dispatch(target net.Destination, outboundRay ray.
 	rawAccount, err := request.User.GetTypedAccount()
 	if err != nil {
 		log.Warning("VMess|Outbound: Failed to get user account: ", err)
+		return err
 	}
 	account := rawAccount.(*vmess.InternalAccount)
 	request.Security = account.Security
 
-	defer conn.Close()
-
 	conn.SetReusable(true)
 	if conn.Reusable() { // Conn reuse may be disabled on transportation layer
 		request.Option.Set(protocol.RequestOptionConnectionReuse)
@@ -160,9 +157,10 @@ func (v *VMessOutboundHandler) Dispatch(target net.Destination, outboundRay ray.
 		conn.SetReusable(false)
 		input.CloseError()
 		output.CloseError()
+		return err
 	}
 
-	return
+	return nil
 }
 
 func init() {

+ 33 - 24
testing/scenarios/dokodemo_test.go

@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
@@ -32,11 +33,13 @@ func TestDokodemoTCP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -47,9 +50,9 @@ func TestDokodemoTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
@@ -57,11 +60,13 @@ func TestDokodemoTCP(t *testing.T) {
 	clientPort := uint32(pickPort())
 	clientPortRange := uint32(5)
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: &v2net.PortRange{From: clientPort, To: clientPort + clientPortRange},
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: &v2net.PortRange{From: clientPort, To: clientPort + clientPortRange},
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -70,9 +75,9 @@ func TestDokodemoTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -129,11 +134,13 @@ func TestDokodemoUDP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -144,9 +151,9 @@ func TestDokodemoUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
@@ -154,11 +161,13 @@ func TestDokodemoUDP(t *testing.T) {
 	clientPort := uint32(pickPort())
 	clientPortRange := uint32(5)
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: &v2net.PortRange{From: clientPort, To: clientPort + clientPortRange},
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: &v2net.PortRange{From: clientPort, To: clientPort + clientPortRange},
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -167,9 +176,9 @@ func TestDokodemoUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),

+ 97 - 72
testing/scenarios/feature_test.go

@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/router"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
@@ -34,12 +35,14 @@ func TestPassiveConnection(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange:              v2net.SinglePortRange(serverPort),
-				ListenOn:               v2net.NewIPOrDomain(v2net.LocalHostIP),
-				AllowPassiveConnection: true,
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange:              v2net.SinglePortRange(serverPort),
+					Listen:                 v2net.NewIPOrDomain(v2net.LocalHostIP),
+					AllowPassiveConnection: true,
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -48,9 +51,9 @@ func TestPassiveConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
@@ -103,11 +106,13 @@ func TestProxy(t *testing.T) {
 	serverUserID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -118,9 +123,9 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
@@ -128,11 +133,13 @@ func TestProxy(t *testing.T) {
 	proxyUserID := protocol.NewID(uuid.New())
 	proxyPort := pickPort()
 	proxyConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(proxyPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(proxyPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -143,20 +150,22 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -165,9 +174,9 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -182,13 +191,15 @@ func TestProxy(t *testing.T) {
 						},
 					},
 				}),
-				ProxySettings: &internet.ProxyConfig{
-					Tag: "proxy",
-				},
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					ProxySettings: &internet.ProxyConfig{
+						Tag: "proxy",
+					},
+				}),
 			},
 			{
 				Tag: "proxy",
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -244,11 +255,16 @@ func TestProxyOverKCP(t *testing.T) {
 	serverUserID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						Protocol: internet.TransportProtocol_MKCP,
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -257,14 +273,11 @@ func TestProxyOverKCP(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					Protocol: internet.TransportProtocol_MKCP,
-				},
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
@@ -272,11 +285,13 @@ func TestProxyOverKCP(t *testing.T) {
 	proxyUserID := protocol.NewID(uuid.New())
 	proxyPort := pickPort()
 	proxyConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(proxyPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(proxyPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -287,23 +302,27 @@ func TestProxyOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
-				StreamSettings: &internet.StreamConfig{
-					Protocol: internet.TransportProtocol_MKCP,
-				},
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						Protocol: internet.TransportProtocol_MKCP,
+					},
+				}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -312,9 +331,9 @@ func TestProxyOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -329,16 +348,18 @@ func TestProxyOverKCP(t *testing.T) {
 						},
 					},
 				}),
-				ProxySettings: &internet.ProxyConfig{
-					Tag: "proxy",
-				},
-				StreamSettings: &internet.StreamConfig{
-					Protocol: internet.TransportProtocol_MKCP,
-				},
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					ProxySettings: &internet.ProxyConfig{
+						Tag: "proxy",
+					},
+					StreamSettings: &internet.StreamConfig{
+						Protocol: internet.TransportProtocol_MKCP,
+					},
+				}),
 			},
 			{
 				Tag: "proxy",
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -401,11 +422,13 @@ func TestBlackhole(t *testing.T) {
 	serverPort := pickPort()
 	serverPort2 := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -414,9 +437,11 @@ func TestBlackhole(t *testing.T) {
 				}),
 			},
 			{
-				PortRange: v2net.SinglePortRange(serverPort2),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort2),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest2.Address),
 					Port:    uint32(dest2.Port),
 					NetworkList: &v2net.NetworkList{
@@ -425,14 +450,14 @@ func TestBlackhole(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Tag:      "direct",
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				Tag:           "direct",
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 			{
-				Tag:      "blocked",
-				Settings: serial.ToTypedMessage(&blackhole.Config{}),
+				Tag:           "blocked",
+				ProxySettings: serial.ToTypedMessage(&blackhole.Config{}),
 			},
 		},
 		App: []*serial.TypedMessage{

+ 46 - 33
testing/scenarios/socks_test.go

@@ -7,6 +7,7 @@ import (
 	xproxy "golang.org/x/net/proxy"
 	socks4 "h12.me/socks"
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
@@ -30,11 +31,13 @@ func TestSocksBridgeTCP(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&socks.ServerConfig{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{
 					AuthType: socks.AuthType_PASSWORD,
 					Accounts: map[string]string{
 						"Test Account": "Test Password",
@@ -44,20 +47,22 @@ func TestSocksBridgeTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -66,9 +71,9 @@ func TestSocksBridgeTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&socks.ClientConfig{
+				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -123,11 +128,13 @@ func TestSocksBridageUDP(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&socks.ServerConfig{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{
 					AuthType: socks.AuthType_PASSWORD,
 					Accounts: map[string]string{
 						"Test Account": "Test Password",
@@ -137,20 +144,22 @@ func TestSocksBridageUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -159,9 +168,9 @@ func TestSocksBridageUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&socks.ClientConfig{
+				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -217,11 +226,13 @@ func TestSocksConformance(t *testing.T) {
 	authPort := pickPort()
 	noAuthPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(authPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&socks.ServerConfig{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(authPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{
 					AuthType: socks.AuthType_PASSWORD,
 					Accounts: map[string]string{
 						"Test Account": "Test Password",
@@ -231,9 +242,11 @@ func TestSocksConformance(t *testing.T) {
 				}),
 			},
 			{
-				PortRange: v2net.SinglePortRange(noAuthPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&socks.ServerConfig{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(noAuthPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{
 					AuthType: socks.AuthType_NO_AUTH,
 					Accounts: map[string]string{
 						"Test Account": "Test Password",
@@ -243,9 +256,9 @@ func TestSocksConformance(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}

+ 102 - 83
testing/scenarios/tls_test.go

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
@@ -35,11 +36,21 @@ func TestSimpleTLSConnection(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
+							}),
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -48,30 +59,24 @@ func TestSimpleTLSConnection(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
-						}),
-					},
-				},
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -80,9 +85,9 @@ func TestSimpleTLSConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -97,14 +102,16 @@ func TestSimpleTLSConnection(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							AllowInsecure: true,
-						}),
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								AllowInsecure: true,
+							}),
+						},
 					},
-				},
+				}),
 			},
 		},
 	}
@@ -143,11 +150,22 @@ func TestTLSOverKCP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						Protocol:     internet.TransportProtocol_MKCP,
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
+							}),
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -156,31 +174,24 @@ func TestTLSOverKCP(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					Protocol:     internet.TransportProtocol_MKCP,
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
-						}),
-					},
-				},
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -189,9 +200,9 @@ func TestTLSOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -206,15 +217,17 @@ func TestTLSOverKCP(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					Protocol:     internet.TransportProtocol_MKCP,
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							AllowInsecure: true,
-						}),
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						Protocol:     internet.TransportProtocol_MKCP,
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								AllowInsecure: true,
+							}),
+						},
 					},
-				},
+				}),
 			},
 		},
 	}
@@ -253,11 +266,21 @@ func TestTLSConnectionReuse(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
+							}),
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -266,30 +289,24 @@ func TestTLSConnectionReuse(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()},
-						}),
-					},
-				},
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -298,9 +315,9 @@ func TestTLSConnectionReuse(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -315,14 +332,16 @@ func TestTLSConnectionReuse(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					SecurityType: serial.GetMessageType(&tls.Config{}),
-					SecuritySettings: []*serial.TypedMessage{
-						serial.ToTypedMessage(&tls.Config{
-							AllowInsecure: true,
-						}),
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						SecurityType: serial.GetMessageType(&tls.Config{}),
+						SecuritySettings: []*serial.TypedMessage{
+							serial.ToTypedMessage(&tls.Config{
+								AllowInsecure: true,
+							}),
+						},
 					},
-				},
+				}),
 			},
 		},
 	}

+ 37 - 30
testing/scenarios/transport_test.go

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
@@ -35,11 +36,23 @@ func TestNoOpConnectionHeader(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(serverPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&inbound.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(serverPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+					StreamSettings: &internet.StreamConfig{
+						TransportSettings: []*internet.TransportConfig{
+							{
+								Protocol: internet.TransportProtocol_TCP,
+								Settings: serial.ToTypedMessage(&tcptransport.Config{
+									HeaderSettings: serial.ToTypedMessage(&http.Config{}),
+								}),
+							},
+						},
+					},
+				}),
+				ProxySettings: serial.ToTypedMessage(&inbound.Config{
 					User: []*protocol.User{
 						{
 							Account: serial.ToTypedMessage(&vmess.Account{
@@ -48,32 +61,24 @@ func TestNoOpConnectionHeader(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					TransportSettings: []*internet.TransportConfig{
-						{
-							Protocol: internet.TransportProtocol_TCP,
-							Settings: serial.ToTypedMessage(&tcptransport.Config{
-								HeaderSettings: serial.ToTypedMessage(&http.Config{}),
-							}),
-						},
-					},
-				},
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&freedom.Config{}),
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*core.InboundConnectionConfig{
+		Inbound: []*proxyman.InboundHandlerConfig{
 			{
-				PortRange: v2net.SinglePortRange(clientPort),
-				ListenOn:  v2net.NewIPOrDomain(v2net.LocalHostIP),
-				Settings: serial.ToTypedMessage(&dokodemo.Config{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: v2net.SinglePortRange(clientPort),
+					Listen:    v2net.NewIPOrDomain(v2net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
 					Address: v2net.NewIPOrDomain(dest.Address),
 					Port:    uint32(dest.Port),
 					NetworkList: &v2net.NetworkList{
@@ -82,9 +87,9 @@ func TestNoOpConnectionHeader(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*core.OutboundConnectionConfig{
+		Outbound: []*proxyman.OutboundHandlerConfig{
 			{
-				Settings: serial.ToTypedMessage(&outbound.Config{
+				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 						{
 							Address: v2net.NewIPOrDomain(v2net.LocalHostIP),
@@ -99,16 +104,18 @@ func TestNoOpConnectionHeader(t *testing.T) {
 						},
 					},
 				}),
-				StreamSettings: &internet.StreamConfig{
-					TransportSettings: []*internet.TransportConfig{
-						{
-							Protocol: internet.TransportProtocol_TCP,
-							Settings: serial.ToTypedMessage(&tcptransport.Config{
-								HeaderSettings: serial.ToTypedMessage(&http.Config{}),
-							}),
+				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
+					StreamSettings: &internet.StreamConfig{
+						TransportSettings: []*internet.TransportConfig{
+							{
+								Protocol: internet.TransportProtocol_TCP,
+								Settings: serial.ToTypedMessage(&tcptransport.Config{
+									HeaderSettings: serial.ToTypedMessage(&http.Config{}),
+								}),
+							},
 						},
 					},
-				},
+				}),
 			},
 		},
 	}

+ 27 - 71
tools/conf/router_test.go

@@ -4,6 +4,8 @@ import (
 	"net"
 	"testing"
 
+	"context"
+
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/testing/assert"
@@ -28,22 +30,12 @@ func TestChinaIPJson(t *testing.T) {
 	assert.String(rule.Tag).Equals("x")
 	cond, err := rule.BuildCondition()
 	assert.Error(err).IsNil()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("121.14.1.189"), 80),
-	})).IsTrue() // sina.com.cn
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("101.226.103.106"), 80),
-	})).IsTrue() // qq.com
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("115.239.210.36"), 80),
-	})).IsTrue() // image.baidu.com
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("120.135.126.1"), 80),
-	})).IsTrue()
-
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("8.8.8.8"), 80),
-	})).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("121.14.1.189"), 80)))).IsTrue()    // sina.com.cn
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("101.226.103.106"), 80)))).IsTrue() // qq.com
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("115.239.210.36"), 80)))).IsTrue()  // image.baidu.com
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("120.135.126.1"), 80)))).IsTrue()
+
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("8.8.8.8"), 80)))).IsFalse()
 }
 
 func TestChinaSitesJson(t *testing.T) {
@@ -56,22 +48,12 @@ func TestChinaSitesJson(t *testing.T) {
 	assert.String(rule.Tag).Equals("y")
 	cond, err := rule.BuildCondition()
 	assert.Error(err).IsNil()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("v.qq.com"), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("www.163.com"), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("ngacn.cc"), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("12306.cn"), 80),
-	})).IsTrue()
-
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("v2ray.com"), 80),
-	})).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("v.qq.com"), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("www.163.com"), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("ngacn.cc"), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("12306.cn"), 80)))).IsTrue()
+
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("v2ray.com"), 80)))).IsFalse()
 }
 
 func TestDomainRule(t *testing.T) {
@@ -90,21 +72,11 @@ func TestDomainRule(t *testing.T) {
 	assert.Pointer(rule).IsNotNil()
 	cond, err := rule.BuildCondition()
 	assert.Error(err).IsNil()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("www.ooxx.com"), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("www.aabb.com"), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("www.12306.cn"), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.ParseAddress("www.acn.com"), 80),
-	})).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("www.ooxx.com"), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("www.aabb.com"), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("www.12306.cn"), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.ParseAddress("www.acn.com"), 80)))).IsFalse()
 }
 
 func TestIPRule(t *testing.T) {
@@ -122,18 +94,10 @@ func TestIPRule(t *testing.T) {
 	assert.Pointer(rule).IsNotNil()
 	cond, err := rule.BuildCondition()
 	assert.Error(err).IsNil()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.DomainAddress("www.ooxx.com"), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Destination: v2net.TCPDestination(v2net.IPAddress([]byte{192, 0, 0, 1}), 80),
-	})).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.DomainAddress("www.ooxx.com"), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithDestination(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{192, 0, 0, 1}), 80)))).IsTrue()
 }
 
 func TestSourceIPRule(t *testing.T) {
@@ -150,16 +114,8 @@ func TestSourceIPRule(t *testing.T) {
 	assert.Pointer(rule).IsNotNil()
 	cond, err := rule.BuildCondition()
 	assert.Error(err).IsNil()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Source: v2net.TCPDestination(v2net.DomainAddress("www.ooxx.com"), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Source: v2net.TCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}), 80),
-	})).IsTrue()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Source: v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80),
-	})).IsFalse()
-	assert.Bool(cond.Apply(&proxy.SessionInfo{
-		Source: v2net.TCPDestination(v2net.IPAddress([]byte{192, 0, 0, 1}), 80),
-	})).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithSource(context.Background(), v2net.TCPDestination(v2net.DomainAddress("www.ooxx.com"), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithSource(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{10, 0, 0, 1}), 80)))).IsTrue()
+	assert.Bool(cond.Apply(proxy.ContextWithSource(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{127, 0, 0, 1}), 80)))).IsFalse()
+	assert.Bool(cond.Apply(proxy.ContextWithSource(context.Background(), v2net.TCPDestination(v2net.IPAddress([]byte{192, 0, 0, 1}), 80)))).IsTrue()
 }

+ 80 - 61
tools/conf/v2ray.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"v2ray.com/core"
+	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common/errors"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
@@ -40,40 +41,45 @@ type InboundConnectionConfig struct {
 	Tag           string          `json:"tag"`
 }
 
-func (v *InboundConnectionConfig) Build() (*core.InboundConnectionConfig, error) {
-	config := new(core.InboundConnectionConfig)
-	config.PortRange = &v2net.PortRange{
-		From: uint32(v.Port),
-		To:   uint32(v.Port),
+func (v *InboundConnectionConfig) Build() (*proxyman.InboundHandlerConfig, error) {
+	receiverConfig := &proxyman.ReceiverConfig{
+		PortRange: &v2net.PortRange{
+			From: uint32(v.Port),
+			To:   uint32(v.Port),
+		},
+		AllowPassiveConnection: v.AllowPassive,
 	}
 	if v.Listen != nil {
 		if v.Listen.Family().IsDomain() {
 			return nil, errors.New("Point: Unable to listen on domain address: " + v.Listen.Domain())
 		}
-		config.ListenOn = v.Listen.Build()
+		receiverConfig.Listen = v.Listen.Build()
 	}
 	if v.StreamSetting != nil {
 		ts, err := v.StreamSetting.Build()
 		if err != nil {
 			return nil, err
 		}
-		config.StreamSettings = ts
+		receiverConfig.StreamSettings = ts
 	}
-	config.AllowPassiveConnection = v.AllowPassive
 
 	jsonConfig, err := inboundConfigLoader.LoadWithID(v.Settings, v.Protocol)
 	if err != nil {
 		return nil, errors.Base(err).Message("Failed to load inbound config.")
 	}
+	if dokodemoConfig, ok := jsonConfig.(*DokodemoConfig); ok {
+		receiverConfig.ReceiveOriginalDestination = dokodemoConfig.Redirect
+	}
 	ts, err := jsonConfig.(Buildable).Build()
 	if err != nil {
 		return nil, err
 	}
-	config.Settings = ts
-	if len(v.Tag) > 0 {
-		config.Tag = v.Tag
-	}
-	return config, nil
+
+	return &proxyman.InboundHandlerConfig{
+		Tag:              v.Tag,
+		ReceiverSettings: serial.ToTypedMessage(receiverConfig),
+		ProxySettings:    ts,
+	}, nil
 }
 
 type OutboundConnectionConfig struct {
@@ -85,43 +91,45 @@ type OutboundConnectionConfig struct {
 	Tag           string          `json:"tag"`
 }
 
-func (v *OutboundConnectionConfig) Build() (*core.OutboundConnectionConfig, error) {
-	config := new(core.OutboundConnectionConfig)
-	rawConfig, err := outboundConfigLoader.LoadWithID(v.Settings, v.Protocol)
-	if err != nil {
-		return nil, errors.Base(err).Message("Failed to parse outbound config.")
-	}
-	ts, err := rawConfig.(Buildable).Build()
-	if err != nil {
-		return nil, err
-	}
-	config.Settings = ts
+func (v *OutboundConnectionConfig) Build() (*proxyman.OutboundHandlerConfig, error) {
+	senderSettings := &proxyman.SenderConfig{}
 
 	if v.SendThrough != nil {
 		address := v.SendThrough
 		if address.Family().IsDomain() {
 			return nil, errors.New("Invalid sendThrough address: " + address.String())
 		}
-		config.SendThrough = address.Build()
+		senderSettings.Via = address.Build()
 	}
 	if v.StreamSetting != nil {
 		ss, err := v.StreamSetting.Build()
 		if err != nil {
 			return nil, err
 		}
-		config.StreamSettings = ss
+		senderSettings.StreamSettings = ss
 	}
 	if v.ProxySettings != nil {
 		ps, err := v.ProxySettings.Build()
 		if err != nil {
 			return nil, errors.Base(err).Message("Invalid outbound proxy settings.")
 		}
-		config.ProxySettings = ps
+		senderSettings.ProxySettings = ps
+	}
+
+	rawConfig, err := outboundConfigLoader.LoadWithID(v.Settings, v.Protocol)
+	if err != nil {
+		return nil, errors.Base(err).Message("Failed to parse outbound config.")
 	}
-	if len(v.Tag) > 0 {
-		config.Tag = v.Tag
+	ts, err := rawConfig.(Buildable).Build()
+	if err != nil {
+		return nil, err
 	}
-	return config, nil
+
+	return &proxyman.OutboundHandlerConfig{
+		SenderSettings: serial.ToTypedMessage(senderSettings),
+		ProxySettings:  ts,
+		Tag:            v.Tag,
+	}, nil
 }
 
 type InboundDetourAllocationConfig struct {
@@ -130,26 +138,26 @@ type InboundDetourAllocationConfig struct {
 	RefreshMin  *uint32 `json:"refresh"`
 }
 
-func (v *InboundDetourAllocationConfig) Build() (*core.AllocationStrategy, error) {
-	config := new(core.AllocationStrategy)
+func (v *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) {
+	config := new(proxyman.AllocationStrategy)
 	switch strings.ToLower(v.Strategy) {
 	case "always":
-		config.Type = core.AllocationStrategy_Always
+		config.Type = proxyman.AllocationStrategy_Always
 	case "random":
-		config.Type = core.AllocationStrategy_Random
+		config.Type = proxyman.AllocationStrategy_Random
 	case "external":
-		config.Type = core.AllocationStrategy_External
+		config.Type = proxyman.AllocationStrategy_External
 	default:
 		return nil, errors.New("Unknown allocation strategy: ", v.Strategy)
 	}
 	if v.Concurrency != nil {
-		config.Concurrency = &core.AllocationStrategyConcurrency{
+		config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
 			Value: *v.Concurrency,
 		}
 	}
 
 	if v.RefreshMin != nil {
-		config.Refresh = &core.AllocationStrategyRefresh{
+		config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{
 			Value: *v.RefreshMin,
 		}
 	}
@@ -168,46 +176,54 @@ type InboundDetourConfig struct {
 	AllowPassive  bool                           `json:"allowPassive"`
 }
 
-func (v *InboundDetourConfig) Build() (*core.InboundConnectionConfig, error) {
-	config := new(core.InboundConnectionConfig)
+func (v *InboundDetourConfig) Build() (*proxyman.InboundHandlerConfig, error) {
+	receiverSettings := &proxyman.ReceiverConfig{
+		AllowPassiveConnection: v.AllowPassive,
+	}
+
 	if v.PortRange == nil {
 		return nil, errors.New("Port range not specified in InboundDetour.")
 	}
-	config.PortRange = v.PortRange.Build()
+	receiverSettings.PortRange = v.PortRange.Build()
 
 	if v.ListenOn != nil {
 		if v.ListenOn.Family().IsDomain() {
 			return nil, errors.New("Unable to listen on domain address: ", v.ListenOn.Domain())
 		}
-		config.ListenOn = v.ListenOn.Build()
+		receiverSettings.Listen = v.ListenOn.Build()
 	}
-	config.Tag = v.Tag
 	if v.Allocation != nil {
 		as, err := v.Allocation.Build()
 		if err != nil {
 			return nil, err
 		}
-		config.AllocationStrategy = as
+		receiverSettings.AllocationStrategy = as
 	}
 	if v.StreamSetting != nil {
 		ss, err := v.StreamSetting.Build()
 		if err != nil {
 			return nil, err
 		}
-		config.StreamSettings = ss
+		receiverSettings.StreamSettings = ss
 	}
-	config.AllowPassiveConnection = v.AllowPassive
 
 	rawConfig, err := inboundConfigLoader.LoadWithID(v.Settings, v.Protocol)
 	if err != nil {
 		return nil, errors.Base(err).Message("Failed to load inbound detour config.")
 	}
+	if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
+		receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect
+	}
 	ts, err := rawConfig.(Buildable).Build()
 	if err != nil {
 		return nil, err
 	}
-	config.Settings = ts
-	return config, nil
+
+	return &proxyman.InboundHandlerConfig{
+		Tag:              v.Tag,
+		ReceiverSettings: serial.ToTypedMessage(receiverSettings),
+		ProxySettings:    ts,
+	}, nil
 }
 
 type OutboundDetourConfig struct {
@@ -219,16 +235,15 @@ type OutboundDetourConfig struct {
 	ProxySettings *ProxyConfig    `json:"proxySettings"`
 }
 
-func (v *OutboundDetourConfig) Build() (*core.OutboundConnectionConfig, error) {
-	config := new(core.OutboundConnectionConfig)
-	config.Tag = v.Tag
+func (v *OutboundDetourConfig) Build() (*proxyman.OutboundHandlerConfig, error) {
+	senderSettings := &proxyman.SenderConfig{}
 
 	if v.SendThrough != nil {
 		address := v.SendThrough
 		if address.Family().IsDomain() {
 			return nil, errors.New("Point: Unable to send through: " + address.String())
 		}
-		config.SendThrough = address.Build()
+		senderSettings.Via = address.Build()
 	}
 
 	if v.StreamSetting != nil {
@@ -236,7 +251,15 @@ func (v *OutboundDetourConfig) Build() (*core.OutboundConnectionConfig, error) {
 		if err != nil {
 			return nil, err
 		}
-		config.StreamSettings = ss
+		senderSettings.StreamSettings = ss
+	}
+
+	if v.ProxySettings != nil {
+		ps, err := v.ProxySettings.Build()
+		if err != nil {
+			return nil, errors.Base(err).Message("Invalid outbound detour proxy settings.")
+		}
+		senderSettings.ProxySettings = ps
 	}
 
 	rawConfig, err := outboundConfigLoader.LoadWithID(v.Settings, v.Protocol)
@@ -248,15 +271,11 @@ func (v *OutboundDetourConfig) Build() (*core.OutboundConnectionConfig, error) {
 		return nil, err
 	}
 
-	if v.ProxySettings != nil {
-		ps, err := v.ProxySettings.Build()
-		if err != nil {
-			return nil, errors.Base(err).Message("Invalid outbound detour proxy settings.")
-		}
-		config.ProxySettings = ps
-	}
-	config.Settings = ts
-	return config, nil
+	return &proxyman.OutboundHandlerConfig{
+		SenderSettings: serial.ToTypedMessage(senderSettings),
+		Tag:            v.Tag,
+		ProxySettings:  ts,
+	}, nil
 }
 
 type Config struct {

+ 18 - 0
transport/internet/config.go

@@ -56,6 +56,24 @@ func (v *StreamConfig) GetEffectiveTransportSettings() (interface{}, error) {
 	return CreateTransportConfig(protocol)
 }
 
+func (c *StreamConfig) GetTransportSettingsFor(protocol TransportProtocol) (interface{}, error) {
+	if c != nil {
+		for _, settings := range c.TransportSettings {
+			if settings.Protocol == protocol {
+				return settings.GetTypedSettings()
+			}
+		}
+	}
+
+	for _, settings := range globalTransportSettings {
+		if settings.Protocol == protocol {
+			return settings.GetTypedSettings()
+		}
+	}
+
+	return CreateTransportConfig(protocol)
+}
+
 func (v *StreamConfig) GetEffectiveSecuritySettings() (interface{}, error) {
 	for _, settings := range v.SecuritySettings {
 		if settings.Type == v.SecurityType {

+ 52 - 0
transport/internet/context.go

@@ -0,0 +1,52 @@
+package internet
+
+import (
+	"context"
+
+	"v2ray.com/core/common/net"
+)
+
+type key int
+
+const (
+	streamSettingsKey key = iota
+	dialerSrcKey
+	transportSettingsKey
+	securitySettingsKey
+)
+
+func ContextWithStreamSettings(ctx context.Context, streamSettings *StreamConfig) context.Context {
+	return context.WithValue(ctx, streamSettingsKey, streamSettings)
+}
+
+func StreamSettingsFromContext(ctx context.Context) (*StreamConfig, bool) {
+	ss, ok := ctx.Value(streamSettingsKey).(*StreamConfig)
+	return ss, ok
+}
+
+func ContextWithDialerSource(ctx context.Context, addr net.Address) context.Context {
+	return context.WithValue(ctx, dialerSrcKey, addr)
+}
+
+func DialerSourceFromContext(ctx context.Context) net.Address {
+	if addr, ok := ctx.Value(dialerSrcKey).(net.Address); ok {
+		return addr
+	}
+	return net.AnyIP
+}
+
+func ContextWithTransportSettings(ctx context.Context, transportSettings interface{}) context.Context {
+	return context.WithValue(ctx, transportSettingsKey, transportSettings)
+}
+
+func TransportSettingsFromContext(ctx context.Context) interface{} {
+	return ctx.Value(transportSettingsKey)
+}
+
+func ContextWithSecuritySettings(ctx context.Context, securitySettings interface{}) context.Context {
+	return context.WithValue(ctx, securitySettingsKey, securitySettings)
+}
+
+func SecuritySettingsFromContext(ctx context.Context) interface{} {
+	return ctx.Value(securitySettingsKey)
+}

+ 20 - 19
transport/internet/dialer.go

@@ -1,24 +1,17 @@
 package internet
 
 import (
+	"context"
 	"net"
 
 	"v2ray.com/core/common/errors"
-	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
 )
 
-type DialerOptions struct {
-	Stream *StreamConfig
-	Proxy  *ProxyConfig
-}
-
-type Dialer func(src v2net.Address, dest v2net.Destination, options DialerOptions) (Connection, error)
+type Dialer func(ctx context.Context, dest v2net.Destination) (Connection, error)
 
 var (
 	transportDialerCache = make(map[TransportProtocol]Dialer)
-
-	ProxyDialer Dialer
 )
 
 func RegisterTransportDialer(protocol TransportProtocol, dialer Dialer) error {
@@ -29,26 +22,34 @@ func RegisterTransportDialer(protocol TransportProtocol, dialer Dialer) error {
 	return nil
 }
 
-func Dial(src v2net.Address, dest v2net.Destination, options DialerOptions) (Connection, error) {
-	if options.Proxy.HasTag() && ProxyDialer != nil {
-		log.Info("Internet: Proxying outbound connection through: ", options.Proxy.Tag)
-		return ProxyDialer(src, dest, options)
-	}
-
+func Dial(ctx context.Context, dest v2net.Destination) (Connection, error) {
 	if dest.Network == v2net.Network_TCP {
-		protocol := options.Stream.GetEffectiveProtocol()
+		streamSettings, _ := StreamSettingsFromContext(ctx)
+		protocol := streamSettings.GetEffectiveProtocol()
+		transportSettings, err := streamSettings.GetEffectiveTransportSettings()
+		if err != nil {
+			return nil, err
+		}
+		ctx = ContextWithTransportSettings(ctx, transportSettings)
+		if streamSettings != nil && streamSettings.HasSecuritySettings() {
+			securitySettings, err := streamSettings.GetEffectiveSecuritySettings()
+			if err != nil {
+				return nil, err
+			}
+			ctx = ContextWithSecuritySettings(ctx, securitySettings)
+		}
 		dialer := transportDialerCache[protocol]
 		if dialer == nil {
-			return nil, errors.New("Internet|Dialer: ", options.Stream.Protocol, " dialer not registered.")
+			return nil, errors.New("Internet|Dialer: ", protocol, " dialer not registered.")
 		}
-		return dialer(src, dest, options)
+		return dialer(ctx, dest)
 	}
 
 	udpDialer := transportDialerCache[TransportProtocol_UDP]
 	if udpDialer == nil {
 		return nil, errors.New("Internet|Dialer: UDP dialer not registered.")
 	}
-	return udpDialer(src, dest, options)
+	return udpDialer(ctx, dest)
 }
 
 // DialSystem calls system dialer to create a network connection.

+ 5 - 13
transport/internet/kcp/dialer.go

@@ -1,6 +1,7 @@
 package kcp
 
 import (
+	"context"
 	"crypto/cipher"
 	"crypto/tls"
 	"net"
@@ -108,10 +109,11 @@ func (o *ClientConnection) Run() {
 	}
 }
 
-func DialKCP(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (internet.Connection, error) {
+func DialKCP(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
 	dest.Network = v2net.Network_UDP
 	log.Info("KCP|Dialer: Dialing KCP to ", dest)
 
+	src := internet.DialerSourceFromContext(ctx)
 	id := internal.NewConnectionID(src, dest)
 	conn := globalPool.Get(id)
 	if conn == nil {
@@ -128,12 +130,7 @@ func DialKCP(src v2net.Address, dest v2net.Destination, options internet.DialerO
 		conn = c
 	}
 
-	networkSettings, err := options.Stream.GetEffectiveTransportSettings()
-	if err != nil {
-		log.Error("KCP|Dialer: Failed to get KCP settings: ", err)
-		return nil, err
-	}
-	kcpSettings := networkSettings.(*Config)
+	kcpSettings := internet.TransportSettingsFromContext(ctx).(*Config)
 
 	clientConn := conn.(*ClientConnection)
 	header, err := kcpSettings.GetPackerHeader()
@@ -151,12 +148,7 @@ func DialKCP(src v2net.Address, dest v2net.Destination, options internet.DialerO
 	var iConn internet.Connection
 	iConn = session
 
-	if options.Stream != nil && options.Stream.HasSecuritySettings() {
-		securitySettings, err := options.Stream.GetEffectiveSecuritySettings()
-		if err != nil {
-			log.Error("KCP|Dialer: Failed to get security settings: ", err)
-			return nil, err
-		}
+	if securitySettings := internet.SecuritySettingsFromContext(ctx); securitySettings != nil {
 		switch securitySettings := securitySettings.(type) {
 		case *v2tls.Config:
 			config := securitySettings.GetTLSConfig()

+ 3 - 11
transport/internet/kcp/kcp_test.go

@@ -1,6 +1,7 @@
 package kcp_test
 
 import (
+	"context"
 	"crypto/rand"
 	"io"
 	"net"
@@ -55,19 +56,10 @@ func TestDialAndListen(t *testing.T) {
 		}
 	}()
 
+	ctx := internet.ContextWithTransportSettings(context.Background(), &Config{})
 	wg := new(sync.WaitGroup)
 	for i := 0; i < 10; i++ {
-		clientConn, err := DialKCP(v2net.LocalHostIP, v2net.UDPDestination(v2net.LocalHostIP, port), internet.DialerOptions{
-			Stream: &internet.StreamConfig{
-				Protocol: internet.TransportProtocol_MKCP,
-				TransportSettings: []*internet.TransportConfig{
-					{
-						Protocol: internet.TransportProtocol_MKCP,
-						Settings: serial.ToTypedMessage(&Config{}),
-					},
-				},
-			},
-		})
+		clientConn, err := DialKCP(ctx, v2net.UDPDestination(v2net.LocalHostIP, port))
 		assert.Error(err).IsNil()
 		wg.Add(1)
 

+ 1 - 4
transport/internet/kcp/listener.go

@@ -13,7 +13,6 @@ import (
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet/internal"
 	v2tls "v2ray.com/core/transport/internet/tls"
@@ -140,11 +139,9 @@ func NewListener(address v2net.Address, port v2net.Port, options internet.Listen
 	return l, nil
 }
 
-func (v *Listener) OnReceive(payload *buf.Buffer, session *proxy.SessionInfo) {
+func (v *Listener) OnReceive(payload *buf.Buffer, src v2net.Destination, originalDest v2net.Destination) {
 	defer payload.Release()
 
-	src := session.Source
-
 	segments := v.reader.Read(payload.Bytes())
 	if len(segments) == 0 {
 		log.Info("KCP|Listener: discarding invalid payload from ", src)

+ 6 - 15
transport/internet/tcp/dialer.go

@@ -1,6 +1,7 @@
 package tcp
 
 import (
+	"context"
 	"crypto/tls"
 	"net"
 
@@ -17,16 +18,11 @@ var (
 	globalCache = internal.NewConnectionPool()
 )
 
-func Dial(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (internet.Connection, error) {
+func Dial(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
 	log.Info("Internet|TCP: Dailing TCP to ", dest)
-	if src == nil {
-		src = v2net.AnyIP
-	}
-	networkSettings, err := options.Stream.GetEffectiveTransportSettings()
-	if err != nil {
-		return nil, err
-	}
-	tcpSettings := networkSettings.(*Config)
+	src := internet.DialerSourceFromContext(ctx)
+
+	tcpSettings := internet.TransportSettingsFromContext(ctx).(*Config)
 
 	id := internal.NewConnectionID(src, dest)
 	var conn net.Conn
@@ -39,12 +35,7 @@ func Dial(src v2net.Address, dest v2net.Destination, options internet.DialerOpti
 		if err != nil {
 			return nil, err
 		}
-		if options.Stream != nil && options.Stream.HasSecuritySettings() {
-			securitySettings, err := options.Stream.GetEffectiveSecuritySettings()
-			if err != nil {
-				log.Error("TCP: Failed to get security settings: ", err)
-				return nil, err
-			}
+		if securitySettings := internet.SecuritySettingsFromContext(ctx); securitySettings != nil {
 			tlsConfig, ok := securitySettings.(*v2tls.Config)
 			if ok {
 				config := tlsConfig.GetTLSConfig()

+ 1 - 0
transport/internet/tcp/hub.go

@@ -42,6 +42,7 @@ func ListenTCP(address v2net.Address, port v2net.Port, options internet.ListenOp
 	if err != nil {
 		return nil, err
 	}
+	log.Info("TCP|Listener: Listening on ", address, ":", port)
 	networkSettings, err := options.Stream.GetEffectiveTransportSettings()
 	if err != nil {
 		return nil, err

+ 1 - 1
proxy/dokodemo/sockopt_linux.go → transport/internet/tcp/sockopt_linux.go

@@ -1,6 +1,6 @@
 // +build linux
 
-package dokodemo
+package tcp
 
 import (
 	"syscall"

+ 1 - 1
proxy/dokodemo/sockopt_other.go → transport/internet/tcp/sockopt_other.go

@@ -1,6 +1,6 @@
 // +build !linux
 
-package dokodemo
+package tcp
 
 import (
 	"v2ray.com/core/common/net"

+ 2 - 1
transport/internet/tcp_hub.go

@@ -24,7 +24,8 @@ func RegisterTransportListener(protocol TransportProtocol, listener ListenFunc)
 
 type ListenFunc func(address v2net.Address, port v2net.Port, options ListenOptions) (Listener, error)
 type ListenOptions struct {
-	Stream *StreamConfig
+	Stream       *StreamConfig
+	RecvOrigDest bool
 }
 
 type Listener interface {

+ 4 - 1
transport/internet/udp/dialer.go

@@ -1,6 +1,8 @@
 package udp
 
 import (
+	"context"
+
 	"v2ray.com/core/common"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/transport/internet"
@@ -9,7 +11,8 @@ import (
 
 func init() {
 	common.Must(internet.RegisterTransportDialer(internet.TransportProtocol_UDP,
-		func(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (internet.Connection, error) {
+		func(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
+			src := internet.DialerSourceFromContext(ctx)
 			conn, err := internet.DialSystem(src, dest)
 			if err != nil {
 				return nil, err

+ 12 - 12
transport/internet/udp/hub.go

@@ -9,18 +9,18 @@ import (
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/signal"
-	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet/internal"
 )
 
 // Payload represents a single UDP payload.
 type Payload struct {
-	payload *buf.Buffer
-	session *proxy.SessionInfo
+	payload      *buf.Buffer
+	source       v2net.Destination
+	originalDest v2net.Destination
 }
 
 // PayloadHandler is function to handle Payload.
-type PayloadHandler func(*buf.Buffer, *proxy.SessionInfo)
+type PayloadHandler func(payload *buf.Buffer, source v2net.Destination, originalDest v2net.Destination)
 
 // PayloadQueue is a queue of Payload.
 type PayloadQueue struct {
@@ -59,7 +59,7 @@ func (v *PayloadQueue) Enqueue(payload Payload) {
 
 func (v *PayloadQueue) Dequeue(queue <-chan Payload) {
 	for payload := range queue {
-		v.callback(payload.payload, payload.session)
+		v.callback(payload.payload, payload.source, payload.originalDest)
 	}
 }
 
@@ -94,6 +94,7 @@ func ListenUDP(address v2net.Address, port v2net.Port, option ListenOption) (*Hu
 	if err != nil {
 		return nil, err
 	}
+	log.Info("UDP|Hub: Listening on ", address, ":", port)
 	if option.ReceiveOriginalDest {
 		fd, err := internal.GetSysFd(udpConn)
 		if err != nil {
@@ -155,15 +156,14 @@ func (v *Hub) start() {
 			continue
 		}
 
-		session := new(proxy.SessionInfo)
-		session.Source = v2net.UDPDestination(v2net.IPAddress(addr.IP), v2net.Port(addr.Port))
+		payload := Payload{
+			payload: buffer,
+		}
+		payload.source = v2net.UDPDestination(v2net.IPAddress(addr.IP), v2net.Port(addr.Port))
 		if v.option.ReceiveOriginalDest && noob > 0 {
-			session.Destination = RetrieveOriginalDest(oobBytes[:noob])
+			payload.originalDest = RetrieveOriginalDest(oobBytes[:noob])
 		}
-		v.queue.Enqueue(Payload{
-			payload: buffer,
-			session: session,
-		})
+		v.queue.Enqueue(payload)
 	}
 }
 

+ 27 - 121
transport/internet/udp/udp_server.go

@@ -1,8 +1,8 @@
 package udp
 
 import (
+	"context"
 	"sync"
-	"time"
 
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/common/buf"
@@ -12,95 +12,17 @@ import (
 	"v2ray.com/core/transport/ray"
 )
 
-type ResponseCallback func(destination v2net.Destination, payload *buf.Buffer)
-
-type TimedInboundRay struct {
-	name       string
-	inboundRay ray.InboundRay
-	accessed   chan bool
-	server     *Server
-	sync.RWMutex
-}
-
-func NewTimedInboundRay(name string, inboundRay ray.InboundRay, server *Server) *TimedInboundRay {
-	r := &TimedInboundRay{
-		name:       name,
-		inboundRay: inboundRay,
-		accessed:   make(chan bool, 1),
-		server:     server,
-	}
-	go r.Monitor()
-	return r
-}
-
-func (v *TimedInboundRay) Monitor() {
-	for {
-		time.Sleep(time.Second * 16)
-		select {
-		case <-v.accessed:
-		default:
-			// Ray not accessed for a while, assuming communication is dead.
-			v.RLock()
-			if v.server == nil {
-				v.RUnlock()
-				return
-			}
-			v.server.RemoveRay(v.name)
-			v.RUnlock()
-			v.Release()
-			return
-		}
-	}
-}
-
-func (v *TimedInboundRay) InboundInput() ray.OutputStream {
-	v.RLock()
-	defer v.RUnlock()
-	if v.inboundRay == nil {
-		return nil
-	}
-	select {
-	case v.accessed <- true:
-	default:
-	}
-	return v.inboundRay.InboundInput()
-}
-
-func (v *TimedInboundRay) InboundOutput() ray.InputStream {
-	v.RLock()
-	defer v.RUnlock()
-	if v.inboundRay == nil {
-		return nil
-	}
-	select {
-	case v.accessed <- true:
-	default:
-	}
-	return v.inboundRay.InboundOutput()
-}
-
-func (v *TimedInboundRay) Release() {
-	log.Debug("UDP Server: Releasing TimedInboundRay: ", v.name)
-	v.Lock()
-	defer v.Unlock()
-	if v.server == nil {
-		return
-	}
-	v.server = nil
-	v.inboundRay.InboundInput().Close()
-	v.inboundRay.InboundOutput().CloseError()
-	v.inboundRay = nil
-}
+type ResponseCallback func(payload *buf.Buffer)
 
 type Server struct {
 	sync.RWMutex
-	conns            map[string]*TimedInboundRay
+	conns            map[string]ray.InboundRay
 	packetDispatcher dispatcher.Interface
 }
 
 func NewServer(packetDispatcher dispatcher.Interface) *Server {
 	return &Server{
-		conns:            make(map[string]*TimedInboundRay),
+		conns:            make(map[string]ray.InboundRay),
 		packetDispatcher: packetDispatcher,
 	}
 }
@@ -108,69 +30,53 @@ func NewServer(packetDispatcher dispatcher.Interface) *Server {
 func (v *Server) RemoveRay(name string) {
 	v.Lock()
 	defer v.Unlock()
-	delete(v.conns, name)
-}
-
-func (v *Server) locateExistingAndDispatch(name string, payload *buf.Buffer) bool {
-	log.Debug("UDP Server: Locating existing connection for ", name)
-	v.RLock()
-	defer v.RUnlock()
-	if entry, found := v.conns[name]; found {
-		outputStream := entry.InboundInput()
-		if outputStream == nil {
-			return false
-		}
-		err := outputStream.Write(payload)
-		if err != nil {
-			go entry.Release()
-			return false
-		}
-		return true
+	if conn, found := v.conns[name]; found {
+		conn.InboundInput().Close()
+		conn.InboundOutput().Close()
+		delete(v.conns, name)
 	}
-	return false
 }
 
-func (v *Server) getInboundRay(dest string, session *proxy.SessionInfo) (*TimedInboundRay, bool) {
+func (v *Server) getInboundRay(ctx context.Context, dest v2net.Destination) (ray.InboundRay, bool) {
+	destString := dest.String()
 	v.Lock()
 	defer v.Unlock()
 
-	if entry, found := v.conns[dest]; found {
+	if entry, found := v.conns[destString]; found {
 		return entry, true
 	}
 
 	log.Info("UDP|Server: establishing new connection for ", dest)
-	inboundRay := v.packetDispatcher.DispatchToOutbound(session)
-	return NewTimedInboundRay(dest, inboundRay, v), false
+	ctx = proxy.ContextWithDestination(ctx, dest)
+	return v.packetDispatcher.DispatchToOutbound(ctx), false
 }
 
-func (v *Server) Dispatch(session *proxy.SessionInfo, payload *buf.Buffer, callback ResponseCallback) {
-	source := session.Source
-	destination := session.Destination
-
+func (v *Server) Dispatch(ctx context.Context, destination v2net.Destination, payload *buf.Buffer, callback ResponseCallback) {
 	// TODO: Add user to destString
-	destString := source.String() + "-" + destination.String()
+	destString := destination.String()
 	log.Debug("UDP|Server: Dispatch request: ", destString)
-	inboundRay, existing := v.getInboundRay(destString, session)
+
+	inboundRay, existing := v.getInboundRay(ctx, destination)
 	outputStream := inboundRay.InboundInput()
 	if outputStream != nil {
-		outputStream.Write(payload)
+		if err := outputStream.Write(payload); err != nil {
+			v.RemoveRay(destString)
+		}
 	}
 	if !existing {
-		go v.handleConnection(inboundRay, source, callback)
+		go func() {
+			handleInput(inboundRay.InboundOutput(), callback)
+			v.RemoveRay(destString)
+		}()
 	}
 }
 
-func (v *Server) handleConnection(inboundRay *TimedInboundRay, source v2net.Destination, callback ResponseCallback) {
+func handleInput(input ray.InputStream, callback ResponseCallback) {
 	for {
-		inputStream := inboundRay.InboundOutput()
-		if inputStream == nil {
-			break
-		}
-		data, err := inputStream.Read()
+		data, err := input.Read()
 		if err != nil {
 			break
 		}
-		callback(source, data)
+		callback(data)
 	}
-	inboundRay.Release()
 }

+ 11 - 24
transport/internet/websocket/dialer.go

@@ -1,6 +1,7 @@
 package websocket
 
 import (
+	"context"
 	"io/ioutil"
 	"net"
 
@@ -17,16 +18,10 @@ var (
 	globalCache = internal.NewConnectionPool()
 )
 
-func Dial(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (internet.Connection, error) {
-	log.Info("WebSocket|Dailer: Creating connection to ", dest)
-	if src == nil {
-		src = v2net.AnyIP
-	}
-	networkSettings, err := options.Stream.GetEffectiveTransportSettings()
-	if err != nil {
-		return nil, err
-	}
-	wsSettings := networkSettings.(*Config)
+func Dial(ctx context.Context, dest v2net.Destination) (internet.Connection, error) {
+	log.Info("WebSocket|Dialer: Creating connection to ", dest)
+	src := internet.DialerSourceFromContext(ctx)
+	wsSettings := internet.TransportSettingsFromContext(ctx).(*Config)
 
 	id := internal.NewConnectionID(src, dest)
 	var conn *wsconn
@@ -38,7 +33,7 @@ func Dial(src v2net.Address, dest v2net.Destination, options internet.DialerOpti
 	}
 	if conn == nil {
 		var err error
-		conn, err = wsDial(src, dest, options)
+		conn, err = wsDial(ctx, dest)
 		if err != nil {
 			log.Warning("WebSocket|Dialer: Dial failed: ", err)
 			return nil, err
@@ -51,12 +46,9 @@ func init() {
 	common.Must(internet.RegisterTransportDialer(internet.TransportProtocol_WebSocket, Dial))
 }
 
-func wsDial(src v2net.Address, dest v2net.Destination, options internet.DialerOptions) (*wsconn, error) {
-	networkSettings, err := options.Stream.GetEffectiveTransportSettings()
-	if err != nil {
-		return nil, err
-	}
-	wsSettings := networkSettings.(*Config)
+func wsDial(ctx context.Context, dest v2net.Destination) (*wsconn, error) {
+	src := internet.DialerSourceFromContext(ctx)
+	wsSettings := internet.TransportSettingsFromContext(ctx).(*Config)
 
 	commonDial := func(network, addr string) (net.Conn, error) {
 		return internet.DialSystem(src, dest)
@@ -70,15 +62,10 @@ func wsDial(src v2net.Address, dest v2net.Destination, options internet.DialerOp
 
 	protocol := "ws"
 
-	if options.Stream != nil && options.Stream.HasSecuritySettings() {
-		protocol = "wss"
-		securitySettings, err := options.Stream.GetEffectiveSecuritySettings()
-		if err != nil {
-			log.Error("WebSocket: Failed to create security settings: ", err)
-			return nil, err
-		}
+	if securitySettings := internet.SecuritySettingsFromContext(ctx); securitySettings != nil {
 		tlsConfig, ok := securitySettings.(*v2tls.Config)
 		if ok {
+			protocol = "wss"
 			dialer.TLSClientConfig = tlsConfig.GetTLSConfig()
 			if dest.Address.Family().IsDomain() {
 				dialer.TLSClientConfig.ServerName = dest.Address.Domain()

+ 16 - 57
transport/internet/websocket/ws_test.go

@@ -6,6 +6,8 @@ import (
 
 	"bytes"
 
+	"context"
+
 	v2net "v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/testing/assert"
@@ -54,19 +56,10 @@ func Test_listenWSAndDial(t *testing.T) {
 			}()
 		}
 	}()
-	conn, err := Dial(v2net.AnyIP, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146), internet.DialerOptions{
-		Stream: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_WebSocket,
-			TransportSettings: []*internet.TransportConfig{
-				{
-					Protocol: internet.TransportProtocol_WebSocket,
-					Settings: serial.ToTypedMessage(&Config{
-						Path: "ws",
-					}),
-				},
-			},
-		},
-	})
+
+	ctx := internet.ContextWithTransportSettings(context.Background(), &Config{Path: "ws"})
+	conn, err := Dial(ctx, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146))
+
 	assert.Error(err).IsNil()
 	_, err = conn.Write([]byte("Test connection 1"))
 	assert.Error(err).IsNil()
@@ -78,19 +71,7 @@ func Test_listenWSAndDial(t *testing.T) {
 
 	assert.Error(conn.Close()).IsNil()
 	<-time.After(time.Second * 5)
-	conn, err = Dial(v2net.AnyIP, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146), internet.DialerOptions{
-		Stream: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_WebSocket,
-			TransportSettings: []*internet.TransportConfig{
-				{
-					Protocol: internet.TransportProtocol_WebSocket,
-					Settings: serial.ToTypedMessage(&Config{
-						Path: "ws",
-					}),
-				},
-			},
-		},
-	})
+	conn, err = Dial(ctx, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146))
 	assert.Error(err).IsNil()
 	_, err = conn.Write([]byte("Test connection 2"))
 	assert.Error(err).IsNil()
@@ -99,19 +80,7 @@ func Test_listenWSAndDial(t *testing.T) {
 	assert.String(string(b[:n])).Equals("Response")
 	assert.Error(conn.Close()).IsNil()
 	<-time.After(time.Second * 15)
-	conn, err = Dial(v2net.AnyIP, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146), internet.DialerOptions{
-		Stream: &internet.StreamConfig{
-			Protocol: internet.TransportProtocol_WebSocket,
-			TransportSettings: []*internet.TransportConfig{
-				{
-					Protocol: internet.TransportProtocol_WebSocket,
-					Settings: serial.ToTypedMessage(&Config{
-						Path: "ws",
-					}),
-				},
-			},
-		},
-	})
+	conn, err = Dial(ctx, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13146))
 	assert.Error(err).IsNil()
 	_, err = conn.Write([]byte("Test connection 3"))
 	assert.Error(err).IsNil()
@@ -157,26 +126,16 @@ func Test_listenWSAndDial_TLS(t *testing.T) {
 		conn.Close()
 		listen.Close()
 	}()
-	conn, err := Dial(v2net.AnyIP, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13143), internet.DialerOptions{
-		Stream: &internet.StreamConfig{
-			SecurityType: serial.GetMessageType(new(v2tls.Config)),
-			SecuritySettings: []*serial.TypedMessage{serial.ToTypedMessage(&v2tls.Config{
-				AllowInsecure: true,
-			})},
-			Protocol: internet.TransportProtocol_WebSocket,
-			TransportSettings: []*internet.TransportConfig{
-				{
-					Protocol: internet.TransportProtocol_WebSocket,
-					Settings: serial.ToTypedMessage(&Config{
-						Path: "wss",
-						ConnectionReuse: &ConnectionReuse{
-							Enable: true,
-						},
-					}),
-				},
-			},
+	ctx := internet.ContextWithTransportSettings(context.Background(), &Config{
+		Path: "wss",
+		ConnectionReuse: &ConnectionReuse{
+			Enable: true,
 		},
 	})
+	ctx = internet.ContextWithSecuritySettings(ctx, &v2tls.Config{
+		AllowInsecure: true,
+	})
+	conn, err := Dial(ctx, v2net.TCPDestination(v2net.DomainAddress("localhost"), 13143))
 	assert.Error(err).IsNil()
 	conn.Close()
 }

+ 20 - 4
transport/ray/direct.go

@@ -6,6 +6,8 @@ import (
 
 	"time"
 
+	"context"
+
 	"v2ray.com/core/common/buf"
 )
 
@@ -16,10 +18,10 @@ const (
 var ErrReadTimeout = errors.New("Ray: timeout.")
 
 // NewRay creates a new Ray for direct traffic transport.
-func NewRay() Ray {
+func NewRay(ctx context.Context) Ray {
 	return &directRay{
-		Input:  NewStream(),
-		Output: NewStream(),
+		Input:  NewStream(ctx),
+		Output: NewStream(ctx),
 	}
 }
 
@@ -54,13 +56,15 @@ func (v *directRay) AddInspector(inspector Inspector) {
 
 type Stream struct {
 	buffer    chan *buf.Buffer
+	ctx       context.Context
 	close     chan bool
 	err       chan bool
 	inspector *InspectorChain
 }
 
-func NewStream() *Stream {
+func NewStream(ctx context.Context) *Stream {
 	return &Stream{
+		ctx:       ctx,
 		buffer:    make(chan *buf.Buffer, bufferSize),
 		close:     make(chan bool),
 		err:       make(chan bool),
@@ -70,12 +74,16 @@ func NewStream() *Stream {
 
 func (v *Stream) Read() (*buf.Buffer, error) {
 	select {
+	case <-v.ctx.Done():
+		return nil, io.ErrClosedPipe
 	case <-v.err:
 		return nil, io.ErrClosedPipe
 	case b := <-v.buffer:
 		return b, nil
 	default:
 		select {
+		case <-v.ctx.Done():
+			return nil, io.ErrClosedPipe
 		case b := <-v.buffer:
 			return b, nil
 		case <-v.close:
@@ -88,12 +96,16 @@ func (v *Stream) Read() (*buf.Buffer, error) {
 
 func (v *Stream) ReadTimeout(timeout time.Duration) (*buf.Buffer, error) {
 	select {
+	case <-v.ctx.Done():
+		return nil, io.ErrClosedPipe
 	case <-v.err:
 		return nil, io.ErrClosedPipe
 	case b := <-v.buffer:
 		return b, nil
 	default:
 		select {
+		case <-v.ctx.Done():
+			return nil, io.ErrClosedPipe
 		case b := <-v.buffer:
 			return b, nil
 		case <-v.close:
@@ -112,12 +124,16 @@ func (v *Stream) Write(data *buf.Buffer) (err error) {
 	}
 
 	select {
+	case <-v.ctx.Done():
+		return io.ErrClosedPipe
 	case <-v.err:
 		return io.ErrClosedPipe
 	case <-v.close:
 		return io.ErrClosedPipe
 	default:
 		select {
+		case <-v.ctx.Done():
+			return io.ErrClosedPipe
 		case <-v.err:
 			return io.ErrClosedPipe
 		case <-v.close:

+ 4 - 2
transport/ray/direct_test.go

@@ -4,6 +4,8 @@ import (
 	"io"
 	"testing"
 
+	"context"
+
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/testing/assert"
 	. "v2ray.com/core/transport/ray"
@@ -12,7 +14,7 @@ import (
 func TestStreamIO(t *testing.T) {
 	assert := assert.On(t)
 
-	stream := NewStream()
+	stream := NewStream(context.Background())
 	b1 := buf.New()
 	b1.AppendBytes('a')
 	assert.Error(stream.Write(b1)).IsNil()
@@ -33,7 +35,7 @@ func TestStreamIO(t *testing.T) {
 func TestStreamClose(t *testing.T) {
 	assert := assert.On(t)
 
-	stream := NewStream()
+	stream := NewStream(context.Background())
 	b1 := buf.New()
 	b1.AppendBytes('a')
 	assert.Error(stream.Write(b1)).IsNil()

+ 14 - 91
v2ray.go

@@ -6,22 +6,13 @@ import (
 	"v2ray.com/core/app"
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/dns"
-	proxydialer "v2ray.com/core/app/proxy"
 	"v2ray.com/core/app/proxyman"
-	"v2ray.com/core/common"
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
 )
 
 // Point shell of V2Ray.
 type Point struct {
-	inboundHandlers       []InboundDetourHandler
-	taggedInboundHandlers map[string]InboundDetourHandler
-
-	outboundHandlers       []proxy.OutboundHandler
-	taggedOutboundHandlers map[string]proxy.OutboundHandler
-
 	space app.Space
 }
 
@@ -42,7 +33,6 @@ func NewPoint(pConfig *Config) (*Point, error) {
 	ctx := app.ContextWithSpace(context.Background(), space)
 
 	vpoint.space = space
-	vpoint.space.AddApplication(vpoint)
 
 	outboundHandlerManager := proxyman.OutboundHandlerManagerFromSpace(space)
 	if outboundHandlerManager == nil {
@@ -54,16 +44,15 @@ func NewPoint(pConfig *Config) (*Point, error) {
 		outboundHandlerManager = o.(proxyman.OutboundHandlerManager)
 	}
 
-	proxyDialer := proxydialer.OutboundProxyFromSpace(space)
-	if proxyDialer == nil {
-		p, err := app.CreateAppFromConfig(ctx, new(proxydialer.Config))
+	inboundHandlerManager := proxyman.InboundHandlerManagerFromSpace(space)
+	if inboundHandlerManager == nil {
+		o, err := app.CreateAppFromConfig(ctx, new(proxyman.InboundConfig))
 		if err != nil {
 			return nil, err
 		}
-		space.AddApplication(p)
-		proxyDialer = p.(*proxydialer.OutboundProxy)
+		space.AddApplication(o)
+		inboundHandlerManager = o.(proxyman.InboundHandlerManager)
 	}
-	proxyDialer.RegisterDialer()
 
 	for _, appSettings := range pConfig.App {
 		settings, err := appSettings.GetInstance()
@@ -104,62 +93,16 @@ func NewPoint(pConfig *Config) (*Point, error) {
 		disp = d.(dispatcher.Interface)
 	}
 
-	vpoint.inboundHandlers = make([]InboundDetourHandler, 0, 8)
-	vpoint.taggedInboundHandlers = make(map[string]InboundDetourHandler)
 	for _, inbound := range pConfig.Inbound {
-		allocConfig := inbound.GetAllocationStrategyValue()
-		var inboundHandler InboundDetourHandler
-		switch allocConfig.Type {
-		case AllocationStrategy_Always:
-			dh, err := NewInboundDetourHandlerAlways(ctx, inbound)
-			if err != nil {
-				log.Error("V2Ray: Failed to create detour handler: ", err)
-				return nil, common.ErrBadConfiguration
-			}
-			inboundHandler = dh
-		case AllocationStrategy_Random:
-			dh, err := NewInboundDetourHandlerDynamic(ctx, inbound)
-			if err != nil {
-				log.Error("V2Ray: Failed to create detour handler: ", err)
-				return nil, common.ErrBadConfiguration
-			}
-			inboundHandler = dh
-		default:
-			log.Error("V2Ray: Unknown allocation strategy: ", allocConfig.Type)
-			return nil, common.ErrBadConfiguration
-		}
-		vpoint.inboundHandlers = append(vpoint.inboundHandlers, inboundHandler)
-		if len(inbound.Tag) > 0 {
-			vpoint.taggedInboundHandlers[inbound.Tag] = inboundHandler
+		if err := inboundHandlerManager.AddHandler(ctx, inbound); err != nil {
+			return nil, err
 		}
 	}
 
-	vpoint.outboundHandlers = make([]proxy.OutboundHandler, 0, 8)
-	vpoint.taggedOutboundHandlers = make(map[string]proxy.OutboundHandler)
-	for idx, outbound := range pConfig.Outbound {
-		outboundSettings, err := outbound.GetTypedSettings()
-		if err != nil {
+	for _, outbound := range pConfig.Outbound {
+		if err := outboundHandlerManager.AddHandler(ctx, outbound); err != nil {
 			return nil, err
 		}
-		outboundHandler, err := proxy.CreateOutboundHandler(proxy.ContextWithOutboundMeta(ctx, &proxy.OutboundHandlerMeta{
-			Tag:            outbound.Tag,
-			Address:        outbound.GetSendThroughValue(),
-			StreamSettings: outbound.StreamSettings,
-			ProxySettings:  outbound.ProxySettings,
-		}), outboundSettings)
-		if err != nil {
-			log.Error("V2Ray: Failed to create detour outbound connection handler: ", err)
-			return nil, err
-		}
-		if idx == 0 {
-			outboundHandlerManager.SetDefaultHandler(outboundHandler)
-		}
-		if len(outbound.Tag) > 0 {
-			outboundHandlerManager.SetHandler(outbound.Tag, outboundHandler)
-			vpoint.taggedOutboundHandlers[outbound.Tag] = outboundHandler
-		}
-
-		vpoint.outboundHandlers = append(vpoint.outboundHandlers, outboundHandler)
 	}
 
 	if err := vpoint.space.Initialize(); err != nil {
@@ -169,39 +112,19 @@ func NewPoint(pConfig *Config) (*Point, error) {
 	return vpoint, nil
 }
 
-func (Point) Interface() interface{} {
-	return (*proxyman.InboundHandlerManager)(nil)
-}
-
 func (v *Point) Close() {
-	for _, inbound := range v.inboundHandlers {
-		inbound.Close()
-	}
+	ihm := proxyman.InboundHandlerManagerFromSpace(v.space)
+	ihm.Close()
 }
 
 // Start starts the Point server, and return any error during the process.
 // In the case of any errors, the state of the server is unpredicatable.
 func (v *Point) Start() error {
-	for _, inbound := range v.inboundHandlers {
-		err := inbound.Start()
-		if err != nil {
-			return err
-		}
+	ihm := proxyman.InboundHandlerManagerFromSpace(v.space)
+	if err := ihm.Start(); err != nil {
+		return err
 	}
 	log.Warning("V2Ray started.")
 
 	return nil
 }
-
-func (v *Point) GetHandler(tag string) (proxy.InboundHandler, int) {
-	handler, found := v.taggedInboundHandlers[tag]
-	if !found {
-		log.Warning("V2Ray: Unable to find an inbound handler with tag: ", tag)
-		return nil, 0
-	}
-	return handler.GetConnectionHandler()
-}
-
-func (v *Point) Release() {
-
-}