浏览代码

massive refactoring for interoperability

Darien Raymond 7 年之前
父节点
当前提交
292d7cc353
共有 66 个文件被更改,包括 1504 次插入1189 次删除
  1. 0 2
      app/app.go
  2. 21 32
      app/dispatcher/default.go
  3. 1 19
      app/dispatcher/dispatcher.go
  4. 5 0
      app/dispatcher/errors.generated.go
  5. 0 7
      app/dispatcher/impl/errors.generated.go
  6. 1 1
      app/dispatcher/sniffer.go
  7. 2 3
      app/dispatcher/sniffer_test.go
  8. 2 2
      app/dns/nameserver.go
  9. 27 31
      app/dns/server.go
  10. 28 32
      app/dns/server_test.go
  11. 16 0
      app/policy/config.go
  12. 2 2
      app/policy/errors.generated.go
  13. 61 0
      app/policy/manager.go
  14. 0 68
      app/policy/manager/manager.go
  15. 1 18
      app/policy/policy.go
  16. 0 17
      app/proxyman/config.go
  17. 48 139
      app/proxyman/config.pb.go
  18. 0 23
      app/proxyman/config.proto
  19. 1 3
      app/proxyman/errors.generated.go
  20. 7 1
      app/proxyman/inbound/always.go
  21. 5 1
      app/proxyman/inbound/dynamic.go
  22. 41 40
      app/proxyman/inbound/inbound.go
  23. 3 3
      app/proxyman/inbound/worker.go
  24. 13 14
      app/proxyman/mux/mux.go
  25. 20 18
      app/proxyman/outbound/handler.go
  26. 15 0
      app/proxyman/outbound/handler_test.go
  27. 23 19
      app/proxyman/outbound/outbound.go
  28. 0 44
      app/proxyman/proxyman.go
  29. 26 35
      app/router/router.go
  30. 21 18
      app/router/router_test.go
  31. 0 132
      app/space.go
  32. 10 0
      common/interfaces.go
  33. 1 1
      common/protocol/user.pb.go
  34. 12 0
      common/router/dispatcher.go
  35. 53 0
      common/router/router.go
  36. 124 28
      config.pb.go
  37. 24 3
      config.proto
  38. 17 0
      context.go
  39. 57 0
      dns.go
  40. 2 2
      main/distro/all/all.go
  41. 165 0
      network.go
  42. 117 0
      policy.go
  43. 30 29
      proxy/dokodemo/dokodemo.go
  44. 28 30
      proxy/freedom/freedom.go
  45. 23 26
      proxy/http/server.go
  46. 2 2
      proxy/proxy.go
  47. 10 19
      proxy/shadowsocks/client.go
  48. 17 27
      proxy/shadowsocks/server.go
  49. 23 27
      proxy/socks/server.go
  50. 21 34
      proxy/vmess/inbound/inbound.go
  51. 12 22
      proxy/vmess/outbound/outbound.go
  52. 118 0
      router.go
  53. 11 0
      testing/scenarios/common.go
  54. 2 2
      testing/scenarios/dns_test.go
  55. 8 8
      testing/scenarios/dokodemo_test.go
  56. 22 22
      testing/scenarios/feature_test.go
  57. 8 8
      testing/scenarios/http_test.go
  58. 26 26
      testing/scenarios/shadowsocks_test.go
  59. 10 10
      testing/scenarios/socks_test.go
  60. 12 12
      testing/scenarios/tls_test.go
  61. 4 4
      testing/scenarios/transport_test.go
  62. 36 36
      testing/scenarios/vmess_test.go
  63. 3 2
      transport/internet/config.pb.go
  64. 3 3
      transport/internet/udp/dispatcher.go
  65. 101 80
      v2ray.go
  66. 2 2
      v2ray_test.go

+ 0 - 2
app/app.go

@@ -1,3 +1 @@
 package app
-
-//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg app -path App

+ 21 - 32
app/dispatcher/impl/default.go → app/dispatcher/default.go

@@ -1,4 +1,4 @@
-package impl
+package dispatcher
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg impl -path App,Dispatcher,Default
 
@@ -6,10 +6,8 @@ import (
 	"context"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
-	"v2ray.com/core/app/router"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
@@ -21,31 +19,27 @@ var (
 	errSniffingTimeout = newError("timeout on sniffing")
 )
 
-var (
-	_ app.Application = (*DefaultDispatcher)(nil)
-)
-
 // DefaultDispatcher is a default implementation of Dispatcher.
 type DefaultDispatcher struct {
-	ohm    proxyman.OutboundHandlerManager
-	router *router.Router
+	ohm    core.OutboundHandlerManager
+	router core.Router
 }
 
 // NewDefaultDispatcher create a new DefaultDispatcher.
-func NewDefaultDispatcher(ctx context.Context, config *dispatcher.Config) (*DefaultDispatcher, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
+func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+
+	d := &DefaultDispatcher{
+		ohm:    v.OutboundHandlerManager(),
+		router: v.Router(),
+	}
+
+	if err := v.RegisterFeature((*core.Dispatcher)(nil), d); err != nil {
+		return nil, newError("unable to register Dispatcher")
 	}
-	d := &DefaultDispatcher{}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		d.ohm = proxyman.OutboundHandlerManagerFromSpace(space)
-		if d.ohm == nil {
-			return newError("OutboundHandlerManager is not found in the space")
-		}
-		d.router = router.FromSpace(space)
-		return nil
-	})
 	return d, nil
 }
 
@@ -57,12 +51,7 @@ func (*DefaultDispatcher) Start() error {
 // Close implements app.Application.
 func (*DefaultDispatcher) Close() {}
 
-// Interface implements app.Application.
-func (*DefaultDispatcher) Interface() interface{} {
-	return (*dispatcher.Interface)(nil)
-}
-
-// Dispatch implements Dispatcher.Interface.
+// Dispatch implements core.Dispatcher.
 func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (ray.InboundRay, error) {
 	if !destination.IsValid() {
 		panic("Dispatcher: Invalid destination.")
@@ -120,7 +109,7 @@ func snifer(ctx context.Context, sniferList []proxyman.KnownProtocols, outbound
 func (d *DefaultDispatcher) routedDispatch(ctx context.Context, outbound ray.OutboundRay, destination net.Destination) {
 	dispatcher := d.ohm.GetDefaultHandler()
 	if d.router != nil {
-		if tag, err := d.router.TakeDetour(ctx); err == nil {
+		if tag, err := d.router.PickRoute(ctx); err == nil {
 			if handler := d.ohm.GetHandler(tag); handler != nil {
 				newError("taking detour [", tag, "] for [", destination, "]").WriteToLog()
 				dispatcher = handler
@@ -135,7 +124,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, outbound ray.Out
 }
 
 func init() {
-	common.Must(common.RegisterConfig((*dispatcher.Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
-		return NewDefaultDispatcher(ctx, config.(*dispatcher.Config))
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewDefaultDispatcher(ctx, config.(*Config))
 	}))
 }

+ 1 - 19
app/dispatcher/dispatcher.go

@@ -1,21 +1,3 @@
 package dispatcher
 
-import (
-	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/transport/ray"
-)
-
-// Interface dispatch a packet and possibly further network payload to its destination.
-type Interface interface {
-	Dispatch(ctx context.Context, dest net.Destination) (ray.InboundRay, error)
-}
-
-func FromSpace(space app.Space) Interface {
-	if app := space.GetApplication((*Interface)(nil)); app != nil {
-		return app.(Interface)
-	}
-	return nil
-}
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg dispatcher -path App,Dispatcher

+ 5 - 0
app/dispatcher/errors.generated.go

@@ -0,0 +1,5 @@
+package dispatcher
+
+import "v2ray.com/core/common/errors"
+
+func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("App", "Dispatcher") }

+ 0 - 7
app/dispatcher/impl/errors.generated.go

@@ -1,7 +0,0 @@
-package impl
-
-import "v2ray.com/core/common/errors"
-
-func newError(values ...interface{}) *errors.Error {
-	return errors.New(values...).Path("App", "Dispatcher", "Default")
-}

+ 1 - 1
app/dispatcher/impl/sniffer.go → app/dispatcher/sniffer.go

@@ -1,4 +1,4 @@
-package impl
+package dispatcher
 
 import (
 	"bytes"

+ 2 - 3
app/dispatcher/impl/sniffer_test.go → app/dispatcher/sniffer_test.go

@@ -1,11 +1,10 @@
-package impl_test
+package dispatcher_test
 
 import (
 	"testing"
 
+	. "v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/proxyman"
-
-	. "v2ray.com/core/app/dispatcher/impl"
 	. "v2ray.com/ext/assert"
 )
 

+ 2 - 2
app/dns/nameserver.go

@@ -6,7 +6,7 @@ import (
 	"time"
 
 	"github.com/miekg/dns"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
@@ -48,7 +48,7 @@ type UDPNameServer struct {
 	nextCleanup time.Time
 }
 
-func NewUDPNameServer(address net.Destination, dispatcher dispatcher.Interface) *UDPNameServer {
+func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
 	s := &UDPNameServer{
 		address:   address,
 		requests:  make(map[uint16]*PendingRequest),

+ 27 - 31
app/dns/server.go

@@ -1,6 +1,6 @@
 package dns
 
-//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg server -path App,DNS,Server
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg dns -path App,DNS
 
 import (
 	"context"
@@ -8,8 +8,7 @@ import (
 	"time"
 
 	dnsmsg "github.com/miekg/dns"
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 )
@@ -41,39 +40,38 @@ type Server struct {
 }
 
 func New(ctx context.Context, config *Config) (*Server, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
-	}
 	server := &Server{
 		records: make(map[string]*DomainRecord),
 		servers: make([]NameServer, len(config.NameServers)),
 		hosts:   config.GetInternalHosts(),
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		disp := dispatcher.FromSpace(space)
-		if disp == nil {
-			return newError("dispatcher is not found in the space")
-		}
-		for idx, destPB := range config.NameServers {
-			address := destPB.Address.AsAddress()
-			if address.Family().IsDomain() && address.Domain() == "localhost" {
-				server.servers[idx] = &LocalNameServer{}
-			} else {
-				dest := destPB.AsDestination()
-				if dest.Network == net.Network_Unknown {
-					dest.Network = net.Network_UDP
-				}
-				if dest.Network == net.Network_UDP {
-					server.servers[idx] = NewUDPNameServer(dest, disp)
-				}
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+
+	if err := v.RegisterFeature((*core.DNSClient)(nil), server); err != nil {
+		return nil, newError("unable to register DNSClient.").Base(err)
+	}
+
+	for idx, destPB := range config.NameServers {
+		address := destPB.Address.AsAddress()
+		if address.Family().IsDomain() && address.Domain() == "localhost" {
+			server.servers[idx] = &LocalNameServer{}
+		} else {
+			dest := destPB.AsDestination()
+			if dest.Network == net.Network_Unknown {
+				dest.Network = net.Network_UDP
+			}
+			if dest.Network == net.Network_UDP {
+				server.servers[idx] = NewUDPNameServer(dest, v.Dispatcher())
 			}
 		}
-		if len(config.NameServers) == 0 {
-			server.servers = append(server.servers, &LocalNameServer{})
-		}
-		return nil
-	})
+	}
+	if len(config.NameServers) == 0 {
+		server.servers = append(server.servers, &LocalNameServer{})
+	}
+
 	return server, nil
 }
 
@@ -82,12 +80,10 @@ func (*Server) Interface() interface{} {
 }
 
 func (s *Server) Start() error {
-	net.RegisterIPResolver(s)
 	return nil
 }
 
 func (*Server) Close() {
-	net.RegisterIPResolver(net.SystemIPResolver())
 }
 
 func (s *Server) GetCached(domain string) []net.IP {

+ 28 - 32
app/dns/server_test.go

@@ -1,18 +1,14 @@
 package dns_test
 
 import (
-	"context"
 	"testing"
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/app/dispatcher"
-	_ "v2ray.com/core/app/dispatcher/impl"
 	. "v2ray.com/core/app/dns"
-	"v2ray.com/core/app/policy"
-	_ "v2ray.com/core/app/policy/manager"
 	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/app/policy"
 	_ "v2ray.com/core/app/proxyman/outbound"
-	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/proxy/freedom"
@@ -54,50 +50,50 @@ func TestUDPServer(t *testing.T) {
 
 	go dnsServer.ListenAndServe()
 
-	config := &Config{
-		NameServers: []*net.Endpoint{
-			{
-				Network: net.Network_UDP,
-				Address: &net.IPOrDomain{
-					Address: &net.IPOrDomain_Ip{
-						Ip: []byte{127, 0, 0, 1},
+	config := &core.Config{
+		App: []*serial.TypedMessage{
+			serial.ToTypedMessage(&Config{
+				NameServers: []*net.Endpoint{
+					{
+						Network: net.Network_UDP,
+						Address: &net.IPOrDomain{
+							Address: &net.IPOrDomain_Ip{
+								Ip: []byte{127, 0, 0, 1},
+							},
+						},
+						Port: uint32(port),
 					},
 				},
-				Port: uint32(port),
+			}),
+			serial.ToTypedMessage(&dispatcher.Config{}),
+			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
+			serial.ToTypedMessage(&policy.Config{}),
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			&core.OutboundHandlerConfig{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 		},
 	}
 
-	ctx := context.Background()
-	space := app.NewSpace()
-
-	ctx = app.ContextWithSpace(ctx, space)
-	common.Must(app.AddApplicationToSpace(ctx, config))
-	common.Must(app.AddApplicationToSpace(ctx, &dispatcher.Config{}))
-	common.Must(app.AddApplicationToSpace(ctx, &proxyman.OutboundConfig{}))
-	common.Must(app.AddApplicationToSpace(ctx, &policy.Config{}))
-
-	om := proxyman.OutboundHandlerManagerFromSpace(space)
-	om.AddHandler(ctx, &proxyman.OutboundHandlerConfig{
-		ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
-	})
+	v, err := core.New(config)
+	assert(err, IsNil)
 
-	common.Must(space.Initialize())
-	common.Must(space.Start())
+	client := v.DNSClient()
 
-	ips, err := net.LookupIP("google.com")
+	ips, err := client.LookupIP("google.com")
 	assert(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{8, 8, 8, 8})
 
-	ips, err = net.LookupIP("facebook.com")
+	ips, err = client.LookupIP("facebook.com")
 	assert(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{9, 9, 9, 9})
 
 	dnsServer.Shutdown()
 
-	ips, err = net.LookupIP("google.com")
+	ips, err = client.LookupIP("google.com")
 	assert(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{8, 8, 8, 8})

+ 16 - 0
app/policy/config.go

@@ -2,10 +2,15 @@ package policy
 
 import (
 	"time"
+
+	"v2ray.com/core"
 )
 
 // Duration converts Second to time.Duration.
 func (s *Second) Duration() time.Duration {
+	if s == nil {
+		return 0
+	}
 	return time.Second * time.Duration(s.Value)
 }
 
@@ -26,3 +31,14 @@ func (p *Policy) OverrideWith(another *Policy) {
 		}
 	}
 }
+
+func (p *Policy) ToCorePolicy() core.Policy {
+	var cp core.Policy
+	if p.Timeout != nil {
+		cp.Timeouts.ConnectionIdle = p.Timeout.ConnectionIdle.Duration()
+		cp.Timeouts.Handshake = p.Timeout.Handshake.Duration()
+		cp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration()
+		cp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration()
+	}
+	return cp
+}

+ 2 - 2
app/errors.generated.go → app/policy/errors.generated.go

@@ -1,5 +1,5 @@
-package app
+package policy
 
 import "v2ray.com/core/common/errors"
 
-func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("App") }
+func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("App", "Policy") }

+ 61 - 0
app/policy/manager.go

@@ -0,0 +1,61 @@
+package policy
+
+import (
+	"context"
+
+	"v2ray.com/core"
+	"v2ray.com/core/common"
+)
+
+// Instance is an instance of Policy manager.
+type Instance struct {
+	levels map[uint32]core.Policy
+}
+
+// New creates new Policy manager instance.
+func New(ctx context.Context, config *Config) (*Instance, error) {
+	m := &Instance{
+		levels: make(map[uint32]core.Policy),
+	}
+	if len(config.Level) > 0 {
+		for lv, p := range config.Level {
+			dp := core.DefaultPolicy()
+			dp.OverrideWith(p.ToCorePolicy())
+			m.levels[lv] = dp
+		}
+	}
+
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+
+	if err := v.RegisterFeature((*core.PolicyManager)(nil), m); err != nil {
+		return nil, newError("unable to register PolicyManager in core").Base(err).AtError()
+	}
+
+	return m, nil
+}
+
+// ForLevel implements core.PolicyManager.
+func (m *Instance) ForLevel(level uint32) core.Policy {
+	if p, ok := m.levels[level]; ok {
+		return p
+	}
+	return core.DefaultPolicy()
+}
+
+// Start implements app.Application.Start().
+func (m *Instance) Start() error {
+	return nil
+}
+
+// Close implements app.Application.Close().
+func (m *Instance) Close() {
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return New(ctx, config.(*Config))
+	}))
+}

+ 0 - 68
app/policy/manager/manager.go

@@ -1,68 +0,0 @@
-package manager
-
-import (
-	"context"
-
-	"v2ray.com/core/app/policy"
-	"v2ray.com/core/common"
-)
-
-// Instance is an instance of Policy manager.
-type Instance struct {
-	levels map[uint32]*policy.Policy
-}
-
-// New creates new Policy manager instance.
-func New(ctx context.Context, config *policy.Config) (*Instance, error) {
-	levels := config.Level
-	if levels == nil {
-		levels = make(map[uint32]*policy.Policy)
-	}
-	for _, p := range levels {
-		g := global()
-		g.OverrideWith(p)
-		*p = g
-	}
-	return &Instance{
-		levels: levels,
-	}, nil
-}
-
-func global() policy.Policy {
-	return policy.Policy{
-		Timeout: &policy.Policy_Timeout{
-			Handshake:      &policy.Second{Value: 4},
-			ConnectionIdle: &policy.Second{Value: 300},
-			UplinkOnly:     &policy.Second{Value: 5},
-			DownlinkOnly:   &policy.Second{Value: 30},
-		},
-	}
-}
-
-// GetPolicy implements policy.Manager.
-func (m *Instance) GetPolicy(level uint32) policy.Policy {
-	if p, ok := m.levels[level]; ok {
-		return *p
-	}
-	return global()
-}
-
-// Start implements app.Application.Start().
-func (m *Instance) Start() error {
-	return nil
-}
-
-// Close implements app.Application.Close().
-func (m *Instance) Close() {
-}
-
-// Interface implement app.Application.Interface().
-func (m *Instance) Interface() interface{} {
-	return (*policy.Manager)(nil)
-}
-
-func init() {
-	common.Must(common.RegisterConfig((*policy.Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
-		return New(ctx, config.(*policy.Config))
-	}))
-}

+ 1 - 18
app/policy/policy.go

@@ -1,20 +1,3 @@
 package policy
 
-import (
-	"v2ray.com/core/app"
-)
-
-// Manager is an utility to manage policy per user level.
-type Manager interface {
-	// GetPolicy returns the Policy for the given user level.
-	GetPolicy(level uint32) Policy
-}
-
-// FromSpace returns the policy.Manager in a space.
-func FromSpace(space app.Space) Manager {
-	app := space.GetApplication((*Manager)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(Manager)
-}
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg policy -path App,Policy

+ 0 - 17
app/proxyman/config.go

@@ -1,11 +1,5 @@
 package proxyman
 
-import (
-	"context"
-
-	"v2ray.com/core/proxy"
-)
-
 func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
 	if s == nil || s.Concurrency == nil {
 		return 3
@@ -19,14 +13,3 @@ func (s *AllocationStrategy) GetRefreshValue() uint32 {
 	}
 	return s.Refresh.Value
 }
-
-func (c *OutboundHandlerConfig) GetProxyHandler(ctx context.Context) (proxy.Outbound, error) {
-	if c == nil {
-		return nil, newError("OutboundHandlerConfig is nil")
-	}
-	config, err := c.ProxySettings.GetInstance()
-	if err != nil {
-		return nil, err
-	}
-	return proxy.CreateOutboundHandler(ctx, config)
-}

+ 48 - 139
app/proxyman/config.pb.go

@@ -3,7 +3,6 @@ package proxyman
 import proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
 import math "math"
-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_transport_internet "v2ray.com/core/transport/internet"
@@ -211,45 +210,13 @@ func (m *ReceiverConfig) GetDomainOverride() []KnownProtocols {
 	return nil
 }
 
-type InboundHandlerConfig struct {
-	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{3} }
-
-func (m *InboundHandlerConfig) GetTag() string {
-	if m != nil {
-		return m.Tag
-	}
-	return ""
-}
-
-func (m *InboundHandlerConfig) GetReceiverSettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.ReceiverSettings
-	}
-	return nil
-}
-
-func (m *InboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.ProxySettings
-	}
-	return nil
-}
-
 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{4} }
+func (*OutboundConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
 
 type SenderConfig struct {
 	// Send traffic through the given IP. Only IP is allowed.
@@ -262,7 +229,7 @@ type SenderConfig struct {
 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 (*SenderConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
 
 func (m *SenderConfig) GetVia() *v2ray_core_common_net.IPOrDomain {
 	if m != nil {
@@ -292,54 +259,6 @@ func (m *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
 	return nil
 }
 
-type OutboundHandlerConfig struct {
-	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{6} }
-
-func (m *OutboundHandlerConfig) GetTag() string {
-	if m != nil {
-		return m.Tag
-	}
-	return ""
-}
-
-func (m *OutboundHandlerConfig) GetSenderSettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.SenderSettings
-	}
-	return nil
-}
-
-func (m *OutboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.TypedMessage {
-	if m != nil {
-		return m.ProxySettings
-	}
-	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 ""
-}
-
 type MultiplexingConfig struct {
 	// Whether or not Mux is enabled.
 	Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"`
@@ -350,7 +269,7 @@ type MultiplexingConfig struct {
 func (m *MultiplexingConfig) Reset()                    { *m = MultiplexingConfig{} }
 func (m *MultiplexingConfig) String() string            { return proto.CompactTextString(m) }
 func (*MultiplexingConfig) ProtoMessage()               {}
-func (*MultiplexingConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
+func (*MultiplexingConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
 
 func (m *MultiplexingConfig) GetEnabled() bool {
 	if m != nil {
@@ -372,10 +291,8 @@ func init() {
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyConcurrency)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyRefresh)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh")
 	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((*SenderConfig)(nil), "v2ray.core.app.proxyman.SenderConfig")
-	proto.RegisterType((*OutboundHandlerConfig)(nil), "v2ray.core.app.proxyman.OutboundHandlerConfig")
 	proto.RegisterType((*MultiplexingConfig)(nil), "v2ray.core.app.proxyman.MultiplexingConfig")
 	proto.RegisterEnum("v2ray.core.app.proxyman.KnownProtocols", KnownProtocols_name, KnownProtocols_value)
 	proto.RegisterEnum("v2ray.core.app.proxyman.AllocationStrategy_Type", AllocationStrategy_Type_name, AllocationStrategy_Type_value)
@@ -384,57 +301,49 @@ func init() {
 func init() { proto.RegisterFile("v2ray.com/core/app/proxyman/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 822 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xd1, 0x8e, 0xdb, 0x44,
-	0x14, 0xad, 0xe3, 0x34, 0xc9, 0xde, 0xed, 0x7a, 0xdd, 0xa1, 0xd0, 0x10, 0x40, 0x0a, 0x01, 0xd1,
-	0xa8, 0x20, 0xa7, 0xa4, 0xe2, 0x81, 0x27, 0x58, 0x76, 0x2b, 0x75, 0x81, 0x55, 0xcc, 0x24, 0xe2,
-	0xa1, 0x42, 0xb2, 0x66, 0xed, 0xa9, 0x19, 0x61, 0xcf, 0x58, 0x33, 0x93, 0x74, 0xfd, 0x4b, 0x7c,
-	0x05, 0x8f, 0x3c, 0xf0, 0x05, 0xfc, 0x0a, 0x2f, 0xc8, 0x9e, 0x71, 0x76, 0xb7, 0x49, 0x5a, 0x96,
-	0xaa, 0x6f, 0x33, 0xc9, 0x39, 0xc7, 0x73, 0xcf, 0x3d, 0x77, 0x06, 0xc6, 0xab, 0xa9, 0x24, 0x65,
-	0x10, 0x8b, 0x7c, 0x12, 0x0b, 0x49, 0x27, 0xa4, 0x28, 0x26, 0x85, 0x14, 0x17, 0x65, 0x4e, 0xf8,
-	0x24, 0x16, 0xfc, 0x39, 0x4b, 0x83, 0x42, 0x0a, 0x2d, 0xd0, 0xfd, 0x06, 0x29, 0x69, 0x40, 0x8a,
-	0x22, 0x68, 0x50, 0x83, 0x47, 0x2f, 0x49, 0xc4, 0x22, 0xcf, 0x05, 0x9f, 0x28, 0x2a, 0x19, 0xc9,
-	0x26, 0xba, 0x2c, 0x68, 0x12, 0xe5, 0x54, 0x29, 0x92, 0x52, 0x23, 0x35, 0x78, 0xb0, 0x9d, 0xc1,
-	0xa9, 0x9e, 0x90, 0x24, 0x91, 0x54, 0x29, 0x0b, 0xfc, 0x74, 0x37, 0xb0, 0x10, 0x52, 0x5b, 0x54,
-	0xf0, 0x12, 0x4a, 0x4b, 0xc2, 0x55, 0xf5, 0xff, 0x84, 0x71, 0x4d, 0x65, 0x85, 0xbe, 0x5a, 0xc9,
-	0xe8, 0x10, 0x0e, 0x4e, 0xf9, 0xb9, 0x58, 0xf2, 0xe4, 0xb8, 0xfe, 0x79, 0xf4, 0x87, 0x0b, 0xe8,
-	0x28, 0xcb, 0x44, 0x4c, 0x34, 0x13, 0x7c, 0xae, 0x25, 0xd1, 0x34, 0x2d, 0xd1, 0x09, 0xb4, 0xab,
-	0xd3, 0xf7, 0x9d, 0xa1, 0x33, 0xf6, 0xa6, 0x8f, 0x82, 0x1d, 0x06, 0x04, 0x9b, 0xd4, 0x60, 0x51,
-	0x16, 0x14, 0xd7, 0x6c, 0xf4, 0x1b, 0xec, 0xc7, 0x82, 0xc7, 0x4b, 0x29, 0x29, 0x8f, 0xcb, 0x7e,
-	0x6b, 0xe8, 0x8c, 0xf7, 0xa7, 0xa7, 0x37, 0x11, 0xdb, 0xfc, 0xe9, 0xf8, 0x52, 0x10, 0x5f, 0x55,
-	0x47, 0x11, 0x74, 0x25, 0x7d, 0x2e, 0xa9, 0xfa, 0xb5, 0xef, 0xd6, 0x1f, 0x7a, 0xf2, 0x66, 0x1f,
-	0xc2, 0x46, 0x0c, 0x37, 0xaa, 0x83, 0xaf, 0xe0, 0xa3, 0x57, 0x1e, 0x07, 0xdd, 0x83, 0xdb, 0x2b,
-	0x92, 0x2d, 0x8d, 0x6b, 0x07, 0xd8, 0x6c, 0x06, 0x5f, 0xc2, 0xfb, 0x3b, 0xc5, 0xb7, 0x53, 0x46,
-	0x5f, 0x40, 0xbb, 0x72, 0x11, 0x01, 0x74, 0x8e, 0xb2, 0x17, 0xa4, 0x54, 0xfe, 0xad, 0x6a, 0x8d,
-	0x09, 0x4f, 0x44, 0xee, 0x3b, 0xe8, 0x0e, 0xf4, 0x9e, 0x5c, 0x54, 0xed, 0x25, 0x99, 0xdf, 0x1a,
-	0xfd, 0xed, 0x82, 0x87, 0x69, 0x4c, 0xd9, 0x8a, 0x4a, 0xd3, 0x55, 0xf4, 0x0d, 0x40, 0x15, 0x82,
-	0x48, 0x12, 0x9e, 0x1a, 0xed, 0xfd, 0xe9, 0xf0, 0xaa, 0x1d, 0x26, 0x4d, 0x01, 0xa7, 0x3a, 0x08,
-	0x85, 0xd4, 0xb8, 0xc2, 0xe1, 0xbd, 0xa2, 0x59, 0xa2, 0xaf, 0xa1, 0x93, 0x31, 0xa5, 0x29, 0xb7,
-	0x4d, 0xfb, 0x78, 0x07, 0xf9, 0x34, 0x9c, 0xc9, 0x13, 0x91, 0x13, 0xc6, 0xb1, 0x25, 0xa0, 0x5f,
-	0xe0, 0x1d, 0xb2, 0xae, 0x37, 0x52, 0xb6, 0x60, 0xdb, 0x93, 0xcf, 0x6f, 0xd0, 0x13, 0x8c, 0xc8,
-	0x66, 0x30, 0x17, 0x70, 0xa8, 0xb4, 0xa4, 0x24, 0x8f, 0x14, 0xd5, 0x9a, 0xf1, 0x54, 0xf5, 0xdb,
-	0x9b, 0xca, 0xeb, 0x31, 0x08, 0x9a, 0x31, 0x08, 0xe6, 0x35, 0xcb, 0xf8, 0x83, 0x3d, 0xa3, 0x31,
-	0xb7, 0x12, 0xe8, 0x5b, 0xf8, 0x50, 0x1a, 0x07, 0x23, 0x21, 0x59, 0xca, 0x38, 0xc9, 0xa2, 0x84,
-	0x2a, 0xcd, 0x78, 0xfd, 0xf5, 0xfe, 0xed, 0xa1, 0x33, 0xee, 0xe1, 0x81, 0xc5, 0xcc, 0x2c, 0xe4,
-	0xe4, 0x12, 0x81, 0x42, 0x38, 0x4c, 0x6a, 0x1f, 0x22, 0xb1, 0xa2, 0x52, 0xb2, 0x84, 0xf6, 0xbb,
-	0x43, 0x77, 0xec, 0x4d, 0x1f, 0xec, 0xac, 0xf8, 0x07, 0x2e, 0x5e, 0xf0, 0xb0, 0x1a, 0xcb, 0x58,
-	0x64, 0x0a, 0x7b, 0x86, 0x3f, 0xb3, 0xf4, 0xef, 0xdb, 0xbd, 0x8e, 0xdf, 0x1d, 0xfd, 0xe5, 0xc0,
-	0x3d, 0x3b, 0xb1, 0x4f, 0x09, 0x4f, 0xb2, 0x75, 0x8b, 0x7d, 0x70, 0x35, 0x49, 0xeb, 0xde, 0xee,
-	0xe1, 0x6a, 0x89, 0xe6, 0x70, 0xd7, 0x1e, 0x50, 0x5e, 0x9a, 0x63, 0xda, 0xf7, 0xd9, 0x96, 0xf6,
-	0x99, 0x4b, 0xaa, 0x1e, 0xd7, 0xe4, 0xcc, 0xdc, 0x51, 0xd8, 0x6f, 0x04, 0xd6, 0xce, 0x9c, 0x81,
-	0x57, 0x1f, 0xf8, 0x52, 0xd1, 0xbd, 0x91, 0xe2, 0x41, 0xcd, 0x6e, 0xe4, 0x46, 0x3e, 0x78, 0xb3,
-	0xa5, 0xbe, 0x7a, 0x01, 0xfd, 0xd9, 0x82, 0x3b, 0x73, 0xca, 0x93, 0x75, 0x61, 0x8f, 0xc1, 0x5d,
-	0x31, 0x62, 0x43, 0xfb, 0x1f, 0x72, 0x57, 0xa1, 0xb7, 0xc5, 0xa2, 0xf5, 0xe6, 0xb1, 0xf8, 0x69,
-	0x47, 0xf1, 0x0f, 0x5f, 0x23, 0x1a, 0x56, 0x24, 0xab, 0x79, 0xdd, 0x00, 0xf4, 0x0c, 0x50, 0xbe,
-	0xcc, 0x34, 0x2b, 0x32, 0x7a, 0xf1, 0xca, 0x08, 0x5f, 0x8b, 0xca, 0x59, 0x43, 0x61, 0x3c, 0xb5,
-	0xba, 0x77, 0xd7, 0x32, 0x6b, 0x73, 0xff, 0x71, 0xe0, 0xdd, 0xc6, 0xdd, 0xd7, 0x85, 0x65, 0x06,
-	0x87, 0xaa, 0x76, 0xfd, 0xff, 0x46, 0xc5, 0x33, 0xf4, 0xb7, 0x14, 0x14, 0xf4, 0x1e, 0x74, 0xe8,
-	0x45, 0xc1, 0x24, 0xad, 0xbd, 0x71, 0xb1, 0xdd, 0xa1, 0x3e, 0x74, 0x2b, 0x11, 0xca, 0x75, 0x3d,
-	0x94, 0x7b, 0xb8, 0xd9, 0x8e, 0x42, 0x40, 0x9b, 0x36, 0x55, 0x78, 0xca, 0xc9, 0x79, 0x46, 0x93,
-	0xba, 0xfa, 0x1e, 0x6e, 0xb6, 0x68, 0xb8, 0xf9, 0x38, 0x1d, 0x5c, 0x7b, 0x51, 0x1e, 0x7e, 0x02,
-	0xde, 0xf5, 0x19, 0x45, 0x3d, 0x68, 0x3f, 0x5d, 0x2c, 0x42, 0xff, 0x16, 0xea, 0x82, 0xbb, 0xf8,
-	0x71, 0xee, 0x3b, 0xdf, 0x1d, 0xc3, 0x07, 0xb1, 0xc8, 0x77, 0x75, 0x2e, 0x74, 0x9e, 0xf5, 0x9a,
-	0xf5, 0xef, 0xad, 0xfb, 0x3f, 0x4f, 0x31, 0x29, 0x83, 0xe3, 0x0a, 0x75, 0x54, 0x14, 0x26, 0x27,
-	0x39, 0xe1, 0xe7, 0x9d, 0xfa, 0x75, 0x7e, 0xfc, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x25, 0xdf,
-	0x6a, 0xb2, 0x93, 0x08, 0x00, 0x00,
+	// 691 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x5d, 0x6f, 0xd3, 0x3a,
+	0x1c, 0xc6, 0x97, 0xb6, 0x6b, 0x7b, 0xfe, 0x5b, 0xb3, 0x1c, 0x9f, 0x23, 0x2d, 0xa7, 0x07, 0xa4,
+	0x52, 0x90, 0x56, 0x0d, 0x94, 0x40, 0x27, 0x2e, 0xb8, 0x82, 0xd1, 0x4d, 0x62, 0xbc, 0xa8, 0xc1,
+	0xad, 0xb8, 0x98, 0x90, 0x22, 0x2f, 0xf1, 0x8a, 0x45, 0x62, 0x47, 0x8e, 0xdb, 0x2d, 0x5f, 0x89,
+	0x4f, 0xc1, 0x25, 0x9f, 0x81, 0x4f, 0x83, 0xf2, 0xd6, 0x75, 0xeb, 0x3a, 0x98, 0x76, 0xe7, 0xa6,
+	0xcf, 0xf3, 0xb3, 0xfd, 0x3c, 0xb6, 0xa1, 0x37, 0xeb, 0x4b, 0x92, 0x58, 0x9e, 0x08, 0x6d, 0x4f,
+	0x48, 0x6a, 0x93, 0x28, 0xb2, 0x23, 0x29, 0xce, 0x93, 0x90, 0x70, 0xdb, 0x13, 0xfc, 0x94, 0x4d,
+	0xac, 0x48, 0x0a, 0x25, 0xd0, 0x76, 0xa9, 0x94, 0xd4, 0x22, 0x51, 0x64, 0x95, 0xaa, 0xf6, 0xce,
+	0x15, 0x84, 0x27, 0xc2, 0x50, 0x70, 0x9b, 0x53, 0x65, 0x13, 0xdf, 0x97, 0x34, 0x8e, 0x73, 0x42,
+	0xfb, 0xd1, 0x6a, 0x61, 0x24, 0xa4, 0x2a, 0x54, 0xd6, 0x15, 0x95, 0x92, 0x84, 0xc7, 0xe9, 0xff,
+	0x36, 0xe3, 0x8a, 0xca, 0x54, 0xbd, 0xb8, 0xae, 0xee, 0x16, 0xb4, 0x8e, 0xf8, 0x89, 0x98, 0x72,
+	0x7f, 0x90, 0x7d, 0xee, 0x7e, 0xaf, 0x02, 0xda, 0x0f, 0x02, 0xe1, 0x11, 0xc5, 0x04, 0x1f, 0x29,
+	0x49, 0x14, 0x9d, 0x24, 0xe8, 0x00, 0x6a, 0x2a, 0x89, 0xa8, 0xa9, 0x75, 0xb4, 0x9e, 0xde, 0x7f,
+	0x6a, 0xad, 0xd8, 0x8e, 0xb5, 0x6c, 0xb5, 0xc6, 0x49, 0x44, 0x71, 0xe6, 0x46, 0x5f, 0x61, 0xc3,
+	0x13, 0xdc, 0x9b, 0x4a, 0x49, 0xb9, 0x97, 0x98, 0x95, 0x8e, 0xd6, 0xdb, 0xe8, 0x1f, 0xdd, 0x06,
+	0xb6, 0xfc, 0x69, 0x70, 0x01, 0xc4, 0x8b, 0x74, 0xe4, 0x42, 0x43, 0xd2, 0x53, 0x49, 0xe3, 0x2f,
+	0x66, 0x35, 0x9b, 0xe8, 0xf0, 0x6e, 0x13, 0xe1, 0x1c, 0x86, 0x4b, 0x6a, 0xfb, 0x39, 0xdc, 0xbf,
+	0x71, 0x39, 0xe8, 0x5f, 0x58, 0x9f, 0x91, 0x60, 0x9a, 0xa7, 0xd6, 0xc2, 0xf9, 0x8f, 0xf6, 0x33,
+	0xf8, 0x6f, 0x25, 0xfc, 0x7a, 0x4b, 0xf7, 0x09, 0xd4, 0xd2, 0x14, 0x11, 0x40, 0x7d, 0x3f, 0x38,
+	0x23, 0x49, 0x6c, 0xac, 0xa5, 0x63, 0x4c, 0xb8, 0x2f, 0x42, 0x43, 0x43, 0x9b, 0xd0, 0x3c, 0x3c,
+	0x4f, 0xeb, 0x25, 0x81, 0x51, 0xe9, 0xfe, 0xac, 0x82, 0x8e, 0xa9, 0x47, 0xd9, 0x8c, 0xca, 0xbc,
+	0x55, 0xf4, 0x12, 0x20, 0x3d, 0x04, 0xae, 0x24, 0x7c, 0x92, 0xb3, 0x37, 0xfa, 0x9d, 0xc5, 0x38,
+	0xf2, 0xd3, 0x64, 0x71, 0xaa, 0x2c, 0x47, 0x48, 0x85, 0x53, 0x1d, 0xfe, 0x2b, 0x2a, 0x87, 0xe8,
+	0x05, 0xd4, 0x03, 0x16, 0x2b, 0xca, 0x8b, 0xd2, 0x1e, 0xac, 0x30, 0x1f, 0x39, 0x43, 0x79, 0x20,
+	0x42, 0xc2, 0x38, 0x2e, 0x0c, 0xe8, 0x33, 0xfc, 0x43, 0xe6, 0xfb, 0x75, 0xe3, 0x62, 0xc3, 0x45,
+	0x27, 0x8f, 0x6f, 0xd1, 0x09, 0x46, 0x64, 0xf9, 0x60, 0x8e, 0x61, 0x2b, 0x56, 0x92, 0x92, 0xd0,
+	0x8d, 0xa9, 0x52, 0x8c, 0x4f, 0x62, 0xb3, 0xb6, 0x4c, 0x9e, 0x5f, 0x03, 0xab, 0xbc, 0x06, 0xd6,
+	0x28, 0x73, 0xe5, 0xf9, 0x60, 0x3d, 0x67, 0x8c, 0x0a, 0x04, 0x7a, 0x05, 0xf7, 0x64, 0x9e, 0xa0,
+	0x2b, 0x24, 0x9b, 0x30, 0x4e, 0x02, 0xd7, 0xa7, 0xb1, 0x62, 0x3c, 0x9b, 0xdd, 0x5c, 0xef, 0x68,
+	0xbd, 0x26, 0x6e, 0x17, 0x9a, 0x61, 0x21, 0x39, 0xb8, 0x50, 0x20, 0x07, 0xb6, 0xfc, 0x2c, 0x07,
+	0x57, 0xcc, 0xa8, 0x94, 0xcc, 0xa7, 0x66, 0xa3, 0x53, 0xed, 0xe9, 0xfd, 0x9d, 0x95, 0x3b, 0x7e,
+	0xc7, 0xc5, 0x19, 0x77, 0xd2, 0x6b, 0xe9, 0x89, 0x20, 0xc6, 0x7a, 0xee, 0x1f, 0x16, 0xf6, 0xb7,
+	0xb5, 0x66, 0xdd, 0x68, 0x74, 0x0d, 0xd0, 0x87, 0x53, 0xb5, 0x78, 0x63, 0x7f, 0x54, 0x60, 0x73,
+	0x44, 0xb9, 0x3f, 0x2f, 0x7b, 0x0f, 0xaa, 0x33, 0x46, 0x8a, 0x96, 0xff, 0xa0, 0xa8, 0x54, 0x7d,
+	0x5d, 0x8e, 0x95, 0xbb, 0xe7, 0xf8, 0x11, 0xf4, 0x6c, 0x7b, 0x17, 0xd0, 0xbc, 0xf6, 0xdd, 0xdf,
+	0x40, 0x9d, 0xd4, 0x54, 0x30, 0x5b, 0x19, 0x61, 0x8e, 0x3c, 0x06, 0x14, 0x4e, 0x03, 0xc5, 0xa2,
+	0x80, 0x9e, 0xdf, 0xd8, 0xf9, 0xa5, 0x6c, 0x3f, 0x94, 0x16, 0xc6, 0x27, 0x05, 0xf7, 0xef, 0x39,
+	0xa6, 0x64, 0x77, 0x1d, 0x40, 0xcb, 0x42, 0x64, 0x42, 0x83, 0x72, 0x72, 0x12, 0x50, 0x3f, 0xcb,
+	0xb4, 0x89, 0xcb, 0x9f, 0xa8, 0xb3, 0xfc, 0x9e, 0xb5, 0x2e, 0x3d, 0x42, 0xbb, 0x0f, 0x41, 0xbf,
+	0x5c, 0x2b, 0x6a, 0x42, 0xed, 0xcd, 0x78, 0xec, 0x18, 0x6b, 0xa8, 0x01, 0xd5, 0xf1, 0xfb, 0x91,
+	0xa1, 0xbd, 0x1e, 0xc0, 0xff, 0x9e, 0x08, 0x57, 0xad, 0xdd, 0xd1, 0x8e, 0x9b, 0xe5, 0xf8, 0x5b,
+	0x65, 0xfb, 0x53, 0x1f, 0x93, 0xc4, 0x1a, 0xa4, 0xaa, 0xfd, 0x28, 0xca, 0x93, 0x0a, 0x09, 0x3f,
+	0xa9, 0x67, 0x0f, 0xfa, 0xde, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0x7f, 0xdc, 0x8e, 0x94,
+	0x06, 0x00, 0x00,
 }

+ 0 - 23
app/proxyman/config.proto

@@ -6,7 +6,6 @@ option go_package = "proxyman";
 option java_package = "com.v2ray.core.app.proxyman";
 option java_multiple_files = true;
 
-import "v2ray.com/core/common/serial/typed_message.proto";
 import "v2ray.com/core/common/net/address.proto";
 import "v2ray.com/core/common/net/port.proto";
 import "v2ray.com/core/transport/internet/config.proto";
@@ -61,15 +60,6 @@ message ReceiverConfig {
   repeated KnownProtocols domain_override = 7;
 }
 
-message InboundHandlerConfig {
-  // Tag of the inbound handler.
-  string tag = 1;
-  // Settings for how this inbound proxy is handled. Must be ReceiverConfig above.
-  v2ray.core.common.serial.TypedMessage receiver_settings = 2;
-  // Settings for inbound proxy. Must be one of the inbound proxies.
-  v2ray.core.common.serial.TypedMessage proxy_settings = 3;
-}
-
 message OutboundConfig {
   
 }
@@ -82,19 +72,6 @@ message SenderConfig {
   MultiplexingConfig multiplex_settings = 4;
 }
 
-message OutboundHandlerConfig {
-  // Tag of this outbound handler.
-  string tag = 1;
-  // Settings for how to dial connection for this outbound handler. Must be SenderConfig above.
-  v2ray.core.common.serial.TypedMessage sender_settings = 2;
-  // Settings for this outbound proxy. Must be one of the outbound proxies.
-  v2ray.core.common.serial.TypedMessage proxy_settings = 3;
-  // If not zero, this outbound will be expired in seconds. Not used for now.
-  int64 expire = 4;
-  // Comment of this outbound handler. Not used for now.
-  string comment = 5;
-}
-
 message MultiplexingConfig {
   // Whether or not Mux is enabled.
   bool enabled = 1;

+ 1 - 3
app/proxyman/errors.generated.go

@@ -2,6 +2,4 @@ package proxyman
 
 import "v2ray.com/core/common/errors"
 
-func newError(values ...interface{}) *errors.Error {
-	return errors.New(values...).Path("App", "Proxyman")
-}
+func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("App", "Proxyman") }

+ 7 - 1
app/proxyman/inbound/always.go

@@ -14,6 +14,7 @@ type AlwaysOnInboundHandler struct {
 	proxy   proxy.Inbound
 	workers []worker
 	mux     *mux.Server
+	tag     string
 }
 
 func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
@@ -25,6 +26,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 	h := &AlwaysOnInboundHandler{
 		proxy: p,
 		mux:   mux.NewServer(ctx),
+		tag:   tag,
 	}
 
 	nl := p.Network()
@@ -80,10 +82,14 @@ func (h *AlwaysOnInboundHandler) Close() {
 	}
 }
 
-func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port, int) {
+func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
 	if len(h.workers) == 0 {
 		return nil, 0, 0
 	}
 	w := h.workers[dice.Roll(len(h.workers))]
 	return w.Proxy(), w.Port(), 9999
 }
+
+func (h *AlwaysOnInboundHandler) Tag() string {
+	return h.tag
+}

+ 5 - 1
app/proxyman/inbound/dynamic.go

@@ -163,7 +163,7 @@ func (h *DynamicInboundHandler) Close() {
 	h.cancel()
 }
 
-func (h *DynamicInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port, int) {
+func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
 	h.workerMutex.RLock()
 	defer h.workerMutex.RUnlock()
 
@@ -174,3 +174,7 @@ func (h *DynamicInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port
 	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
 	return w.Proxy(), w.Port(), int(expire)
 }
+
+func (h *DynamicInboundHandler) Tag() string {
+	return h.tag
+}

+ 41 - 40
app/proxyman/inbound/inbound.go

@@ -5,64 +5,41 @@ package inbound
 import (
 	"context"
 
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 )
 
 // Manager is to manage all inbound handlers.
 type Manager struct {
-	handlers       []proxyman.InboundHandler
-	taggedHandlers map[string]proxyman.InboundHandler
+	handlers       []core.InboundHandler
+	taggedHandlers map[string]core.InboundHandler
 }
 
 func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) {
-	return &Manager{
-		taggedHandlers: make(map[string]proxyman.InboundHandler),
-	}, nil
-}
-
-func (m *Manager) 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 newError("not a ReceiverConfig").AtError()
-	}
-	proxySettings, err := config.ProxySettings.GetInstance()
-	if err != nil {
-		return err
+	m := &Manager{
+		taggedHandlers: make(map[string]core.InboundHandler),
 	}
-	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
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context")
 	}
-
-	if handler == nil {
-		return newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
+	if err := v.RegisterFeature((*core.InboundHandlerManager)(nil), m); err != nil {
+		return nil, newError("unable to register InboundHandlerManager").Base(err)
 	}
+	return m, nil
+}
 
+func (m *Manager) AddHandler(ctx context.Context, handler core.InboundHandler) error {
 	m.handlers = append(m.handlers, handler)
+	tag := handler.Tag()
 	if len(tag) > 0 {
 		m.taggedHandlers[tag] = handler
 	}
 	return nil
 }
 
-func (m *Manager) GetHandler(ctx context.Context, tag string) (proxyman.InboundHandler, error) {
+func (m *Manager) GetHandler(ctx context.Context, tag string) (core.InboundHandler, error) {
 	handler, found := m.taggedHandlers[tag]
 	if !found {
 		return nil, newError("handler not found: ", tag)
@@ -85,12 +62,36 @@ func (m *Manager) Close() {
 	}
 }
 
-func (m *Manager) Interface() interface{} {
-	return (*proxyman.InboundHandlerManager)(nil)
+func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (core.InboundHandler, error) {
+	rawReceiverSettings, err := config.ReceiverSettings.GetInstance()
+	if err != nil {
+		return nil, err
+	}
+	receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
+	if !ok {
+		return nil, newError("not a ReceiverConfig").AtError()
+	}
+	proxySettings, err := config.ProxySettings.GetInstance()
+	if err != nil {
+		return nil, err
+	}
+	tag := config.Tag
+	allocStrategy := receiverSettings.AllocationStrategy
+	if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
+		return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
+	}
+
+	if allocStrategy.Type == proxyman.AllocationStrategy_Random {
+		return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
+	}
+	return nil, newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
 }
 
 func init() {
 	common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		return New(ctx, config.(*proxyman.InboundConfig))
 	}))
+	common.Must(common.RegisterConfig((*core.InboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewHandler(ctx, config.(*core.InboundHandlerConfig))
+	}))
 }

+ 3 - 3
app/proxyman/inbound/worker.go

@@ -7,7 +7,7 @@ import (
 	"sync/atomic"
 	"time"
 
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
@@ -31,7 +31,7 @@ type tcpWorker struct {
 	stream       *internet.StreamConfig
 	recvOrigDest bool
 	tag          string
-	dispatcher   dispatcher.Interface
+	dispatcher   core.Dispatcher
 	sniffers     []proxyman.KnownProtocols
 
 	ctx    context.Context
@@ -185,7 +185,7 @@ type udpWorker struct {
 	port         net.Port
 	recvOrigDest bool
 	tag          string
-	dispatcher   dispatcher.Interface
+	dispatcher   core.Dispatcher
 
 	ctx        context.Context
 	cancel     context.CancelFunc

+ 13 - 14
app/proxyman/mux/mux.go

@@ -8,8 +8,7 @@ import (
 	"sync"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
@@ -262,21 +261,14 @@ func (m *Client) fetchOutput() {
 }
 
 type Server struct {
-	dispatcher dispatcher.Interface
+	dispatcher core.Dispatcher
 }
 
 // NewServer creates a new mux.Server.
 func NewServer(ctx context.Context) *Server {
-	s := &Server{}
-	space := app.SpaceFromContext(ctx)
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		d := dispatcher.FromSpace(space)
-		if d == nil {
-			return newError("no dispatcher in space")
-		}
-		s.dispatcher = d
-		return nil
-	})
+	s := &Server{
+		dispatcher: core.FromContext(ctx).Dispatcher(),
+	}
 	return s
 }
 
@@ -295,8 +287,15 @@ func (s *Server) Dispatch(ctx context.Context, dest net.Destination) (ray.Inboun
 	return ray, nil
 }
 
+func (s *Server) Start() error {
+	return nil
+}
+
+func (s *Server) Close() {
+}
+
 type ServerWorker struct {
-	dispatcher     dispatcher.Interface
+	dispatcher     core.Dispatcher
 	outboundRay    ray.OutboundRay
 	sessionManager *SessionManager
 }

+ 20 - 18
app/proxyman/outbound/handler.go

@@ -5,7 +5,7 @@ import (
 	"io"
 	"time"
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/common/buf"
@@ -17,29 +17,22 @@ import (
 )
 
 type Handler struct {
-	config          *proxyman.OutboundHandlerConfig
+	config          *core.OutboundHandlerConfig
 	senderSettings  *proxyman.SenderConfig
 	proxy           proxy.Outbound
-	outboundManager proxyman.OutboundHandlerManager
+	outboundManager core.OutboundHandlerManager
 	mux             *mux.ClientManager
 }
 
-func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*Handler, error) {
-	h := &Handler{
-		config: config,
+func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (*Handler, error) {
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context")
 	}
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
+	h := &Handler{
+		config:          config,
+		outboundManager: v.OutboundHandlerManager(),
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		ohm := proxyman.OutboundHandlerManagerFromSpace(space)
-		if ohm == nil {
-			return newError("no OutboundManager in space")
-		}
-		h.outboundManager = ohm
-		return nil
-	})
 
 	if config.SenderSettings != nil {
 		senderSettings, err := config.SenderSettings.GetInstance()
@@ -54,7 +47,12 @@ func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*H
 		}
 	}
 
-	proxyHandler, err := config.GetProxyHandler(ctx)
+	proxyConfig, err := config.ProxySettings.GetInstance()
+	if err != nil {
+		return nil, err
+	}
+
+	proxyHandler, err := proxy.CreateOutboundHandler(ctx, proxyConfig)
 	if err != nil {
 		return nil, err
 	}
@@ -71,6 +69,10 @@ func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*H
 	return h, nil
 }
 
+func (h *Handler) Tag() string {
+	return h.config.Tag
+}
+
 // Dispatch implements proxy.Outbound.Dispatch.
 func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
 	if h.mux != nil {

+ 15 - 0
app/proxyman/outbound/handler_test.go

@@ -0,0 +1,15 @@
+package outbound_test
+
+import (
+	"testing"
+
+	"v2ray.com/core"
+	. "v2ray.com/core/app/proxyman/outbound"
+	. "v2ray.com/ext/assert"
+)
+
+func TestInterfaces(t *testing.T) {
+	assert := With(t)
+
+	assert((*Handler)(nil), Implements, (*core.OutboundHandler)(nil))
+}

+ 23 - 19
app/proxyman/outbound/outbound.go

@@ -6,6 +6,7 @@ import (
 	"context"
 	"sync"
 
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 )
@@ -13,20 +14,23 @@ import (
 // Manager is to manage all outbound handlers.
 type Manager struct {
 	sync.RWMutex
-	defaultHandler *Handler
-	taggedHandler  map[string]*Handler
+	defaultHandler core.OutboundHandler
+	taggedHandler  map[string]core.OutboundHandler
 }
 
 // New creates a new Manager.
 func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) {
-	return &Manager{
-		taggedHandler: make(map[string]*Handler),
-	}, nil
-}
-
-// Interface implements Application.Interface.
-func (*Manager) Interface() interface{} {
-	return (*proxyman.OutboundHandlerManager)(nil)
+	m := &Manager{
+		taggedHandler: make(map[string]core.OutboundHandler),
+	}
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context")
+	}
+	if err := v.RegisterFeature((*core.OutboundHandlerManager)(nil), m); err != nil {
+		return nil, newError("unable to register OutboundHandlerManager").Base(err)
+	}
+	return m, nil
 }
 
 // Start implements Application.Start
@@ -35,7 +39,7 @@ func (*Manager) Start() error { return nil }
 // Close implements Application.Close
 func (*Manager) Close() {}
 
-func (m *Manager) GetDefaultHandler() proxyman.OutboundHandler {
+func (m *Manager) GetDefaultHandler() core.OutboundHandler {
 	m.RLock()
 	defer m.RUnlock()
 	if m.defaultHandler == nil {
@@ -44,7 +48,7 @@ func (m *Manager) GetDefaultHandler() proxyman.OutboundHandler {
 	return m.defaultHandler
 }
 
-func (m *Manager) GetHandler(tag string) proxyman.OutboundHandler {
+func (m *Manager) GetHandler(tag string) core.OutboundHandler {
 	m.RLock()
 	defer m.RUnlock()
 	if handler, found := m.taggedHandler[tag]; found {
@@ -53,20 +57,17 @@ func (m *Manager) GetHandler(tag string) proxyman.OutboundHandler {
 	return nil
 }
 
-func (m *Manager) AddHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) error {
+func (m *Manager) AddHandler(ctx context.Context, handler core.OutboundHandler) error {
 	m.Lock()
 	defer m.Unlock()
 
-	handler, err := NewHandler(ctx, config)
-	if err != nil {
-		return err
-	}
 	if m.defaultHandler == nil {
 		m.defaultHandler = handler
 	}
 
-	if len(config.Tag) > 0 {
-		m.taggedHandler[config.Tag] = handler
+	tag := handler.Tag()
+	if len(tag) > 0 {
+		m.taggedHandler[tag] = handler
 	}
 
 	return nil
@@ -76,4 +77,7 @@ func init() {
 	common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		return New(ctx, config.(*proxyman.OutboundConfig))
 	}))
+	common.Must(common.RegisterConfig((*core.OutboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewHandler(ctx, config.(*core.OutboundHandlerConfig))
+	}))
 }

+ 0 - 44
app/proxyman/proxyman.go

@@ -5,52 +5,8 @@ package proxyman
 
 import (
 	"context"
-
-	"v2ray.com/core/app"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/proxy"
-	"v2ray.com/core/transport/ray"
 )
 
-type InboundHandlerManager interface {
-	GetHandler(ctx context.Context, tag string) (InboundHandler, error)
-	AddHandler(ctx context.Context, config *InboundHandlerConfig) error
-}
-
-type InboundHandler interface {
-	Start() error
-	Close()
-
-	// For migration
-	GetRandomInboundProxy() (proxy.Inbound, net.Port, int)
-}
-
-type OutboundHandlerManager interface {
-	GetHandler(tag string) OutboundHandler
-	GetDefaultHandler() OutboundHandler
-	AddHandler(ctx context.Context, config *OutboundHandlerConfig) error
-}
-
-type OutboundHandler interface {
-	Dispatch(ctx context.Context, outboundRay ray.OutboundRay)
-}
-
-func InboundHandlerManagerFromSpace(space app.Space) InboundHandlerManager {
-	app := space.GetApplication((*InboundHandlerManager)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(InboundHandlerManager)
-}
-
-func OutboundHandlerManagerFromSpace(space app.Space) OutboundHandlerManager {
-	app := space.GetApplication((*OutboundHandlerManager)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(OutboundHandlerManager)
-}
-
 type key int
 
 const (

+ 26 - 35
app/router/router.go

@@ -5,46 +5,47 @@ package router
 import (
 	"context"
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 )
 
-var (
-	ErrNoRuleApplicable = newError("No rule applicable")
-)
-
 type Router struct {
 	domainStrategy Config_DomainStrategy
 	rules          []Rule
+	dns            core.DNSClient
 }
 
 func NewRouter(ctx context.Context, config *Config) (*Router, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context")
 	}
+
 	r := &Router{
 		domainStrategy: config.DomainStrategy,
 		rules:          make([]Rule, len(config.Rule)),
+		dns:            v.DNSClient(),
 	}
 
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		for idx, rule := range config.Rule {
-			r.rules[idx].Tag = rule.Tag
-			cond, err := rule.BuildCondition()
-			if err != nil {
-				return err
-			}
-			r.rules[idx].Condition = cond
+	for idx, rule := range config.Rule {
+		r.rules[idx].Tag = rule.Tag
+		cond, err := rule.BuildCondition()
+		if err != nil {
+			return nil, err
 		}
-		return nil
-	})
+		r.rules[idx].Condition = cond
+	}
+
+	if err := v.RegisterFeature((*core.Router)(nil), r); err != nil {
+		return nil, newError("unable to register Router").Base(err)
+	}
 	return r, nil
 }
 
 type ipResolver struct {
+	dns      core.DNSClient
 	ip       []net.Address
 	domain   string
 	resolved bool
@@ -57,7 +58,7 @@ func (r *ipResolver) Resolve() []net.Address {
 
 	newError("looking for IP for domain: ", r.domain).WriteToLog()
 	r.resolved = true
-	ips, err := net.LookupIP(r.domain)
+	ips, err := r.dns.LookupIP(r.domain)
 	if err != nil {
 		newError("failed to get IP address").Base(err).WriteToLog()
 	}
@@ -71,8 +72,10 @@ func (r *ipResolver) Resolve() []net.Address {
 	return r.ip
 }
 
-func (r *Router) TakeDetour(ctx context.Context) (string, error) {
-	resolver := &ipResolver{}
+func (r *Router) PickRoute(ctx context.Context) (string, error) {
+	resolver := &ipResolver{
+		dns: r.dns,
+	}
 	if r.domainStrategy == Config_IpOnDemand {
 		if dest, ok := proxy.TargetFromContext(ctx); ok && dest.Address.Family().IsDomain() {
 			resolver.domain = dest.Address.Domain()
@@ -88,7 +91,7 @@ func (r *Router) TakeDetour(ctx context.Context) (string, error) {
 
 	dest, ok := proxy.TargetFromContext(ctx)
 	if !ok {
-		return "", ErrNoRuleApplicable
+		return "", core.ErrNoClue
 	}
 
 	if r.domainStrategy == Config_IpIfNonMatch && dest.Address.Family().IsDomain() {
@@ -104,11 +107,7 @@ func (r *Router) TakeDetour(ctx context.Context) (string, error) {
 		}
 	}
 
-	return "", ErrNoRuleApplicable
-}
-
-func (*Router) Interface() interface{} {
-	return (*Router)(nil)
+	return "", core.ErrNoClue
 }
 
 func (*Router) Start() error {
@@ -117,14 +116,6 @@ func (*Router) Start() error {
 
 func (*Router) Close() {}
 
-func FromSpace(space app.Space) *Router {
-	app := space.GetApplication((*Router)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(*Router)
-}
-
 func init() {
 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		return NewRouter(ctx, config.(*Config))

+ 21 - 18
app/router/router_test.go

@@ -4,13 +4,14 @@ import (
 	"context"
 	"testing"
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"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/app/router"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/serial"
 	"v2ray.com/core/proxy"
 	. "v2ray.com/ext/assert"
 )
@@ -18,28 +19,30 @@ import (
 func TestSimpleRouter(t *testing.T) {
 	assert := With(t)
 
-	config := &Config{
-		Rule: []*RoutingRule{
-			{
-				Tag: "test",
-				NetworkList: &net.NetworkList{
-					Network: []net.Network{net.Network_TCP},
+	config := &core.Config{
+		App: []*serial.TypedMessage{
+			serial.ToTypedMessage(&Config{
+				Rule: []*RoutingRule{
+					{
+						Tag: "test",
+						NetworkList: &net.NetworkList{
+							Network: []net.Network{net.Network_TCP},
+						},
+					},
 				},
-			},
+			}),
+			serial.ToTypedMessage(&dispatcher.Config{}),
+			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
 		},
 	}
 
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-	assert(app.AddApplicationToSpace(ctx, new(dispatcher.Config)), IsNil)
-	assert(app.AddApplicationToSpace(ctx, new(proxyman.OutboundConfig)), IsNil)
-	assert(app.AddApplicationToSpace(ctx, config), IsNil)
-	assert(space.Initialize(), IsNil)
+	v, err := core.New(config)
+	common.Must(err)
 
-	r := FromSpace(space)
+	r := v.Router()
 
-	ctx = proxy.ContextWithTarget(ctx, net.TCPDestination(net.DomainAddress("v2ray.com"), 80))
-	tag, err := r.TakeDetour(ctx)
+	ctx := proxy.ContextWithTarget(context.Background(), net.TCPDestination(net.DomainAddress("v2ray.com"), 80))
+	tag, err := r.PickRoute(ctx)
 	assert(err, IsNil)
 	assert(tag, Equals, "test")
 }

+ 0 - 132
app/space.go

@@ -1,132 +0,0 @@
-package app
-
-import (
-	"context"
-	"reflect"
-
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/event"
-)
-
-// Application is a component that runs in Space.
-type Application interface {
-	Interface() interface{}
-	Start() error
-	Close()
-}
-
-// CreateAppFromConfig creates an Application based on its config. Application must have been registered.
-func CreateAppFromConfig(ctx context.Context, config interface{}) (Application, error) {
-	application, err := common.CreateObject(ctx, config)
-	if err != nil {
-		return nil, err
-	}
-	switch a := application.(type) {
-	case Application:
-		return a, nil
-	default:
-		return nil, newError("not an application")
-	}
-}
-
-// A Space contains all apps that may be available in a V2Ray runtime.
-type Space interface {
-	event.Registry
-	GetApplication(appInterface interface{}) Application
-	AddApplication(application Application) error
-	Initialize() error
-	Start() error
-	Close()
-}
-
-const (
-	// SpaceInitializing is an event to be fired when Space is being initialized.
-	SpaceInitializing event.Event = iota
-)
-
-type spaceImpl struct {
-	event.Listener
-	cache       map[reflect.Type]Application
-	initialized bool
-}
-
-// NewSpace creates a new Space.
-func NewSpace() Space {
-	return &spaceImpl{
-		cache: make(map[reflect.Type]Application),
-	}
-}
-
-func (s *spaceImpl) On(e event.Event, h event.Handler) {
-	if e == SpaceInitializing && s.initialized {
-		_ = h(nil) // Ignore error
-		return
-	}
-	s.Listener.On(e, h)
-}
-
-func (s *spaceImpl) Initialize() error {
-	if s.initialized {
-		return nil
-	}
-	s.initialized = true
-	return s.Fire(SpaceInitializing, nil)
-}
-
-func (s *spaceImpl) GetApplication(appInterface interface{}) Application {
-	if s == nil {
-		return nil
-	}
-	appType := reflect.TypeOf(appInterface)
-	return s.cache[appType]
-}
-
-func (s *spaceImpl) AddApplication(app Application) error {
-	if s == nil {
-		return newError("nil space").AtError()
-	}
-	appType := reflect.TypeOf(app.Interface())
-	s.cache[appType] = app
-	return nil
-}
-
-func (s *spaceImpl) Start() error {
-	for _, app := range s.cache {
-		if err := app.Start(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (s *spaceImpl) Close() {
-	for _, app := range s.cache {
-		app.Close()
-	}
-}
-
-type contextKey int
-
-const (
-	spaceKey = contextKey(0)
-)
-
-func AddApplicationToSpace(ctx context.Context, appConfig interface{}) error {
-	space := SpaceFromContext(ctx)
-	if space == nil {
-		return newError("no space in context").AtError()
-	}
-	application, err := CreateAppFromConfig(ctx, appConfig)
-	if err != nil {
-		return err
-	}
-	return space.AddApplication(application)
-}
-
-func SpaceFromContext(ctx context.Context) Space {
-	return ctx.Value(spaceKey).(Space)
-}
-
-func ContextWithSpace(ctx context.Context, space Space) context.Context {
-	return context.WithValue(ctx, spaceKey, space)
-}

+ 10 - 0
common/interfaces.go

@@ -0,0 +1,10 @@
+package common
+
+// Runnable is the interface for objects that can start to work and stop on demand.
+type Runnable interface {
+	// Start starts the runnable object. Upon the method returning nil, the object begins to function properly.
+	Start() error
+
+	// Close stops the object being working.
+	Close()
+}

+ 1 - 1
common/protocol/user.pb.go

@@ -14,7 +14,7 @@ var _ = math.Inf
 type User struct {
 	Level uint32 `protobuf:"varint,1,opt,name=level" json:"level,omitempty"`
 	Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"`
-	// Protocol specific account information.
+	// Protocol specific account information. Must be the account proto in one of the proxies.
 	Account *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=account" json:"account,omitempty"`
 }
 

+ 12 - 0
common/router/dispatcher.go

@@ -0,0 +1,12 @@
+package router
+
+import (
+	"context"
+
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/transport/ray"
+)
+
+type Dispatcher interface {
+	Dispatch(ctx context.Context, dest net.Destination) (ray.InboundRay, error)
+}

+ 53 - 0
common/router/router.go

@@ -0,0 +1,53 @@
+package router
+
+import (
+	"context"
+	"sync"
+)
+
+type Router interface {
+	Pick(ctx context.Context) (string, bool)
+}
+
+type defaultRouter byte
+
+func (defaultRouter) Pick(ctx context.Context) (string, bool) {
+	return "", false
+}
+
+type syncRouter struct {
+	sync.RWMutex
+	Router
+}
+
+func (r *syncRouter) Pick(ctx context.Context) (string, bool) {
+	r.RLock()
+	defer r.RUnlock()
+
+	return r.Router.Pick(ctx)
+}
+
+func (r *syncRouter) Set(router Router) {
+	r.Lock()
+	defer r.Unlock()
+
+	r.Router = router
+}
+
+var (
+	routerInstance = &syncRouter{
+		Router: defaultRouter(0),
+	}
+)
+
+func RegisterRouter(router Router) {
+	if router == nil {
+		panic("Router is nil.")
+	}
+
+	routerInstance.Set(router)
+}
+
+func Pick(ctx context.Context) (string, bool) {
+	return routerInstance.Router.Pick(ctx)
+}

+ 124 - 28
config.pb.go

@@ -3,7 +3,6 @@ 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_transport "v2ray.com/core/transport"
 
@@ -40,12 +39,12 @@ func (x ConfigFormat) String() string {
 }
 func (ConfigFormat) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-// Master config of V2Ray. V2Ray Core takes this config as input and functions accordingly.
+// Master config of V2Ray. V2Ray takes this config as input and functions accordingly.
 type Config struct {
 	// Inbound handler configurations. Must have at least one item.
-	Inbound []*v2ray_core_app_proxyman.InboundHandlerConfig `protobuf:"bytes,1,rep,name=inbound" json:"inbound,omitempty"`
+	Inbound []*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 []*v2ray_core_app_proxyman.OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound" json:"outbound,omitempty"`
+	Outbound []*OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound" json:"outbound,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 settings.
@@ -60,14 +59,14 @@ func (m *Config) String() string            { return proto.CompactTextString(m)
 func (*Config) ProtoMessage()               {}
 func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-func (m *Config) GetInbound() []*v2ray_core_app_proxyman.InboundHandlerConfig {
+func (m *Config) GetInbound() []*InboundHandlerConfig {
 	if m != nil {
 		return m.Inbound
 	}
 	return nil
 }
 
-func (m *Config) GetOutbound() []*v2ray_core_app_proxyman.OutboundHandlerConfig {
+func (m *Config) GetOutbound() []*OutboundHandlerConfig {
 	if m != nil {
 		return m.Outbound
 	}
@@ -95,34 +94,131 @@ func (m *Config) GetExtension() []*v2ray_core_common_serial.TypedMessage {
 	return nil
 }
 
+type InboundHandlerConfig struct {
+	// Tag of the inbound handler.
+	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	// Settings for how this inbound proxy is handled. Must be ReceiverConfig above.
+	ReceiverSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=receiver_settings,json=receiverSettings" json:"receiver_settings,omitempty"`
+	// Settings for inbound proxy. Must be one of the inbound proxies.
+	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{1} }
+
+func (m *InboundHandlerConfig) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+func (m *InboundHandlerConfig) GetReceiverSettings() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.ReceiverSettings
+	}
+	return nil
+}
+
+func (m *InboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.ProxySettings
+	}
+	return nil
+}
+
+type OutboundHandlerConfig struct {
+	// Tag of this outbound handler.
+	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	// Settings for how to dial connection for this outbound handler. Must be SenderConfig above.
+	SenderSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=sender_settings,json=senderSettings" json:"sender_settings,omitempty"`
+	// Settings for this outbound proxy. Must be one of the outbound proxies.
+	ProxySettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"`
+	// If not zero, this outbound will be expired in seconds. Not used for now.
+	Expire int64 `protobuf:"varint,4,opt,name=expire" json:"expire,omitempty"`
+	// Comment of this outbound handler. Not used for now.
+	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{2} }
+
+func (m *OutboundHandlerConfig) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+func (m *OutboundHandlerConfig) GetSenderSettings() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.SenderSettings
+	}
+	return nil
+}
+
+func (m *OutboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.ProxySettings
+	}
+	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((*Config)(nil), "v2ray.core.Config")
+	proto.RegisterType((*InboundHandlerConfig)(nil), "v2ray.core.InboundHandlerConfig")
+	proto.RegisterType((*OutboundHandlerConfig)(nil), "v2ray.core.OutboundHandlerConfig")
 	proto.RegisterEnum("v2ray.core.ConfigFormat", ConfigFormat_name, ConfigFormat_value)
 }
 
 func init() { proto.RegisterFile("v2ray.com/core/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 336 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xcd, 0x4a, 0xf3, 0x40,
-	0x18, 0x85, 0xbf, 0xb4, 0xf9, 0x6a, 0xfa, 0xb6, 0x48, 0x99, 0x55, 0xa8, 0x2e, 0x8a, 0xd0, 0x52,
-	0x04, 0x27, 0x12, 0x37, 0xe2, 0xd2, 0x8a, 0x3f, 0x05, 0x6d, 0xa9, 0xe2, 0xc2, 0x8d, 0x4c, 0xd3,
-	0x69, 0x09, 0x74, 0xe6, 0x1d, 0x26, 0x53, 0x69, 0x6e, 0xc9, 0x9b, 0xf2, 0x56, 0x24, 0x99, 0xfe,
-	0xa5, 0xe2, 0xc2, 0x55, 0x20, 0x73, 0x9e, 0xe7, 0x4c, 0x4e, 0xe0, 0xe8, 0x23, 0xd4, 0x2c, 0xa5,
-	0x11, 0x8a, 0x20, 0x42, 0xcd, 0x83, 0x08, 0xe5, 0x34, 0x9e, 0x51, 0xa5, 0xd1, 0x20, 0x81, 0xf5,
-	0xa1, 0xe6, 0xcd, 0xee, 0x5e, 0x90, 0x29, 0x15, 0x28, 0x8d, 0xcb, 0x54, 0x30, 0x59, 0xa0, 0x9a,
-	0xe7, 0x3f, 0x94, 0x42, 0xa0, 0x0c, 0x12, 0xae, 0x63, 0x36, 0x0f, 0x4c, 0xaa, 0xf8, 0xe4, 0x5d,
-	0xf0, 0x24, 0x61, 0x33, 0xbe, 0x22, 0xda, 0x7b, 0x84, 0xd1, 0x4c, 0x26, 0x0a, 0xb5, 0x29, 0x88,
-	0x4f, 0xbe, 0x4a, 0x50, 0xe9, 0xe5, 0x2f, 0xc8, 0x1d, 0x1c, 0xc4, 0x72, 0x8c, 0x0b, 0x39, 0xf1,
-	0x9d, 0x56, 0xb9, 0x5b, 0x0b, 0xcf, 0xe8, 0xf6, 0xae, 0x94, 0x29, 0x45, 0xd7, 0x77, 0xa3, 0x0f,
-	0x36, 0x77, 0xcf, 0xe4, 0x64, 0xce, 0xb5, 0xe5, 0x47, 0x6b, 0x9a, 0xf4, 0xc1, 0xc3, 0x85, 0xb1,
-	0xa6, 0x52, 0x6e, 0xa2, 0xbf, 0x9a, 0x06, 0xab, 0x60, 0x51, 0xb5, 0xe1, 0xc9, 0x25, 0x94, 0x99,
-	0x52, 0xbe, 0x9b, 0x6b, 0x3a, 0xbb, 0x1a, 0x3b, 0x01, 0xb5, 0x13, 0xd0, 0x97, 0x6c, 0x82, 0x47,
-	0xbb, 0xc0, 0x28, 0x43, 0xc8, 0x15, 0x54, 0x37, 0xdf, 0xec, 0xff, 0x6f, 0x39, 0xdd, 0x5a, 0x78,
-	0xbc, 0xcb, 0x6f, 0x0e, 0xe9, 0xaa, 0x74, 0x1b, 0x27, 0x37, 0x50, 0xe5, 0x4b, 0xc3, 0x65, 0x12,
-	0xa3, 0xf4, 0x2b, 0x7f, 0xea, 0xde, 0x82, 0x7d, 0xd7, 0x2b, 0x37, 0xdc, 0xd3, 0x0e, 0xd4, 0x6d,
-	0xc1, 0x2d, 0x6a, 0xc1, 0x0c, 0xa9, 0x83, 0x37, 0xcc, 0xa6, 0x1f, 0x2f, 0xa6, 0x8d, 0x7f, 0xc4,
-	0x03, 0xb7, 0xff, 0x3c, 0x78, 0x6a, 0x38, 0xd7, 0x6d, 0x38, 0x8c, 0x50, 0xec, 0xb4, 0x0c, 0x9d,
-	0x37, 0x37, 0x7b, 0x7e, 0x96, 0xe0, 0x35, 0x1c, 0xb1, 0x94, 0xf6, 0x50, 0xf3, 0x71, 0x25, 0xff,
-	0x6f, 0x17, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x62, 0x93, 0x6d, 0x78, 0x65, 0x02, 0x00, 0x00,
+	// 436 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x41, 0x6f, 0xd3, 0x30,
+	0x14, 0xc7, 0x71, 0x13, 0xba, 0xf6, 0x6d, 0x94, 0x60, 0x01, 0xb2, 0x06, 0x87, 0x50, 0x69, 0x53,
+	0xc5, 0xc1, 0x45, 0xe5, 0x82, 0x26, 0x71, 0x61, 0x08, 0xc1, 0xa4, 0xd1, 0x29, 0x45, 0x1c, 0xb8,
+	0x4c, 0x6e, 0xfa, 0x56, 0x45, 0x5a, 0xec, 0xc8, 0x76, 0xa7, 0xe6, 0x2b, 0xf1, 0x3d, 0xb8, 0xf1,
+	0x8d, 0xb8, 0xa0, 0xc4, 0x49, 0x93, 0x41, 0x0f, 0x14, 0x69, 0xa7, 0xc4, 0x79, 0xfe, 0xfd, 0xdf,
+	0xfb, 0x25, 0x31, 0x3c, 0xbb, 0x99, 0x68, 0x91, 0xf3, 0x58, 0xa5, 0xe3, 0x58, 0x69, 0x1c, 0xc7,
+	0x4a, 0x5e, 0x25, 0x4b, 0x9e, 0x69, 0x65, 0x15, 0x85, 0xba, 0xa8, 0xf1, 0xf0, 0xd5, 0x5f, 0x1b,
+	0xd3, 0x54, 0xc9, 0xb1, 0x41, 0x9d, 0x88, 0xeb, 0xb1, 0xcd, 0x33, 0x5c, 0x5c, 0xa6, 0x68, 0x8c,
+	0x58, 0xa2, 0xa3, 0x0f, 0x8f, 0xfe, 0x20, 0xac, 0x16, 0xd2, 0x64, 0x4a, 0xdb, 0x5b, 0x4d, 0x86,
+	0x3f, 0x3a, 0xd0, 0x3d, 0x2d, 0x1f, 0xd0, 0x13, 0xd8, 0x4b, 0xe4, 0x5c, 0xad, 0xe4, 0x82, 0x91,
+	0xd0, 0x1b, 0xed, 0x4f, 0x42, 0xde, 0x4c, 0xc0, 0x3f, 0xb9, 0xd2, 0x47, 0x21, 0x17, 0xd7, 0xa8,
+	0x1d, 0x12, 0xd5, 0x00, 0x7d, 0x0b, 0x3d, 0xb5, 0xb2, 0x0e, 0xee, 0x94, 0xf0, 0x8b, 0x36, 0x3c,
+	0xad, 0x6a, 0xb7, 0xe9, 0x0d, 0x42, 0xdf, 0x80, 0x27, 0xb2, 0x8c, 0xf9, 0x25, 0x79, 0xdc, 0x26,
+	0x9d, 0x28, 0x77, 0xa2, 0xfc, 0x4b, 0x21, 0x7a, 0xee, 0x3c, 0xa3, 0x02, 0xa1, 0x27, 0xd0, 0xdf,
+	0x98, 0xb1, 0xfb, 0x21, 0x19, 0xed, 0x4f, 0x9e, 0xb7, 0xf9, 0x4d, 0x91, 0x57, 0x4d, 0x9b, 0xed,
+	0xf4, 0x3d, 0xf4, 0x71, 0x6d, 0x51, 0x9a, 0x44, 0x49, 0xd6, 0xdd, 0xa9, 0x77, 0x03, 0x9e, 0xf9,
+	0x3d, 0x2f, 0xf0, 0x87, 0x3f, 0x09, 0x3c, 0xde, 0xf6, 0x8a, 0x68, 0x00, 0x9e, 0x15, 0x4b, 0x46,
+	0x42, 0x32, 0xea, 0x47, 0xc5, 0x2d, 0x9d, 0xc1, 0x23, 0x8d, 0x31, 0x26, 0x37, 0xa8, 0x2f, 0x0d,
+	0x5a, 0x9b, 0xc8, 0xa5, 0x61, 0x9d, 0x72, 0xf4, 0x7f, 0x6d, 0x1f, 0xd4, 0x01, 0xb3, 0x8a, 0xa7,
+	0xe7, 0x30, 0xc8, 0xb4, 0x5a, 0xe7, 0x4d, 0xa2, 0xb7, 0x53, 0xe2, 0x83, 0x92, 0xae, 0xe3, 0x86,
+	0xbf, 0x08, 0x3c, 0xd9, 0xfa, 0xd1, 0xb6, 0xf8, 0x4c, 0xe1, 0xa1, 0x41, 0xb9, 0xf8, 0x7f, 0x9b,
+	0x81, 0xc3, 0xef, 0xc8, 0x85, 0x3e, 0x85, 0x2e, 0xae, 0xb3, 0x44, 0x23, 0xf3, 0x43, 0x32, 0xf2,
+	0xa2, 0x6a, 0x45, 0x19, 0xec, 0x15, 0x21, 0x28, 0xdd, 0x8f, 0xd3, 0x8f, 0xea, 0xe5, 0xcb, 0x63,
+	0x38, 0x70, 0xb6, 0x1f, 0x94, 0x4e, 0x85, 0xa5, 0x07, 0xd0, 0xbb, 0x28, 0x4e, 0xcb, 0x7c, 0x75,
+	0x15, 0xdc, 0xa3, 0x3d, 0xf0, 0xcf, 0x66, 0xd3, 0xcf, 0x01, 0x79, 0x77, 0x04, 0x83, 0x58, 0xa5,
+	0xad, 0xa9, 0x2e, 0xc8, 0x37, 0xbf, 0xb8, 0x7e, 0xef, 0xc0, 0xd7, 0x49, 0x24, 0x72, 0x7e, 0xaa,
+	0x34, 0xce, 0xbb, 0xe5, 0x51, 0x7b, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x59, 0x1b,
+	0xee, 0x03, 0x00, 0x00,
 }

+ 24 - 3
config.proto

@@ -6,7 +6,6 @@ option go_package = "core";
 option java_package = "com.v2ray.core";
 option java_multiple_files = true;
 
-import "v2ray.com/core/app/proxyman/config.proto";
 import "v2ray.com/core/common/serial/typed_message.proto";
 import "v2ray.com/core/transport/config.proto";
 
@@ -19,10 +18,10 @@ enum ConfigFormat {
 // Master config of V2Ray. V2Ray takes this config as input and functions accordingly.
 message Config {
   // Inbound handler configurations. Must have at least one item.
-  repeated v2ray.core.app.proxyman.InboundHandlerConfig inbound = 1;
+  repeated InboundHandlerConfig inbound = 1;
 
   // Outbound handler configurations. Must have at least one item. The first item is used as default for routing.
-  repeated v2ray.core.app.proxyman.OutboundHandlerConfig outbound = 2;
+  repeated OutboundHandlerConfig outbound = 2;
 
   reserved 3;
 
@@ -36,3 +35,25 @@ message Config {
   // V2Ray will ignore such config during initialization.
   repeated v2ray.core.common.serial.TypedMessage extension = 6;
 }
+
+message InboundHandlerConfig {
+  // Tag of the inbound handler.
+  string tag = 1;
+  // Settings for how this inbound proxy is handled. Must be ReceiverConfig above.
+  v2ray.core.common.serial.TypedMessage receiver_settings = 2;
+  // Settings for inbound proxy. Must be one of the inbound proxies.
+  v2ray.core.common.serial.TypedMessage proxy_settings = 3;
+}
+
+message OutboundHandlerConfig {
+  // Tag of this outbound handler.
+  string tag = 1;
+  // Settings for how to dial connection for this outbound handler. Must be SenderConfig above.
+  v2ray.core.common.serial.TypedMessage sender_settings = 2;
+  // Settings for this outbound proxy. Must be one of the outbound proxies.
+  v2ray.core.common.serial.TypedMessage proxy_settings = 3;
+  // If not zero, this outbound will be expired in seconds. Not used for now.
+  int64 expire = 4;
+  // Comment of this outbound handler. Not used for now.
+  string comment = 5;
+}

+ 17 - 0
context.go

@@ -0,0 +1,17 @@
+package core
+
+import (
+	"context"
+)
+
+type key int
+
+const v2rayKey key = 1
+
+// FromContext returns a Instance from the given context, or nil if the context doesn't contain one.
+func FromContext(ctx context.Context) *Instance {
+	if s, ok := ctx.Value(v2rayKey).(*Instance); ok {
+		return s
+	}
+	return nil
+}

+ 57 - 0
dns.go

@@ -0,0 +1,57 @@
+package core
+
+import "net"
+import "sync"
+
+// DNSClient is a V2Ray feature for querying DNS information.
+type DNSClient interface {
+	Feature
+	LookupIP(host string) ([]net.IP, error)
+}
+
+type syncDNSClient struct {
+	sync.RWMutex
+	DNSClient
+}
+
+func (d *syncDNSClient) LookupIP(host string) ([]net.IP, error) {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.DNSClient == nil {
+		return net.LookupIP(host)
+	}
+
+	return d.DNSClient.LookupIP(host)
+}
+
+func (d *syncDNSClient) Start() error {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.DNSClient == nil {
+		return nil
+	}
+
+	return d.DNSClient.Start()
+}
+
+func (d *syncDNSClient) Close() {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.DNSClient != nil {
+		d.DNSClient.Close()
+	}
+}
+
+func (d *syncDNSClient) Set(client DNSClient) {
+	if client == nil {
+		return
+	}
+
+	d.Lock()
+	defer d.Unlock()
+
+	d.DNSClient = client
+}

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

@@ -2,10 +2,10 @@ package all
 
 import (
 	// The following are necessary as they register handlers in their init functions.
-	_ "v2ray.com/core/app/dispatcher/impl"
+	_ "v2ray.com/core/app/dispatcher"
 	_ "v2ray.com/core/app/dns"
 	_ "v2ray.com/core/app/log"
-	_ "v2ray.com/core/app/policy/manager"
+	_ "v2ray.com/core/app/policy"
 	_ "v2ray.com/core/app/proxyman/inbound"
 	_ "v2ray.com/core/app/proxyman/outbound"
 	_ "v2ray.com/core/app/router"

+ 165 - 0
network.go

@@ -0,0 +1,165 @@
+package core
+
+import (
+	"context"
+	"sync"
+
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/transport/ray"
+)
+
+// InboundHandler is the interface for handlers that process inbound connections.
+type InboundHandler interface {
+	common.Runnable
+	// The tag of this handler.
+	Tag() string
+
+	// Deprecated. Do not use in new code.
+	GetRandomInboundProxy() (interface{}, net.Port, int)
+}
+
+// OutboundHandler is the interface for handlers that process outbound connections.
+type OutboundHandler interface {
+	Tag() string
+	Dispatch(ctx context.Context, outboundRay ray.OutboundRay)
+}
+
+// InboundHandlerManager is a feature that managers InboundHandlers.
+type InboundHandlerManager interface {
+	Feature
+	// GetHandlers returns an InboundHandler for the given tag.
+	GetHandler(ctx context.Context, tag string) (InboundHandler, error)
+	// AddHandler adds the given handler into this InboundHandlerManager.
+	AddHandler(ctx context.Context, handler InboundHandler) error
+}
+
+type syncInboundHandlerManager struct {
+	sync.RWMutex
+	InboundHandlerManager
+}
+
+func (m *syncInboundHandlerManager) GetHandler(ctx context.Context, tag string) (InboundHandler, error) {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.InboundHandlerManager == nil {
+		return nil, newError("InboundHandlerManager not set.").AtError()
+	}
+
+	return m.InboundHandlerManager.GetHandler(ctx, tag)
+}
+
+func (m *syncInboundHandlerManager) AddHandler(ctx context.Context, handler InboundHandler) error {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.InboundHandlerManager == nil {
+		return newError("InboundHandlerManager not set.").AtError()
+	}
+
+	return m.InboundHandlerManager.AddHandler(ctx, handler)
+}
+
+func (m *syncInboundHandlerManager) Start() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.InboundHandlerManager == nil {
+		return newError("InboundHandlerManager not set.").AtError()
+	}
+
+	return m.InboundHandlerManager.Start()
+}
+
+func (m *syncInboundHandlerManager) Close() {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.InboundHandlerManager != nil {
+		m.InboundHandlerManager.Close()
+	}
+}
+
+func (m *syncInboundHandlerManager) Set(manager InboundHandlerManager) {
+	m.Lock()
+	defer m.Unlock()
+
+	m.InboundHandlerManager = manager
+}
+
+// OutboundHandlerManager is a feature that manages OutboundHandlers.
+type OutboundHandlerManager interface {
+	Feature
+	// GetHandler returns an OutboundHandler will given tag.
+	GetHandler(tag string) OutboundHandler
+	// GetDefaultHandler returns the default OutboundHandler. It is usually the first OutboundHandler specified in the configuration.
+	GetDefaultHandler() OutboundHandler
+	// AddHandler adds a handler into this OutboundHandlerManager.
+	AddHandler(ctx context.Context, handler OutboundHandler) error
+}
+
+type syncOutboundHandlerManager struct {
+	sync.RWMutex
+	OutboundHandlerManager
+}
+
+func (m *syncOutboundHandlerManager) GetHandler(tag string) OutboundHandler {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.OutboundHandlerManager == nil {
+		return nil
+	}
+
+	return m.OutboundHandlerManager.GetHandler(tag)
+}
+
+func (m *syncOutboundHandlerManager) GetDefaultHandler() OutboundHandler {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.OutboundHandlerManager == nil {
+		return nil
+	}
+
+	return m.OutboundHandlerManager.GetDefaultHandler()
+}
+
+func (m *syncOutboundHandlerManager) AddHandler(ctx context.Context, handler OutboundHandler) error {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.OutboundHandlerManager == nil {
+		return newError("OutboundHandlerManager not set.").AtError()
+	}
+
+	return m.OutboundHandlerManager.AddHandler(ctx, handler)
+}
+
+func (m *syncOutboundHandlerManager) Start() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.OutboundHandlerManager == nil {
+		return newError("OutboundHandlerManager not set.").AtError()
+	}
+
+	return m.OutboundHandlerManager.Start()
+}
+
+func (m *syncOutboundHandlerManager) Close() {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.OutboundHandlerManager != nil {
+		m.OutboundHandlerManager.Close()
+	}
+}
+
+func (m *syncOutboundHandlerManager) Set(manager OutboundHandlerManager) {
+	m.Lock()
+	defer m.Unlock()
+
+	m.OutboundHandlerManager = manager
+}

+ 117 - 0
policy.go

@@ -0,0 +1,117 @@
+package core
+
+import (
+	"sync"
+	"time"
+)
+
+// TimeoutPolicy contains limits for connection timeout.
+type TimeoutPolicy struct {
+	// Timeout for handshake phase in a connection.
+	Handshake time.Duration
+	// Timeout for connection being idle, i.e., there is no egress or ingress traffic in this connection.
+	ConnectionIdle time.Duration
+	// Timeout for an uplink only connection, i.e., the downlink of the connection has ben closed.
+	UplinkOnly time.Duration
+	// Timeout for an downlink only connection, i.e., the uplink of the connection has ben closed.
+	DownlinkOnly time.Duration
+}
+
+// OverrideWith overrides the current TimeoutPolicy with another one. All timeouts with zero value will be overridden with the new value.
+func (p TimeoutPolicy) OverrideWith(another TimeoutPolicy) TimeoutPolicy {
+	if p.Handshake == 0 {
+		p.Handshake = another.Handshake
+	}
+	if p.ConnectionIdle == 0 {
+		p.ConnectionIdle = another.ConnectionIdle
+	}
+	if p.UplinkOnly == 0 {
+		p.UplinkOnly = another.UplinkOnly
+	}
+	if p.DownlinkOnly == 0 {
+		p.DownlinkOnly = another.DownlinkOnly
+	}
+	return p
+}
+
+// Policy is session based settings for controlling V2Ray requests. It contains various settings (or limits) that may differ for different users in the context.
+type Policy struct {
+	Timeouts TimeoutPolicy // Timeout settings
+}
+
+// OverrideWith overrides the current Policy with another one. All values with default value will be overridden.
+func (p Policy) OverrideWith(another Policy) Policy {
+	p.Timeouts.OverrideWith(another.Timeouts)
+	return p
+}
+
+// PolicyManager is a feature that provides Policy for the given user by its id or level.
+type PolicyManager interface {
+	Feature
+
+	// ForLevel returns the Policy for the given user level.
+	ForLevel(level uint32) Policy
+}
+
+// DefaultPolicy returns the Policy when user is not specified.
+func DefaultPolicy() Policy {
+	return Policy{
+		Timeouts: TimeoutPolicy{
+			Handshake:      time.Second * 4,
+			ConnectionIdle: time.Second * 300,
+			UplinkOnly:     time.Second * 5,
+			DownlinkOnly:   time.Second * 30,
+		},
+	}
+}
+
+type syncPolicyManager struct {
+	sync.RWMutex
+	PolicyManager
+}
+
+func (m *syncPolicyManager) ForLevel(level uint32) Policy {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.PolicyManager == nil {
+		p := DefaultPolicy()
+		if level == 1 {
+			p.Timeouts.ConnectionIdle = time.Second * 600
+		}
+		return p
+	}
+
+	return m.PolicyManager.ForLevel(level)
+}
+
+func (m *syncPolicyManager) Start() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.PolicyManager == nil {
+		return nil
+	}
+
+	return m.PolicyManager.Start()
+}
+
+func (m *syncPolicyManager) Close() {
+	m.RLock()
+	defer m.RUnlock()
+
+	if m.PolicyManager != nil {
+		m.PolicyManager.Close()
+	}
+}
+
+func (m *syncPolicyManager) Set(manager PolicyManager) {
+	if manager == nil {
+		return
+	}
+
+	m.Lock()
+	defer m.Unlock()
+
+	m.PolicyManager = manager
+}

+ 30 - 29
proxy/dokodemo/dokodemo.go

@@ -4,10 +4,9 @@ package dokodemo
 
 import (
 	"context"
+	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
@@ -18,36 +17,29 @@ import (
 )
 
 type DokodemoDoor struct {
-	config  *Config
-	address net.Address
-	port    net.Port
-	policy  policy.Policy
+	policyManager core.PolicyManager
+	config        *Config
+	address       net.Address
+	port          net.Port
+	v             *core.Instance
 }
 
 func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
-	}
 	if config.NetworkList == nil || config.NetworkList.Size() == 0 {
 		return nil, newError("no network specified")
 	}
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+
 	d := &DokodemoDoor{
-		config:  config,
-		address: config.GetPredefinedAddress(),
-		port:    net.Port(config.Port),
+		config:        config,
+		address:       config.GetPredefinedAddress(),
+		port:          net.Port(config.Port),
+		policyManager: v.PolicyManager(),
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		d.policy = pm.GetPolicy(config.UserLevel)
-		if config.Timeout > 0 && config.UserLevel == 0 {
-			d.policy.Timeout.ConnectionIdle.Value = config.Timeout
-		}
-		return nil
-	})
+
 	return d, nil
 }
 
@@ -55,7 +47,16 @@ func (d *DokodemoDoor) Network() net.NetworkList {
 	return *(d.config.NetworkList)
 }
 
-func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (d *DokodemoDoor) policy() core.Policy {
+	config := d.config
+	p := d.policyManager.ForLevel(config.UserLevel)
+	if config.Timeout > 0 && config.UserLevel == 0 {
+		p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
+	}
+	return p
+}
+
+func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error {
 	newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog()
 	dest := net.Destination{
 		Network: network,
@@ -72,7 +73,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
 	}
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, d.policy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, d.policy().Timeouts.ConnectionIdle)
 
 	inboundRay, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
@@ -88,7 +89,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
 			return newError("failed to transport request").Base(err)
 		}
 
-		timer.SetTimeout(d.policy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(d.policy().Timeouts.DownlinkOnly)
 
 		return nil
 	})
@@ -115,7 +116,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
 			return newError("failed to transport response").Base(err)
 		}
 
-		timer.SetTimeout(d.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(d.policy().Timeouts.UplinkOnly)
 
 		return nil
 	})

+ 28 - 30
proxy/freedom/freedom.go

@@ -4,9 +4,9 @@ package freedom
 
 import (
 	"context"
+	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
@@ -20,37 +20,35 @@ import (
 
 // Handler handles Freedom connections.
 type Handler struct {
-	domainStrategy Config_DomainStrategy
-	timeout        uint32
-	destOverride   *DestinationOverride
-	policy         policy.Policy
+	policyManager core.PolicyManager
+	dns           core.DNSClient
+	config        Config
 }
 
 // New creates a new Freedom handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not found in context.")
 	}
+
 	f := &Handler{
-		domainStrategy: config.DomainStrategy,
-		timeout:        config.Timeout,
-		destOverride:   config.DestinationOverride,
+		config:        *config,
+		policyManager: v.PolicyManager(),
+		dns:           v.DNSClient(),
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		f.policy = pm.GetPolicy(config.UserLevel)
-		if config.Timeout > 0 && config.UserLevel == 0 {
-			f.policy.Timeout.ConnectionIdle.Value = config.Timeout
-		}
-		return nil
-	})
+
 	return f, nil
 }
 
+func (h *Handler) policy() core.Policy {
+	p := h.policyManager.ForLevel(h.config.UserLevel)
+	if h.config.Timeout > 0 && h.config.UserLevel == 0 {
+		p.Timeouts.ConnectionIdle = time.Duration(h.config.Timeout) * time.Second
+	}
+	return p
+}
+
 func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address {
 	if resolver, ok := proxy.ResolvedIPsFromContext(ctx); ok {
 		ips := resolver.Resolve()
@@ -60,7 +58,7 @@ func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address {
 		return ips[dice.Roll(len(ips))]
 	}
 
-	ips, err := net.LookupIP(domain)
+	ips, err := h.dns.LookupIP(domain)
 	if err != nil {
 		newError("failed to get IP address for domain ", domain).Base(err).WriteToLog()
 	}
@@ -73,8 +71,8 @@ func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address {
 // Process implements proxy.Outbound.
 func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dialer proxy.Dialer) error {
 	destination, _ := proxy.TargetFromContext(ctx)
-	if h.destOverride != nil {
-		server := h.destOverride.Server
+	if h.config.DestinationOverride != nil {
+		server := h.config.DestinationOverride.Server
 		destination = net.Destination{
 			Network: destination.Network,
 			Address: server.Address.AsAddress(),
@@ -86,7 +84,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	input := outboundRay.OutboundInput()
 	output := outboundRay.OutboundOutput()
 
-	if h.domainStrategy == Config_USE_IP && destination.Address.Family().IsDomain() {
+	if h.config.DomainStrategy == Config_USE_IP && destination.Address.Family().IsDomain() {
 		ip := h.resolveIP(ctx, destination.Address.Domain())
 		if ip != nil {
 			destination = net.Destination{
@@ -113,7 +111,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	defer conn.Close()
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, h.policy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, h.policy().Timeouts.ConnectionIdle)
 
 	requestDone := signal.ExecuteAsync(func() error {
 		var writer buf.Writer
@@ -125,7 +123,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 		if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to process request").Base(err)
 		}
-		timer.SetTimeout(h.policy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(h.policy().Timeouts.DownlinkOnly)
 		return nil
 	})
 
@@ -136,7 +134,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 		if err := buf.Copy(v2reader, output, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to process response").Base(err)
 		}
-		timer.SetTimeout(h.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(h.policy().Timeouts.UplinkOnly)
 		return nil
 	})
 

+ 23 - 26
proxy/http/server.go

@@ -10,9 +10,7 @@ import (
 	"strings"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
@@ -26,32 +24,31 @@ import (
 // Server is a HTTP proxy server.
 type Server struct {
 	config *ServerConfig
-	policy policy.Policy
+	v      *core.Instance
 }
 
 // NewServer creates a new HTTP inbound handler.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context.")
-	}
 	s := &Server{
 		config: config,
+		v:      core.FromContext(ctx),
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		s.policy = pm.GetPolicy(config.UserLevel)
-		if config.Timeout > 0 && config.UserLevel == 0 {
-			s.policy.Timeout.ConnectionIdle.Value = config.Timeout
-		}
-		return nil
-	})
+	if s.v == nil {
+		return nil, newError("V is not in context.")
+	}
+
 	return s, nil
 }
 
+func (s *Server) policy() core.Policy {
+	config := s.config
+	p := s.v.PolicyManager().ForLevel(config.UserLevel)
+	if config.Timeout > 0 && config.UserLevel == 0 {
+		p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
+	}
+	return p
+}
+
 func (*Server) Network() net.NetworkList {
 	return net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
@@ -104,11 +101,11 @@ type readerOnly struct {
 	io.Reader
 }
 
-func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error {
 	reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
 
 Start:
-	conn.SetReadDeadline(time.Now().Add(s.policy.Timeout.Handshake.Duration()))
+	conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake))
 
 	request, err := http.ReadRequest(reader)
 	if err != nil {
@@ -165,14 +162,14 @@ Start:
 	return err
 }
 
-func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher dispatcher.Interface) error {
+func (s *Server) handleConnect(ctx context.Context, request *http.Request, reader *bufio.Reader, conn internet.Connection, dest net.Destination, dispatcher core.Dispatcher) error {
 	_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
 	if err != nil {
 		return newError("failed to write back OK response").Base(err)
 	}
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, s.policy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, s.policy().Timeouts.ConnectionIdle)
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 		return err
@@ -191,7 +188,7 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
 
 	requestDone := signal.ExecuteAsync(func() error {
 		defer ray.InboundInput().Close()
-		defer timer.SetTimeout(s.policy.Timeout.DownlinkOnly.Duration())
+		defer timer.SetTimeout(s.policy().Timeouts.DownlinkOnly)
 
 		v2reader := buf.NewReader(conn)
 		return buf.Copy(v2reader, ray.InboundInput(), buf.UpdateActivity(timer))
@@ -202,7 +199,7 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
 		if err := buf.Copy(ray.InboundOutput(), v2writer, buf.UpdateActivity(timer)); err != nil {
 			return err
 		}
-		timer.SetTimeout(s.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(s.policy().Timeouts.UplinkOnly)
 		return nil
 	})
 
@@ -217,7 +214,7 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
 
 var errWaitAnother = newError("keep alive")
 
-func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher dispatcher.Interface) error {
+func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher core.Dispatcher) error {
 	if !s.config.AllowTransparent && len(request.URL.Host) <= 0 {
 		// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
 		response := &http.Response{

+ 2 - 2
proxy/proxy.go

@@ -10,7 +10,7 @@ package proxy
 import (
 	"context"
 
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/ray"
@@ -22,7 +22,7 @@ type Inbound interface {
 	Network() net.NetworkList
 
 	// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
-	Process(context.Context, net.Network, internet.Connection, dispatcher.Interface) error
+	Process(context.Context, net.Network, internet.Connection, core.Dispatcher) error
 }
 
 // An Outbound process outbound connections.

+ 10 - 19
proxy/shadowsocks/client.go

@@ -3,8 +3,7 @@ package shadowsocks
 import (
 	"context"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
@@ -18,8 +17,8 @@ import (
 
 // Client is a inbound handler for Shadowsocks protocol
 type Client struct {
-	serverPicker  protocol.ServerPicker
-	policyManager policy.Manager
+	serverPicker protocol.ServerPicker
+	v            *core.Instance
 }
 
 // NewClient create a new Shadowsocks client.
@@ -33,19 +32,11 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
 	}
 	client := &Client{
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
+		v:            core.FromContext(ctx),
 	}
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("Space not found.")
+	if client.v == nil {
+		return nil, newError("V is not in context.")
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		client.policyManager = pm
-		return nil
-	})
 
 	return client, nil
 }
@@ -103,9 +94,9 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale
 		request.Option |= RequestOptionOneTimeAuth
 	}
 
-	sessionPolicy := v.policyManager.GetPolicy(user.Level)
+	sessionPolicy := v.v.PolicyManager().ForLevel(user.Level)
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
 
 	if request.Command == protocol.RequestCommandTCP {
 		bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
@@ -119,13 +110,13 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale
 		}
 
 		requestDone := signal.ExecuteAsync(func() error {
-			defer timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+			defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 			return buf.Copy(outboundRay.OutboundInput(), bodyWriter, buf.UpdateActivity(timer))
 		})
 
 		responseDone := signal.ExecuteAsync(func() error {
 			defer outboundRay.OutboundOutput().Close()
-			defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+			defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 			responseReader, err := ReadTCPResponse(user, conn)
 			if err != nil {

+ 17 - 27
proxy/shadowsocks/server.go

@@ -4,9 +4,7 @@ import (
 	"context"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/log"
@@ -19,18 +17,14 @@ import (
 )
 
 type Server struct {
-	config        *ServerConfig
-	user          *protocol.User
-	account       *MemoryAccount
-	policyManager policy.Manager
+	config  *ServerConfig
+	user    *protocol.User
+	account *MemoryAccount
+	v       *core.Instance
 }
 
 // NewServer create a new Shadowsocks server.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
-	}
 	if config.GetUser() == nil {
 		return nil, newError("user is not specified")
 	}
@@ -45,16 +39,12 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 		config:  config,
 		user:    config.GetUser(),
 		account: account,
+		v:       core.FromContext(ctx),
 	}
 
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		s.policyManager = pm
-		return nil
-	})
+	if s.v == nil {
+		return nil, newError("V is not in context.")
+	}
 
 	return s, nil
 }
@@ -69,7 +59,7 @@ func (s *Server) Network() net.NetworkList {
 	return list
 }
 
-func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error {
 	switch network {
 	case net.Network_TCP:
 		return s.handleConnection(ctx, conn, dispatcher)
@@ -80,7 +70,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn internet
 	}
 }
 
-func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error {
 	udpServer := udp.NewDispatcher(dispatcher)
 
 	reader := buf.NewReader(conn)
@@ -148,9 +138,9 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection
 	return nil
 }
 
-func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, dispatcher dispatcher.Interface) error {
-	sessionPolicy := s.policyManager.GetPolicy(s.user.Level)
-	conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeout.Handshake.Duration()))
+func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error {
+	sessionPolicy := s.v.PolicyManager().ForLevel(s.user.Level)
+	conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake))
 	bufferedReader := buf.NewBufferedReader(buf.NewReader(conn))
 	request, bodyReader, err := ReadTCPSession(s.user, bufferedReader)
 	if err != nil {
@@ -178,7 +168,7 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection,
 	ctx = protocol.ContextWithUser(ctx, request.User)
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 		return err
@@ -208,7 +198,7 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection,
 			return newError("failed to transport all TCP response").Base(err)
 		}
 
-		timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 		return nil
 	})
@@ -219,7 +209,7 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection,
 		if err := buf.Copy(bodyReader, ray.InboundInput(), buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP request").Base(err)
 		}
-		timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 		return nil
 	})
 

+ 23 - 27
proxy/socks/server.go

@@ -5,9 +5,7 @@ import (
 	"io"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/log"
@@ -22,32 +20,30 @@ import (
 // Server is a SOCKS 5 proxy server
 type Server struct {
 	config *ServerConfig
-	policy policy.Policy
+	v      *core.Instance
 }
 
 // NewServer creates a new Server object.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context").AtWarning()
-	}
 	s := &Server{
 		config: config,
+		v:      core.FromContext(ctx),
+	}
+	if s.v == nil {
+		return nil, newError("V is not in context.")
 	}
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy not found in space.")
-		}
-		s.policy = pm.GetPolicy(config.UserLevel)
-		if config.Timeout > 0 && config.UserLevel == 0 {
-			s.policy.Timeout.ConnectionIdle.Value = config.Timeout
-		}
-		return nil
-	})
 	return s, nil
 }
 
+func (s *Server) policy() core.Policy {
+	config := s.config
+	p := s.v.PolicyManager().ForLevel(config.UserLevel)
+	if config.Timeout > 0 && config.UserLevel == 0 {
+		p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
+	}
+	return p
+}
+
 func (s *Server) Network() net.NetworkList {
 	list := net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
@@ -58,7 +54,7 @@ func (s *Server) Network() net.NetworkList {
 	return list
 }
 
-func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error {
 	switch network {
 	case net.Network_TCP:
 		return s.processTCP(ctx, conn, dispatcher)
@@ -69,8 +65,8 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn internet
 	}
 }
 
-func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispatcher dispatcher.Interface) error {
-	conn.SetReadDeadline(time.Now().Add(s.policy.Timeout.Handshake.Duration()))
+func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error {
+	conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake))
 	reader := buf.NewBufferedReader(buf.NewReader(conn))
 
 	inboundDest, ok := proxy.InboundEntryPointFromContext(ctx)
@@ -125,9 +121,9 @@ func (*Server) handleUDP(c net.Conn) error {
 	return err
 }
 
-func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher dispatcher.Interface) error {
+func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher core.Dispatcher) error {
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, v.policy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, v.policy().Timeouts.ConnectionIdle)
 
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
@@ -144,7 +140,7 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
 		if err := buf.Copy(v2reader, input, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP request").Base(err)
 		}
-		timer.SetTimeout(v.policy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(v.policy().Timeouts.DownlinkOnly)
 		return nil
 	})
 
@@ -153,7 +149,7 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
 		if err := buf.Copy(output, v2writer, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP response").Base(err)
 		}
-		timer.SetTimeout(v.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(v.policy().Timeouts.UplinkOnly)
 		return nil
 	})
 
@@ -166,7 +162,7 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
 	return nil
 }
 
-func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher dispatcher.Interface) error {
+func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error {
 	udpServer := udp.NewDispatcher(dispatcher)
 
 	if source, ok := proxy.SourceFromContext(ctx); ok {

+ 21 - 34
proxy/vmess/inbound/inbound.go

@@ -8,10 +8,7 @@ import (
 	"sync"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
-	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
@@ -74,21 +71,16 @@ func (v *userByEmail) Get(email string) (*protocol.User, bool) {
 
 // Handler is an inbound connection handler that handles messages in VMess protocol.
 type Handler struct {
-	inboundHandlerManager proxyman.InboundHandlerManager
+	policyManager         core.PolicyManager
+	inboundHandlerManager core.InboundHandlerManager
 	clients               protocol.UserValidator
 	usersByEmail          *userByEmail
 	detours               *DetourConfig
 	sessionHistory        *encoding.SessionHistory
-	policyManager         policy.Manager
 }
 
 // New creates a new VMess inbound handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context")
-	}
-
 	allowedClients := vmess.NewTimedUserValidator(ctx, protocol.DefaultIDHash)
 	for _, user := range config.User {
 		if err := allowedClients.Add(user); err != nil {
@@ -96,24 +88,19 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 		}
 	}
 
-	handler := &Handler{
-		clients:        allowedClients,
-		detours:        config.Detour,
-		usersByEmail:   newUserByEmail(config.User, config.GetDefaultValue()),
-		sessionHistory: encoding.NewSessionHistory(ctx),
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
 	}
 
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		handler.inboundHandlerManager = proxyman.InboundHandlerManagerFromSpace(space)
-		if handler.inboundHandlerManager == nil {
-			return newError("InboundHandlerManager is not found is space.")
-		}
-		handler.policyManager = policy.FromSpace(space)
-		if handler.policyManager == nil {
-			return newError("Policy is not found in space.")
-		}
-		return nil
-	})
+	handler := &Handler{
+		policyManager:         v.PolicyManager(),
+		inboundHandlerManager: v.InboundHandlerManager(),
+		clients:               allowedClients,
+		detours:               config.Detour,
+		usersByEmail:          newUserByEmail(config.User, config.GetDefaultValue()),
+		sessionHistory:        encoding.NewSessionHistory(ctx),
+	}
 
 	return handler, nil
 }
@@ -179,9 +166,9 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess
 }
 
 // Process implements proxy.Inbound.Process().
-func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher dispatcher.Interface) error {
-	sessionPolicy := h.policyManager.GetPolicy(0)
-	if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeout.Handshake.Duration())); err != nil {
+func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher core.Dispatcher) error {
+	sessionPolicy := h.policyManager.ForLevel(0)
+	if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
 		return newError("unable to set read deadline").Base(err).AtWarning()
 	}
 
@@ -221,11 +208,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
 		newError("unable to set back read deadline").Base(err).WriteToLog()
 	}
 
-	sessionPolicy = h.policyManager.GetPolicy(request.User.Level)
+	sessionPolicy = h.policyManager.ForLevel(request.User.Level)
 	ctx = protocol.ContextWithUser(ctx, request.User)
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
 	ray, err := dispatcher.Dispatch(ctx, request.Destination())
 	if err != nil {
 		return newError("failed to dispatch request to ", request.Destination()).Base(err)
@@ -235,14 +222,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
 	output := ray.InboundOutput()
 
 	requestDone := signal.ExecuteAsync(func() error {
-		defer timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 		return transferRequest(timer, session, request, reader, input)
 	})
 
 	responseDone := signal.ExecuteAsync(func() error {
 		writer := buf.NewBufferedWriter(buf.NewWriter(connection))
 		defer writer.Flush()
-		defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 		response := &protocol.ResponseHeader{
 			Command: h.generateCommand(ctx, request),

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

@@ -6,8 +6,7 @@ import (
 	"context"
 	"time"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
@@ -23,17 +22,12 @@ import (
 
 // Handler is an outbound connection handler for VMess protocol.
 type Handler struct {
-	serverList    *protocol.ServerList
-	serverPicker  protocol.ServerPicker
-	policyManager policy.Manager
+	serverList   *protocol.ServerList
+	serverPicker protocol.ServerPicker
+	v            *core.Instance
 }
 
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	space := app.SpaceFromContext(ctx)
-	if space == nil {
-		return nil, newError("no space in context.")
-	}
-
 	serverList := protocol.NewServerList()
 	for _, rec := range config.Receiver {
 		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
@@ -41,16 +35,12 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 	handler := &Handler{
 		serverList:   serverList,
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
+		v:            core.FromContext(ctx),
 	}
 
-	space.On(app.SpaceInitializing, func(interface{}) error {
-		pm := policy.FromSpace(space)
-		if pm == nil {
-			return newError("Policy is not found in space.")
-		}
-		handler.policyManager = pm
-		return nil
-	})
+	if handler.v == nil {
+		return nil, newError("V is not in context.")
+	}
 
 	return handler, nil
 }
@@ -112,10 +102,10 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	output := outboundRay.OutboundOutput()
 
 	session := encoding.NewClientSession(protocol.DefaultIDHash)
-	sessionPolicy := v.policyManager.GetPolicy(request.User.Level)
+	sessionPolicy := v.v.PolicyManager().ForLevel(request.User.Level)
 
 	ctx, cancel := context.WithCancel(ctx)
-	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeout.ConnectionIdle.Duration())
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
 
 	requestDone := signal.ExecuteAsync(func() error {
 		writer := buf.NewBufferedWriter(buf.NewWriter(conn))
@@ -148,13 +138,13 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 				return err
 			}
 		}
-		timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 		return nil
 	})
 
 	responseDone := signal.ExecuteAsync(func() error {
 		defer output.Close()
-		defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 		reader := buf.NewBufferedReader(buf.NewReader(conn))
 		header, err := session.DecodeResponseHeader(reader)

+ 118 - 0
router.go

@@ -0,0 +1,118 @@
+package core
+
+import (
+	"context"
+	"sync"
+
+	"v2ray.com/core/common/errors"
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/transport/ray"
+)
+
+// Dispatcher is a feature that dispatches inbound requests to outbound handlers based on rules.
+// Dispatcher is required to be registered in a V2Ray instance to make V2Ray function properly.
+type Dispatcher interface {
+	Feature
+
+	// Dispatch returns a Ray for transporting data for the given request.
+	Dispatch(ctx context.Context, dest net.Destination) (ray.InboundRay, error)
+}
+
+type syncDispatcher struct {
+	sync.RWMutex
+	Dispatcher
+}
+
+func (d *syncDispatcher) Dispatch(ctx context.Context, dest net.Destination) (ray.InboundRay, error) {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.Dispatcher == nil {
+		return nil, newError("Dispatcher not set.").AtError()
+	}
+
+	return d.Dispatcher.Dispatch(ctx, dest)
+}
+
+func (d *syncDispatcher) Start() error {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.Dispatcher == nil {
+		return newError("Dispatcher not set.").AtError()
+	}
+
+	return d.Dispatcher.Start()
+}
+
+func (d *syncDispatcher) Close() {
+	d.RLock()
+	defer d.RUnlock()
+
+	if d.Dispatcher != nil {
+		d.Dispatcher.Close()
+	}
+}
+
+func (d *syncDispatcher) Set(disp Dispatcher) {
+	d.Lock()
+	defer d.Unlock()
+
+	d.Dispatcher = disp
+}
+
+var (
+	// ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route.
+	ErrNoClue = errors.New("not enough information for making a decision")
+)
+
+// Router is a feature to choose a outbound tag for the given request.
+type Router interface {
+	Feature
+
+	// PickRoute returns a tag of an OutboundHandler based on the given context.
+	PickRoute(ctx context.Context) (string, error)
+}
+
+type syncRouter struct {
+	sync.RWMutex
+	Router
+}
+
+func (r *syncRouter) PickRoute(ctx context.Context) (string, error) {
+	r.RLock()
+	defer r.RUnlock()
+
+	if r.Router == nil {
+		return "", ErrNoClue
+	}
+
+	return r.Router.PickRoute(ctx)
+}
+
+func (r *syncRouter) Start() error {
+	r.RLock()
+	defer r.RUnlock()
+
+	if r.Router == nil {
+		return nil
+	}
+
+	return r.Router.Start()
+}
+
+func (r *syncRouter) Close() {
+	r.RLock()
+	defer r.RUnlock()
+
+	if r.Router != nil {
+		r.Router.Close()
+	}
+}
+
+func (r *syncRouter) Set(router Router) {
+	r.Lock()
+	defer r.Unlock()
+
+	r.Router = router
+}

+ 11 - 0
testing/scenarios/common.go

@@ -13,10 +13,13 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	"v2ray.com/core"
+	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/retry"
+	"v2ray.com/core/common/serial"
 )
 
 func pickPort() net.Port {
@@ -70,6 +73,7 @@ func InitializeServerConfig(config *core.Config) (*exec.Cmd, error) {
 		return nil, err
 	}
 
+	config = withDefaultApps(config)
 	configBytes, err := proto.Marshal(config)
 	if err != nil {
 		return nil, err
@@ -128,3 +132,10 @@ func CloseAllServers(servers []*exec.Cmd) {
 		Content:  "All server closed.",
 	})
 }
+
+func withDefaultApps(config *core.Config) *core.Config {
+	config.App = append(config.App, serial.ToTypedMessage(&dispatcher.Config{}))
+	config.App = append(config.App, serial.ToTypedMessage(&proxyman.InboundConfig{}))
+	config.App = append(config.App, serial.ToTypedMessage(&proxyman.OutboundConfig{}))
+	return config
+}

+ 2 - 2
testing/scenarios/dns_test.go

@@ -51,7 +51,7 @@ func TestResolveIP(t *testing.T) {
 				},
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -67,7 +67,7 @@ func TestResolveIP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&blackhole.Config{}),
 			},

+ 8 - 8
testing/scenarios/dokodemo_test.go

@@ -40,7 +40,7 @@ func TestDokodemoTCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -57,7 +57,7 @@ func TestDokodemoTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -73,7 +73,7 @@ func TestDokodemoTCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange},
@@ -88,7 +88,7 @@ func TestDokodemoTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -147,7 +147,7 @@ func TestDokodemoUDP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -164,7 +164,7 @@ func TestDokodemoUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -174,7 +174,7 @@ func TestDokodemoUDP(t *testing.T) {
 	clientPort := uint32(pickPort())
 	clientPortRange := uint32(5)
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange},
@@ -189,7 +189,7 @@ func TestDokodemoUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{

+ 22 - 22
testing/scenarios/feature_test.go

@@ -44,7 +44,7 @@ func TestPassiveConnection(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -59,7 +59,7 @@ func TestPassiveConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -115,7 +115,7 @@ func TestProxy(t *testing.T) {
 	serverUserID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -132,7 +132,7 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -142,7 +142,7 @@ func TestProxy(t *testing.T) {
 	proxyUserID := protocol.NewID(uuid.New())
 	proxyPort := pickPort()
 	proxyConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(proxyPort),
@@ -159,7 +159,7 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -168,7 +168,7 @@ func TestProxy(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -183,7 +183,7 @@ func TestProxy(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -263,7 +263,7 @@ func TestProxyOverKCP(t *testing.T) {
 	serverUserID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -283,7 +283,7 @@ func TestProxyOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -293,7 +293,7 @@ func TestProxyOverKCP(t *testing.T) {
 	proxyUserID := protocol.NewID(uuid.New())
 	proxyPort := pickPort()
 	proxyConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(proxyPort),
@@ -310,7 +310,7 @@ func TestProxyOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
@@ -324,7 +324,7 @@ func TestProxyOverKCP(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -339,7 +339,7 @@ func TestProxyOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -429,7 +429,7 @@ func TestBlackhole(t *testing.T) {
 	serverPort := pickPort()
 	serverPort2 := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -457,7 +457,7 @@ func TestBlackhole(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				Tag:           "direct",
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
@@ -519,7 +519,7 @@ func TestForward(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -535,7 +535,7 @@ func TestForward(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{
 					DestinationOverride: &freedom.DestinationOverride{
@@ -585,7 +585,7 @@ func TestUDPConnection(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -600,7 +600,7 @@ func TestUDPConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -662,7 +662,7 @@ func TestDomainSniffing(t *testing.T) {
 	sniffingPort := pickPort()
 	httpPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				Tag: "snif",
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
@@ -689,7 +689,7 @@ func TestDomainSniffing(t *testing.T) {
 				ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				Tag: "redir",
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{

+ 8 - 8
testing/scenarios/http_test.go

@@ -37,7 +37,7 @@ func TestHttpConformance(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -46,7 +46,7 @@ func TestHttpConformance(t *testing.T) {
 				ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -92,7 +92,7 @@ func TestHttpConnectMethod(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -101,7 +101,7 @@ func TestHttpConnectMethod(t *testing.T) {
 				ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -171,7 +171,7 @@ func TestHttpPost(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -180,7 +180,7 @@ func TestHttpPost(t *testing.T) {
 				ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -237,7 +237,7 @@ func TestHttpBasicAuth(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -250,7 +250,7 @@ func TestHttpBasicAuth(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},

+ 26 - 26
testing/scenarios/shadowsocks_test.go

@@ -49,7 +49,7 @@ func TestShadowsocksAES256TCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -63,7 +63,7 @@ func TestShadowsocksAES256TCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -78,7 +78,7 @@ func TestShadowsocksAES256TCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -93,7 +93,7 @@ func TestShadowsocksAES256TCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -167,7 +167,7 @@ func TestShadowsocksAES128UDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -182,7 +182,7 @@ func TestShadowsocksAES128UDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -197,7 +197,7 @@ func TestShadowsocksAES128UDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -212,7 +212,7 @@ func TestShadowsocksAES128UDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -286,7 +286,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -300,7 +300,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -315,7 +315,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -330,7 +330,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -403,7 +403,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -417,7 +417,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -432,7 +432,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -447,7 +447,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -520,7 +520,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -535,7 +535,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -550,7 +550,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -565,7 +565,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -638,7 +638,7 @@ func TestShadowsocksAES256GCMConformance(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -652,7 +652,7 @@ func TestShadowsocksAES256GCMConformance(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -716,7 +716,7 @@ func TestShadowsocksChacha20Poly1305UDPConformance(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -731,7 +731,7 @@ func TestShadowsocksChacha20Poly1305UDPConformance(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -799,7 +799,7 @@ func TestShadowsocksChacha20Conformance(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -813,7 +813,7 @@ func TestShadowsocksChacha20Conformance(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},

+ 10 - 10
testing/scenarios/socks_test.go

@@ -30,7 +30,7 @@ func TestSocksBridgeTCP(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -46,7 +46,7 @@ func TestSocksBridgeTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -55,7 +55,7 @@ func TestSocksBridgeTCP(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -70,7 +70,7 @@ func TestSocksBridgeTCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -127,7 +127,7 @@ func TestSocksBridageUDP(t *testing.T) {
 
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -143,7 +143,7 @@ func TestSocksBridageUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -152,7 +152,7 @@ func TestSocksBridageUDP(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -167,7 +167,7 @@ func TestSocksBridageUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
 					Server: []*protocol.ServerEndpoint{
@@ -225,7 +225,7 @@ func TestSocksConformance(t *testing.T) {
 	authPort := pickPort()
 	noAuthPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(authPort),
@@ -255,7 +255,7 @@ func TestSocksConformance(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},

+ 12 - 12
testing/scenarios/tls_test.go

@@ -38,7 +38,7 @@ func TestSimpleTLSConnection(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -63,7 +63,7 @@ func TestSimpleTLSConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -72,7 +72,7 @@ func TestSimpleTLSConnection(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -87,7 +87,7 @@ func TestSimpleTLSConnection(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -152,7 +152,7 @@ func TestTLSOverKCP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := udp.PickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -178,7 +178,7 @@ func TestTLSOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -187,7 +187,7 @@ func TestTLSOverKCP(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -202,7 +202,7 @@ func TestTLSOverKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -268,7 +268,7 @@ func TestTLSOverWebSocket(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -294,7 +294,7 @@ func TestTLSOverWebSocket(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -303,7 +303,7 @@ func TestTLSOverWebSocket(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -318,7 +318,7 @@ func TestTLSOverWebSocket(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{

+ 4 - 4
testing/scenarios/transport_test.go

@@ -35,7 +35,7 @@ func TestHttpConnectionHeader(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -62,7 +62,7 @@ func TestHttpConnectionHeader(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -71,7 +71,7 @@ func TestHttpConnectionHeader(t *testing.T) {
 
 	clientPort := pickPort()
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -86,7 +86,7 @@ func TestHttpConnectionHeader(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{

+ 36 - 36
testing/scenarios/vmess_test.go

@@ -44,7 +44,7 @@ func TestVMessDynamicPort(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -84,7 +84,7 @@ func TestVMessDynamicPort(t *testing.T) {
 				Tag:           "detour",
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -99,7 +99,7 @@ func TestVMessDynamicPort(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -114,7 +114,7 @@ func TestVMessDynamicPort(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -179,7 +179,7 @@ func TestVMessGCM(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -197,7 +197,7 @@ func TestVMessGCM(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -212,7 +212,7 @@ func TestVMessGCM(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -227,7 +227,7 @@ func TestVMessGCM(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -302,7 +302,7 @@ func TestVMessGCMUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -320,7 +320,7 @@ func TestVMessGCMUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -335,7 +335,7 @@ func TestVMessGCMUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -350,7 +350,7 @@ func TestVMessGCMUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -435,7 +435,7 @@ func TestVMessChacha20(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -453,7 +453,7 @@ func TestVMessChacha20(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -468,7 +468,7 @@ func TestVMessChacha20(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -483,7 +483,7 @@ func TestVMessChacha20(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -558,7 +558,7 @@ func TestVMessNone(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -576,7 +576,7 @@ func TestVMessNone(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -591,7 +591,7 @@ func TestVMessNone(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -606,7 +606,7 @@ func TestVMessNone(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -683,7 +683,7 @@ func TestVMessKCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -704,7 +704,7 @@ func TestVMessKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -719,7 +719,7 @@ func TestVMessKCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -734,7 +734,7 @@ func TestVMessKCP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -816,7 +816,7 @@ func TestVMessIPv6(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -834,7 +834,7 @@ func TestVMessIPv6(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -849,7 +849,7 @@ func TestVMessIPv6(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -864,7 +864,7 @@ func TestVMessIPv6(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
@@ -931,7 +931,7 @@ func TestVMessGCMMux(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -949,7 +949,7 @@ func TestVMessGCMMux(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -964,7 +964,7 @@ func TestVMessGCMMux(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -979,7 +979,7 @@ func TestVMessGCMMux(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
 					MultiplexSettings: &proxyman.MultiplexingConfig{
@@ -1073,7 +1073,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
@@ -1091,7 +1091,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
@@ -1107,7 +1107,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 			}),
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(clientPort),
@@ -1135,7 +1135,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
 					MultiplexSettings: &proxyman.MultiplexingConfig{

+ 3 - 2
transport/internet/config.pb.go

@@ -46,7 +46,7 @@ func (TransportProtocol) EnumDescriptor() ([]byte, []int) { return fileDescripto
 type TransportConfig struct {
 	// Type of network that this settings supports.
 	Protocol TransportProtocol `protobuf:"varint,1,opt,name=protocol,enum=v2ray.core.transport.internet.TransportProtocol" json:"protocol,omitempty"`
-	// Specific settings.
+	// Specific settings. Must be of the transports.
 	Settings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=settings" json:"settings,omitempty"`
 }
 
@@ -74,7 +74,8 @@ type StreamConfig struct {
 	Protocol          TransportProtocol  `protobuf:"varint,1,opt,name=protocol,enum=v2ray.core.transport.internet.TransportProtocol" json:"protocol,omitempty"`
 	TransportSettings []*TransportConfig `protobuf:"bytes,2,rep,name=transport_settings,json=transportSettings" json:"transport_settings,omitempty"`
 	// Type of security. Must be a message name of the settings proto.
-	SecurityType     string                                   `protobuf:"bytes,3,opt,name=security_type,json=securityType" json:"security_type,omitempty"`
+	SecurityType string `protobuf:"bytes,3,opt,name=security_type,json=securityType" json:"security_type,omitempty"`
+	// Settings for transport security. For now the only choice is TLS.
 	SecuritySettings []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings" json:"security_settings,omitempty"`
 }
 

+ 3 - 3
transport/internet/udp/dispatcher.go

@@ -5,7 +5,7 @@ import (
 	"sync"
 	"time"
 
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/signal"
@@ -23,10 +23,10 @@ type connEntry struct {
 type Dispatcher struct {
 	sync.RWMutex
 	conns      map[net.Destination]*connEntry
-	dispatcher dispatcher.Interface
+	dispatcher core.Dispatcher
 }
 
-func NewDispatcher(dispatcher dispatcher.Interface) *Dispatcher {
+func NewDispatcher(dispatcher core.Dispatcher) *Dispatcher {
 	return &Dispatcher{
 		conns:      make(map[net.Destination]*connEntry),
 		dispatcher: dispatcher,

+ 101 - 80
v2ray.go

@@ -3,139 +3,160 @@ package core
 import (
 	"context"
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
-	"v2ray.com/core/app/policy"
-	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 )
 
 // Server is an instance of V2Ray. At any time, there must be at most one Server instance running.
+// Deprecated. Use Instance directly.
 type Server interface {
-	// Start starts the V2Ray server, and return any error during the process.
-	// In the case of any errors, the state of the server is unpredicatable.
-	Start() error
-
-	// Close closes the V2Ray server. All inbound and outbound connections will be closed immediately.
-	Close()
+	common.Runnable
 }
 
-// New creates a new V2Ray server with given config.
-func New(config *Config) (Server, error) {
-	return newSimpleServer(config)
+// Feature is the interface for V2Ray features. All features must implement this interface.
+// All existing features have an implementation in app directory. These features can be replaced by third-party ones.
+type Feature interface {
+	common.Runnable
 }
 
-// simpleServer shell of V2Ray.
-type simpleServer struct {
-	space app.Space
+// Instance combines all functionalities in V2Ray.
+type Instance struct {
+	dnsClient     syncDNSClient
+	policyManager syncPolicyManager
+	dispatcher    syncDispatcher
+	router        syncRouter
+	ihm           syncInboundHandlerManager
+	ohm           syncOutboundHandlerManager
+
+	features []Feature
 }
 
-// newSimpleServer returns a new Point server based on given configuration.
-// The server is not started at this point.
-func newSimpleServer(config *Config) (*simpleServer, error) {
-	var server = new(simpleServer)
+// New returns a new V2Ray instance based on given configuration.
+// The instance is not started at this point.
+// To make sure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.
+func New(config *Config) (*Instance, error) {
+	var server = new(Instance)
 
 	if err := config.Transport.Apply(); err != nil {
 		return nil, err
 	}
 
-	space := app.NewSpace()
-	ctx := app.ContextWithSpace(context.Background(), space)
-
-	server.space = space
+	ctx := context.WithValue(context.Background(), v2rayKey, server)
 
 	for _, appSettings := range config.App {
 		settings, err := appSettings.GetInstance()
 		if err != nil {
 			return nil, err
 		}
-		application, err := app.CreateAppFromConfig(ctx, settings)
+		app, err := common.CreateObject(ctx, settings)
 		if err != nil {
 			return nil, err
 		}
-		if err := space.AddApplication(application); err != nil {
-			return nil, err
+		f, ok := app.(Feature)
+		if !ok {
+			return nil, newError("not a feature")
 		}
+		server.features = append(server.features, f)
 	}
 
-	outboundHandlerManager := proxyman.OutboundHandlerManagerFromSpace(space)
-	if outboundHandlerManager == nil {
-		o, err := app.CreateAppFromConfig(ctx, new(proxyman.OutboundConfig))
+	for _, inbound := range config.Inbound {
+		rawHandler, err := common.CreateObject(ctx, inbound)
 		if err != nil {
 			return nil, err
 		}
-		if err := space.AddApplication(o); err != nil {
-			return nil, newError("failed to add default outbound handler manager").Base(err)
+		handler, ok := rawHandler.(InboundHandler)
+		if !ok {
+			return nil, newError("not an InboundHandler")
 		}
-		outboundHandlerManager = o.(proxyman.OutboundHandlerManager)
-	}
-
-	inboundHandlerManager := proxyman.InboundHandlerManagerFromSpace(space)
-	if inboundHandlerManager == nil {
-		o, err := app.CreateAppFromConfig(ctx, new(proxyman.InboundConfig))
-		if err != nil {
+		if err := server.InboundHandlerManager().AddHandler(ctx, handler); err != nil {
 			return nil, err
 		}
-		if err := space.AddApplication(o); err != nil {
-			return nil, newError("failed to add default inbound handler manager").Base(err)
-		}
-		inboundHandlerManager = o.(proxyman.InboundHandlerManager)
 	}
 
-	if disp := dispatcher.FromSpace(space); disp == nil {
-		d, err := app.CreateAppFromConfig(ctx, new(dispatcher.Config))
+	for _, outbound := range config.Outbound {
+		rawHandler, err := common.CreateObject(ctx, outbound)
 		if err != nil {
 			return nil, err
 		}
-		common.Must(space.AddApplication(d))
-	}
-
-	if p := policy.FromSpace(space); p == nil {
-		p, err := app.CreateAppFromConfig(ctx, &policy.Config{
-			Level: map[uint32]*policy.Policy{
-				1: {
-					Timeout: &policy.Policy_Timeout{
-						ConnectionIdle: &policy.Second{
-							Value: 600,
-						},
-					},
-				},
-			},
-		})
-		if err != nil {
+		handler, ok := rawHandler.(OutboundHandler)
+		if !ok {
+			return nil, newError("not an OutboundHandler")
+		}
+		if err := server.OutboundHandlerManager().AddHandler(ctx, handler); err != nil {
 			return nil, err
 		}
-		common.Must(space.AddApplication(p))
 	}
 
-	for _, inbound := range config.Inbound {
-		if err := inboundHandlerManager.AddHandler(ctx, inbound); err != nil {
-			return nil, err
-		}
+	return server, nil
+}
+
+// Close shutdown the V2Ray instance.
+func (s *Instance) Close() {
+	for _, f := range s.features {
+		f.Close()
 	}
+}
 
-	for _, outbound := range config.Outbound {
-		if err := outboundHandlerManager.AddHandler(ctx, outbound); err != nil {
-			return nil, err
+// Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.
+func (s *Instance) Start() error {
+	for _, f := range s.features {
+		if err := f.Start(); err != nil {
+			return nil
 		}
 	}
 
-	if err := server.space.Initialize(); err != nil {
-		return nil, err
+	newError("V2Ray started").AtWarning().WriteToLog()
+
+	return nil
+}
+
+// RegisterFeature registers the given feature into V2Ray.
+// If feature is one of the following types, the corressponding feature in this Instance
+// will be replaced: DNSClient, PolicyManager, Router, Dispatcher, InboundHandlerManager, OutboundHandlerManager.
+func (s *Instance) RegisterFeature(feature interface{}, instance Feature) error {
+	switch feature.(type) {
+	case DNSClient, *DNSClient:
+		s.dnsClient.Set(instance.(DNSClient))
+	case PolicyManager, *PolicyManager:
+		s.policyManager.Set(instance.(PolicyManager))
+	case Router, *Router:
+		s.router.Set(instance.(Router))
+	case Dispatcher, *Dispatcher:
+		s.dispatcher.Set(instance.(Dispatcher))
+	case InboundHandlerManager, *InboundHandlerManager:
+		s.ihm.Set(instance.(InboundHandlerManager))
+	case OutboundHandlerManager, *OutboundHandlerManager:
+		s.ohm.Set(instance.(OutboundHandlerManager))
 	}
+	s.features = append(s.features, instance)
+	return nil
+}
 
-	return server, nil
+// DNSClient returns the DNSClient used by this Instance. The returned DNSClient is always functional.
+func (s *Instance) DNSClient() DNSClient {
+	return &(s.dnsClient)
 }
 
-func (s *simpleServer) Close() {
-	s.space.Close()
+// PolicyManager returns the PolicyManager used by this Instance. The returned PolicyManager is always functional.
+func (s *Instance) PolicyManager() PolicyManager {
+	return &(s.policyManager)
 }
 
-func (s *simpleServer) Start() error {
-	if err := s.space.Start(); err != nil {
-		return err
-	}
-	newError("V2Ray started").AtWarning().WriteToLog()
+// Router returns the Router used by this Instance. The returned Router is always functional.
+func (s *Instance) Router() Router {
+	return &(s.router)
+}
 
-	return nil
+// Dispatcher returns the Dispatcher used by this Instance. If Dispatcher was not registered before, the returned value doesn't work, although it is not nil.
+func (s *Instance) Dispatcher() Dispatcher {
+	return &(s.dispatcher)
+}
+
+// InboundHandlerManager returns the InboundHandlerManager used by this Instance. If InboundHandlerManager was not registered before, the returned value doesn't work.
+func (s *Instance) InboundHandlerManager() InboundHandlerManager {
+	return &(s.ihm)
+}
+
+// OutboundHandlerManager returns the OutboundHandlerManager used by this Instance. If OutboundHandlerManager was not registered before, the returned value doesn't work.
+func (s *Instance) OutboundHandlerManager() OutboundHandlerManager {
+	return &(s.ohm)
 }

+ 2 - 2
v2ray_test.go

@@ -22,7 +22,7 @@ func TestV2RayClose(t *testing.T) {
 
 	port := net.Port(dice.RollUint16())
 	config := &Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(port),
@@ -37,7 +37,7 @@ func TestV2RayClose(t *testing.T) {
 				}),
 			},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{