Browse Source

Merge pull request #1 from v2ray/master

Pull
Ariselia 7 years ago
parent
commit
693c9bda49
100 changed files with 3563 additions and 1602 deletions
  1. 1 1
      .travis.yml
  2. 0 4
      app/api/api.go
  3. 0 9
      app/api/config.go
  4. 0 2
      app/app.go
  5. 95 0
      app/commander/commander.go
  6. 68 0
      app/commander/config.pb.go
  7. 17 0
      app/commander/config.proto
  8. 2 2
      app/commander/errors.generated.go
  9. 101 0
      app/commander/outbound.go
  10. 9 0
      app/commander/service.go
  11. 22 33
      app/dispatcher/default.go
  12. 1 19
      app/dispatcher/dispatcher.go
  13. 7 0
      app/dispatcher/errors.generated.go
  14. 1 1
      app/dispatcher/sniffer.go
  15. 2 3
      app/dispatcher/sniffer_test.go
  16. 3 4
      app/dns/nameserver.go
  17. 44 48
      app/dns/server.go
  18. 27 31
      app/dns/server_test.go
  19. 3 6
      app/log/log.go
  20. 16 0
      app/policy/config.go
  21. 2 2
      app/policy/errors.generated.go
  22. 62 0
      app/policy/manager.go
  23. 0 68
      app/policy/manager/manager.go
  24. 1 18
      app/policy/policy.go
  25. 148 0
      app/proxyman/command/command.go
  26. 520 0
      app/proxyman/command/command.pb.go
  27. 80 0
      app/proxyman/command/command.proto
  28. 3 0
      app/proxyman/command/doc.go
  29. 7 0
      app/proxyman/command/errors.generated.go
  30. 0 17
      app/proxyman/config.go
  31. 48 139
      app/proxyman/config.pb.go
  32. 0 23
      app/proxyman/config.proto
  33. 20 3
      app/proxyman/inbound/always.go
  34. 28 31
      app/proxyman/inbound/dynamic.go
  35. 100 43
      app/proxyman/inbound/inbound.go
  36. 44 63
      app/proxyman/inbound/worker.go
  37. 34 40
      app/proxyman/mux/mux.go
  38. 5 3
      app/proxyman/mux/session.go
  39. 2 1
      app/proxyman/mux/writer.go
  40. 36 109
      app/proxyman/outbound/handler.go
  41. 16 0
      app/proxyman/outbound/handler_test.go
  42. 94 28
      app/proxyman/outbound/outbound.go
  43. 0 44
      app/proxyman/proxyman.go
  44. 28 35
      app/router/router.go
  45. 21 18
      app/router/router_test.go
  46. 0 132
      app/space.go
  47. 59 0
      clock.go
  48. 44 0
      commander.go
  49. 0 46
      common/event/event.go
  50. 23 0
      common/interfaces.go
  51. 25 6
      common/log/log.go
  52. 0 44
      common/net/dns.go
  53. 2 0
      common/net/system.go
  54. 7 4
      common/protocol/headers.go
  55. 4 4
      common/protocol/id.go
  56. 1 1
      common/protocol/user.pb.go
  57. 1 0
      common/protocol/user_validator.go
  58. 12 0
      common/router/dispatcher.go
  59. 53 0
      common/router/router.go
  60. 48 0
      common/signal/done.go
  61. 4 4
      common/signal/notifier.go
  62. 5 5
      common/signal/semaphore.go
  63. 60 0
      common/signal/task.go
  64. 29 0
      common/signal/task_test.go
  65. 13 5
      common/signal/timer.go
  66. 12 0
      common/signal/timer_test.go
  67. 14 12
      common/uuid/uuid.go
  68. 4 2
      common/uuid/uuid_test.go
  69. 124 28
      config.pb.go
  70. 24 3
      config.proto
  71. 17 0
      context.go
  72. 1 1
      core.go
  73. 20 0
      dial.go
  74. 59 0
      dns.go
  75. 4 2
      main/distro/all/all.go
  76. 168 0
      network.go
  77. 117 0
      policy.go
  78. 30 29
      proxy/dokodemo/dokodemo.go
  79. 28 30
      proxy/freedom/freedom.go
  80. 0 33
      proxy/handler_cache.go
  81. 23 26
      proxy/http/server.go
  82. 20 2
      proxy/proxy.go
  83. 11 20
      proxy/shadowsocks/client.go
  84. 9 0
      proxy/shadowsocks/config.go
  85. 17 27
      proxy/shadowsocks/server.go
  86. 23 27
      proxy/socks/server.go
  87. 1 2
      proxy/vmess/account.go
  88. 2 2
      proxy/vmess/encoding/client.go
  89. 92 6
      proxy/vmess/encoding/encoding_test.go
  90. 88 68
      proxy/vmess/encoding/server.go
  91. 101 70
      proxy/vmess/inbound/inbound.go
  92. 15 25
      proxy/vmess/outbound/outbound.go
  93. 104 75
      proxy/vmess/vmess.go
  94. 58 0
      proxy/vmess/vmess_test.go
  95. 2 2
      release/release-ci.sh
  96. 115 0
      router.go
  97. 130 0
      testing/scenarios/command_test.go
  98. 12 1
      testing/scenarios/common.go
  99. 2 2
      testing/scenarios/dns_test.go
  100. 8 8
      testing/scenarios/dokodemo_test.go

+ 1 - 1
.travis.yml

@@ -1,7 +1,7 @@
 sudo: required
 sudo: required
 language: go
 language: go
 go:
 go:
-- 1.9.2
+- 1.9.4
 go_import_path: v2ray.com/core
 go_import_path: v2ray.com/core
 git:
 git:
   depth: 5
   depth: 5

+ 0 - 4
app/api/api.go

@@ -1,4 +0,0 @@
-package api
-
-type ApiServer struct {
-}

+ 0 - 9
app/api/config.go

@@ -1,9 +0,0 @@
-package api
-
-import (
-	"v2ray.com/core/common/net"
-)
-
-type Config struct {
-	DirectPort net.Port
-}

+ 0 - 2
app/app.go

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

+ 95 - 0
app/commander/commander.go

@@ -0,0 +1,95 @@
+package commander
+
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg commander -path App,Commander
+
+import (
+	"context"
+	"net"
+	"sync"
+
+	"google.golang.org/grpc"
+	"v2ray.com/core"
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/signal"
+)
+
+type Commander struct {
+	sync.Mutex
+	server *grpc.Server
+	config Config
+	v      *core.Instance
+	ohm    core.OutboundHandlerManager
+}
+
+func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+	c := &Commander{
+		config: *config,
+		ohm:    v.OutboundHandlerManager(),
+		v:      v,
+	}
+	if err := v.RegisterFeature((*core.Commander)(nil), c); err != nil {
+		return nil, err
+	}
+	return c, nil
+}
+
+func (c *Commander) Start() error {
+	c.Lock()
+	c.server = grpc.NewServer()
+	for _, rawConfig := range c.config.Service {
+		config, err := rawConfig.GetInstance()
+		if err != nil {
+			return err
+		}
+		rawService, err := c.v.CreateObject(config)
+		if err != nil {
+			return err
+		}
+		service, ok := rawService.(Service)
+		if !ok {
+			return newError("not a Service.")
+		}
+		service.Register(c.server)
+	}
+	c.Unlock()
+
+	listener := &OutboundListener{
+		buffer: make(chan net.Conn, 4),
+		done:   signal.NewDone(),
+	}
+
+	go func() {
+		if err := c.server.Serve(listener); err != nil {
+			newError("failed to start grpc server").Base(err).AtError().WriteToLog()
+		}
+	}()
+
+	c.ohm.RemoveHandler(context.Background(), c.config.Tag)
+	c.ohm.AddHandler(context.Background(), &CommanderOutbound{
+		tag:      c.config.Tag,
+		listener: listener,
+	})
+	return nil
+}
+
+func (c *Commander) Close() error {
+	c.Lock()
+	defer c.Unlock()
+
+	if c.server != nil {
+		c.server.Stop()
+		c.server = nil
+	}
+
+	return nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
+		return NewCommander(ctx, cfg.(*Config))
+	}))
+}

+ 68 - 0
app/commander/config.pb.go

@@ -0,0 +1,68 @@
+package commander
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import v2ray_core_common_serial "v2ray.com/core/common/serial"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+// Config is the settings for Commander.
+type Config struct {
+	// Tag of the outbound handler that handles grpc connections.
+	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	// Services that supported by this server. All services must implement Service interface.
+	Service []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,rep,name=service" json:"service,omitempty"`
+}
+
+func (m *Config) Reset()                    { *m = Config{} }
+func (m *Config) String() string            { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()               {}
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *Config) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+func (m *Config) GetService() []*v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.Service
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Config)(nil), "v2ray.core.app.commander.Config")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/app/commander/config.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 212 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2c, 0x33, 0x2a, 0x4a,
+	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x4f,
+	0xce, 0xcf, 0xcd, 0x4d, 0xcc, 0x4b, 0x49, 0x2d, 0xd2, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7,
+	0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x80, 0x29, 0x2d, 0x4a, 0xd5, 0x4b, 0x2c, 0x28, 0xd0,
+	0x83, 0x2b, 0x93, 0x32, 0x40, 0x33, 0x04, 0x24, 0x93, 0x9f, 0xa7, 0x5f, 0x9c, 0x5a, 0x94, 0x99,
+	0x98, 0xa3, 0x5f, 0x52, 0x59, 0x90, 0x9a, 0x12, 0x9f, 0x9b, 0x5a, 0x5c, 0x9c, 0x98, 0x9e, 0x0a,
+	0x31, 0x4b, 0x29, 0x86, 0x8b, 0xcd, 0x19, 0x6c, 0xb6, 0x90, 0x00, 0x17, 0x73, 0x49, 0x62, 0xba,
+	0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x88, 0x29, 0xe4, 0xc0, 0xc5, 0x5e, 0x9c, 0x5a, 0x54,
+	0x96, 0x99, 0x9c, 0x2a, 0xc1, 0xa4, 0xc0, 0xac, 0xc1, 0x6d, 0xa4, 0xa6, 0x87, 0x64, 0x33, 0xc4,
+	0x6c, 0x3d, 0x88, 0xd9, 0x7a, 0x21, 0x20, 0xb3, 0x7d, 0x21, 0x46, 0x07, 0xc1, 0xb4, 0x39, 0xb9,
+	0x71, 0xc9, 0x24, 0xe7, 0xe7, 0xea, 0xe1, 0x72, 0x6f, 0x00, 0x63, 0x14, 0x27, 0x9c, 0xb3, 0x8a,
+	0x49, 0x22, 0xcc, 0x28, 0x28, 0xb1, 0x52, 0xcf, 0x19, 0xa4, 0xce, 0xb1, 0xa0, 0x40, 0xcf, 0x19,
+	0x26, 0x95, 0xc4, 0x06, 0x76, 0xac, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x29, 0x02, 0xa6, 0x19,
+	0x25, 0x01, 0x00, 0x00,
+}

+ 17 - 0
app/commander/config.proto

@@ -0,0 +1,17 @@
+syntax = "proto3";
+
+package v2ray.core.app.commander;
+option csharp_namespace = "V2Ray.Core.App.Commander";
+option go_package = "commander";
+option java_package = "com.v2ray.core.app.commander";
+option java_multiple_files = true;
+
+import "v2ray.com/core/common/serial/typed_message.proto";
+
+// Config is the settings for Commander.
+message Config {
+  // Tag of the outbound handler that handles grpc connections.
+  string tag = 1;
+  // Services that supported by this server. All services must implement Service interface.
+  repeated v2ray.core.common.serial.TypedMessage service = 2;
+}

+ 2 - 2
app/dispatcher/impl/errors.generated.go → app/commander/errors.generated.go

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

+ 101 - 0
app/commander/outbound.go

@@ -0,0 +1,101 @@
+package commander
+
+import (
+	"context"
+	"net"
+	"sync"
+
+	"v2ray.com/core/common/signal"
+	"v2ray.com/core/transport/ray"
+)
+
+type OutboundListener struct {
+	buffer chan net.Conn
+	done   *signal.Done
+}
+
+func (l *OutboundListener) add(conn net.Conn) {
+	select {
+	case l.buffer <- conn:
+	case <-l.done.C():
+		conn.Close()
+	default:
+		conn.Close()
+	}
+}
+
+func (l *OutboundListener) Accept() (net.Conn, error) {
+	select {
+	case <-l.done.C():
+		return nil, newError("listern closed")
+	case c := <-l.buffer:
+		return c, nil
+	}
+}
+
+func (l *OutboundListener) Close() error {
+	l.done.Close()
+L:
+	for {
+		select {
+		case c := <-l.buffer:
+			c.Close()
+		default:
+			break L
+		}
+	}
+	return nil
+}
+
+func (l *OutboundListener) Addr() net.Addr {
+	return &net.TCPAddr{
+		IP:   net.IP{0, 0, 0, 0},
+		Port: 0,
+	}
+}
+
+type CommanderOutbound struct {
+	tag      string
+	listener *OutboundListener
+	access   sync.RWMutex
+	closed   bool
+}
+
+func (co *CommanderOutbound) Dispatch(ctx context.Context, r ray.OutboundRay) {
+	co.access.RLock()
+
+	if co.closed {
+		r.OutboundInput().CloseError()
+		r.OutboundOutput().CloseError()
+		co.access.RUnlock()
+		return
+	}
+
+	closeSignal := signal.NewNotifier()
+	c := ray.NewConnection(r.OutboundInput(), r.OutboundOutput(), ray.ConnCloseSignal(closeSignal))
+	co.listener.add(c)
+	co.access.RUnlock()
+	<-closeSignal.Wait()
+
+	return
+}
+
+func (co *CommanderOutbound) Tag() string {
+	return co.tag
+}
+
+func (co *CommanderOutbound) Start() error {
+	co.access.Lock()
+	co.closed = false
+	co.access.Unlock()
+	return nil
+}
+
+func (co *CommanderOutbound) Close() error {
+	co.access.Lock()
+	co.closed = true
+	co.listener.Close()
+	co.access.Unlock()
+
+	return nil
+}

+ 9 - 0
app/commander/service.go

@@ -0,0 +1,9 @@
+package commander
+
+import (
+	"google.golang.org/grpc"
+)
+
+type Service interface {
+	Register(*grpc.Server)
+}

+ 22 - 33
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
 //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"
 	"context"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
-	"v2ray.com/core/app/router"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
@@ -21,31 +19,27 @@ var (
 	errSniffingTimeout = newError("timeout on sniffing")
 	errSniffingTimeout = newError("timeout on sniffing")
 )
 )
 
 
-var (
-	_ app.Application = (*DefaultDispatcher)(nil)
-)
-
 // DefaultDispatcher is a default implementation of Dispatcher.
 // DefaultDispatcher is a default implementation of Dispatcher.
 type DefaultDispatcher struct {
 type DefaultDispatcher struct {
-	ohm    proxyman.OutboundHandlerManager
-	router *router.Router
+	ohm    core.OutboundHandlerManager
+	router core.Router
 }
 }
 
 
 // NewDefaultDispatcher create a new DefaultDispatcher.
 // 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
 	return d, nil
 }
 }
 
 
@@ -55,14 +49,9 @@ func (*DefaultDispatcher) Start() error {
 }
 }
 
 
 // Close implements app.Application.
 // Close implements app.Application.
-func (*DefaultDispatcher) Close() {}
-
-// Interface implements app.Application.
-func (*DefaultDispatcher) Interface() interface{} {
-	return (*dispatcher.Interface)(nil)
-}
+func (*DefaultDispatcher) Close() error { return nil }
 
 
-// Dispatch implements Dispatcher.Interface.
+// Dispatch implements core.Dispatcher.
 func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (ray.InboundRay, error) {
 func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (ray.InboundRay, error) {
 	if !destination.IsValid() {
 	if !destination.IsValid() {
 		panic("Dispatcher: Invalid destination.")
 		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) {
 func (d *DefaultDispatcher) routedDispatch(ctx context.Context, outbound ray.OutboundRay, destination net.Destination) {
 	dispatcher := d.ohm.GetDefaultHandler()
 	dispatcher := d.ohm.GetDefaultHandler()
 	if d.router != nil {
 	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 {
 			if handler := d.ohm.GetHandler(tag); handler != nil {
 				newError("taking detour [", tag, "] for [", destination, "]").WriteToLog()
 				newError("taking detour [", tag, "] for [", destination, "]").WriteToLog()
 				dispatcher = handler
 				dispatcher = handler
@@ -135,7 +124,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, outbound ray.Out
 }
 }
 
 
 func init() {
 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
 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

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

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

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

@@ -1,4 +1,4 @@
-package impl
+package dispatcher
 
 
 import (
 import (
 	"bytes"
 	"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 (
 import (
 	"testing"
 	"testing"
 
 
+	. "v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
-
-	. "v2ray.com/core/app/dispatcher/impl"
 	. "v2ray.com/ext/assert"
 	. "v2ray.com/ext/assert"
 )
 )
 
 

+ 3 - 4
app/dns/nameserver.go

@@ -6,7 +6,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/miekg/dns"
 	"github.com/miekg/dns"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
@@ -48,7 +48,7 @@ type UDPNameServer struct {
 	nextCleanup time.Time
 	nextCleanup time.Time
 }
 }
 
 
-func NewUDPNameServer(address net.Destination, dispatcher dispatcher.Interface) *UDPNameServer {
+func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
 	s := &UDPNameServer{
 	s := &UDPNameServer{
 		address:   address,
 		address:   address,
 		requests:  make(map[uint16]*PendingRequest),
 		requests:  make(map[uint16]*PendingRequest),
@@ -216,8 +216,7 @@ func (*LocalNameServer) QueryA(domain string) <-chan *ARecord {
 	go func() {
 	go func() {
 		defer close(response)
 		defer close(response)
 
 
-		resolver := net.SystemIPResolver()
-		ips, err := resolver.LookupIP(domain)
+		ips, err := net.LookupIP(domain)
 		if err != nil {
 		if err != nil {
 			newError("failed to lookup IPs for domain ", domain).Base(err).AtWarning().WriteToLog()
 			newError("failed to lookup IPs for domain ", domain).Base(err).AtWarning().WriteToLog()
 			return
 			return

+ 44 - 48
app/dns/server.go

@@ -1,6 +1,6 @@
 package dns
 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 (
 import (
 	"context"
 	"context"
@@ -8,10 +8,10 @@ import (
 	"time"
 	"time"
 
 
 	dnsmsg "github.com/miekg/dns"
 	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"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/signal"
 )
 )
 
 
 const (
 const (
@@ -38,56 +38,60 @@ type Server struct {
 	hosts   map[string]net.IP
 	hosts   map[string]net.IP
 	records map[string]*DomainRecord
 	records map[string]*DomainRecord
 	servers []NameServer
 	servers []NameServer
+	task    *signal.PeriodicTask
 }
 }
 
 
 func New(ctx context.Context, config *Config) (*Server, error) {
 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{
 	server := &Server{
 		records: make(map[string]*DomainRecord),
 		records: make(map[string]*DomainRecord),
 		servers: make([]NameServer, len(config.NameServers)),
 		servers: make([]NameServer, len(config.NameServers)),
 		hosts:   config.GetInternalHosts(),
 		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)
-				}
+	server.task = &signal.PeriodicTask{
+		Interval: time.Minute * 10,
+		Execute: func() error {
+			server.cleanup()
+			return nil
+		},
+	}
+	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
-	})
-	return server, nil
-}
+	}
+	if len(config.NameServers) == 0 {
+		server.servers = append(server.servers, &LocalNameServer{})
+	}
 
 
-func (*Server) Interface() interface{} {
-	return (*Server)(nil)
+	return server, nil
 }
 }
 
 
+// Start implements common.Runnable.
 func (s *Server) Start() error {
 func (s *Server) Start() error {
-	net.RegisterIPResolver(s)
-	return nil
+	return s.task.Start()
 }
 }
 
 
-func (*Server) Close() {
-	net.RegisterIPResolver(net.SystemIPResolver())
+// Close implements common.Runnable.
+func (s *Server) Close() error {
+	return s.task.Close()
 }
 }
 
 
 func (s *Server) GetCached(domain string) []net.IP {
 func (s *Server) GetCached(domain string) []net.IP {
@@ -101,18 +105,12 @@ func (s *Server) GetCached(domain string) []net.IP {
 	return nil
 	return nil
 }
 }
 
 
-func (s *Server) tryCleanup() {
+func (s *Server) cleanup() {
 	s.Lock()
 	s.Lock()
 	defer s.Unlock()
 	defer s.Unlock()
 
 
-	if len(s.records) > 256 {
-		domains := make([]string, 0, 256)
-		for d, r := range s.records {
-			if r.Expired() {
-				domains = append(domains, d)
-			}
-		}
-		for _, d := range domains {
+	for d, r := range s.records {
+		if r.Expired() {
 			delete(s.records, d)
 			delete(s.records, d)
 		}
 		}
 	}
 	}
@@ -129,8 +127,6 @@ func (s *Server) LookupIP(domain string) ([]net.IP, error) {
 		return ips, nil
 		return ips, nil
 	}
 	}
 
 
-	s.tryCleanup()
-
 	for _, server := range s.servers {
 	for _, server := range s.servers {
 		response := server.QueryA(domain)
 		response := server.QueryA(domain)
 		select {
 		select {

+ 27 - 31
app/dns/server_test.go

@@ -1,18 +1,14 @@
 package dns_test
 package dns_test
 
 
 import (
 import (
-	"context"
 	"testing"
 	"testing"
 
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/dispatcher"
-	_ "v2ray.com/core/app/dispatcher/impl"
 	. "v2ray.com/core/app/dns"
 	. "v2ray.com/core/app/dns"
 	"v2ray.com/core/app/policy"
 	"v2ray.com/core/app/policy"
-	_ "v2ray.com/core/app/policy/manager"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	_ "v2ray.com/core/app/proxyman/outbound"
 	_ "v2ray.com/core/app/proxyman/outbound"
-	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/proxy/freedom"
 	"v2ray.com/core/proxy/freedom"
@@ -54,50 +50,50 @@ func TestUDPServer(t *testing.T) {
 
 
 	go dnsServer.ListenAndServe()
 	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{
+			{
+				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(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{8, 8, 8, 8})
 	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(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{9, 9, 9, 9})
 	assert([]byte(ips[0]), Equals, []byte{9, 9, 9, 9})
 
 
 	dnsServer.Shutdown()
 	dnsServer.Shutdown()
 
 
-	ips, err = net.LookupIP("google.com")
+	ips, err = client.LookupIP("google.com")
 	assert(err, IsNil)
 	assert(err, IsNil)
 	assert(len(ips), Equals, 1)
 	assert(len(ips), Equals, 1)
 	assert([]byte(ips[0]), Equals, []byte{8, 8, 8, 8})
 	assert([]byte(ips[0]), Equals, []byte{8, 8, 8, 8})

+ 3 - 6
app/log/log.go

@@ -37,11 +37,6 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
 	return g, nil
 	return g, nil
 }
 }
 
 
-// Interface implements app.Application.Interface().
-func (*Instance) Interface() interface{} {
-	return (*Instance)(nil)
-}
-
 func (g *Instance) initAccessLogger() error {
 func (g *Instance) initAccessLogger() error {
 	switch g.config.AccessLogType {
 	switch g.config.AccessLogType {
 	case LogType_File:
 	case LogType_File:
@@ -108,11 +103,13 @@ func (g *Instance) Handle(msg log.Message) {
 }
 }
 
 
 // Close implement app.Application.Close().
 // Close implement app.Application.Close().
-func (g *Instance) Close() {
+func (g *Instance) Close() error {
 	g.Lock()
 	g.Lock()
 	defer g.Unlock()
 	defer g.Unlock()
 
 
 	g.active = false
 	g.active = false
+
+	return nil
 }
 }
 
 
 func init() {
 func init() {

+ 16 - 0
app/policy/config.go

@@ -2,10 +2,15 @@ package policy
 
 
 import (
 import (
 	"time"
 	"time"
+
+	"v2ray.com/core"
 )
 )
 
 
 // Duration converts Second to time.Duration.
 // Duration converts Second to time.Duration.
 func (s *Second) Duration() time.Duration {
 func (s *Second) Duration() time.Duration {
+	if s == nil {
+		return 0
+	}
 	return time.Second * time.Duration(s.Value)
 	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"
 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") }

+ 62 - 0
app/policy/manager.go

@@ -0,0 +1,62 @@
+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() error {
+	return nil
+}
+
+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
 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

+ 148 - 0
app/proxyman/command/command.go

@@ -0,0 +1,148 @@
+package command
+
+import (
+	"context"
+
+	grpc "google.golang.org/grpc"
+	"v2ray.com/core"
+	"v2ray.com/core/common"
+	"v2ray.com/core/proxy"
+)
+
+// InboundOperation is the interface for operations that applies to inbound handlers.
+type InboundOperation interface {
+	// ApplyInbound appliess this operation to the given inbound handler.
+	ApplyInbound(context.Context, core.InboundHandler) error
+}
+
+// OutboundOperation is the interface for operations that applies to outbound handlers.
+type OutboundOperation interface {
+	// ApplyOutbound applies this operation to the given outbound handler.
+	ApplyOutbound(context.Context, core.OutboundHandler) error
+}
+
+func getInbound(handler core.InboundHandler) (proxy.Inbound, error) {
+	gi, ok := handler.(proxy.GetInbound)
+	if !ok {
+		return nil, newError("can't get inbound proxy from handler.")
+	}
+	return gi.GetInbound(), nil
+}
+
+// ApplyInbound implements InboundOperation.
+func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler core.InboundHandler) error {
+	p, err := getInbound(handler)
+	if err != nil {
+		return err
+	}
+	um, ok := p.(proxy.UserManager)
+	if !ok {
+		return newError("proxy is not an UserManager")
+	}
+	return um.AddUser(ctx, op.User)
+}
+
+// ApplyInbound implements InboundOperation.
+func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler core.InboundHandler) error {
+	p, err := getInbound(handler)
+	if err != nil {
+		return err
+	}
+	um, ok := p.(proxy.UserManager)
+	if !ok {
+		return newError("proxy is not an UserManager")
+	}
+	return um.RemoveUser(ctx, op.Email)
+}
+
+type handlerServer struct {
+	s   *core.Instance
+	ihm core.InboundHandlerManager
+	ohm core.OutboundHandlerManager
+}
+
+func (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) {
+	rawHandler, err := s.s.CreateObject(request.Inbound)
+	if err != nil {
+		return nil, err
+	}
+	handler, ok := rawHandler.(core.InboundHandler)
+	if !ok {
+		return nil, newError("not an InboundHandler.")
+	}
+	return &AddInboundResponse{}, s.ihm.AddHandler(ctx, handler)
+}
+
+func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) {
+	return &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag)
+}
+
+func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) {
+	rawOperation, err := request.Operation.GetInstance()
+	if err != nil {
+		return nil, newError("unknown operation").Base(err)
+	}
+	operation, ok := rawOperation.(InboundOperation)
+	if !ok {
+		return nil, newError("not an inbound operation")
+	}
+
+	handler, err := s.ihm.GetHandler(ctx, request.Tag)
+	if err != nil {
+		return nil, newError("failed to get handler: ", request.Tag).Base(err)
+	}
+
+	return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)
+}
+
+func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) {
+	rawHandler, err := s.s.CreateObject(request.Outbound)
+	if err != nil {
+		return nil, err
+	}
+	handler, ok := rawHandler.(core.OutboundHandler)
+	if !ok {
+		return nil, newError("not an OutboundHandler.")
+	}
+	return &AddOutboundResponse{}, s.ohm.AddHandler(ctx, handler)
+}
+
+func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {
+	return &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag)
+}
+
+func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) {
+	rawOperation, err := request.Operation.GetInstance()
+	if err != nil {
+		return nil, newError("unknown operation").Base(err)
+	}
+	operation, ok := rawOperation.(OutboundOperation)
+	if !ok {
+		return nil, newError("not an outbound operation")
+	}
+
+	handler := s.ohm.GetHandler(request.Tag)
+	return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)
+}
+
+type service struct {
+	v *core.Instance
+}
+
+func (s *service) Register(server *grpc.Server) {
+	RegisterHandlerServiceServer(server, &handlerServer{
+		s:   s.v,
+		ihm: s.v.InboundHandlerManager(),
+		ohm: s.v.OutboundHandlerManager(),
+	})
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
+		s := core.FromContext(ctx)
+		if s == nil {
+			return nil, newError("V is not in context.")
+		}
+		return &service{v: s}, nil
+	}))
+}

+ 520 - 0
app/proxyman/command/command.pb.go

@@ -0,0 +1,520 @@
+package command
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import v2ray_core_common_protocol "v2ray.com/core/common/protocol"
+import v2ray_core_common_serial "v2ray.com/core/common/serial"
+import v2ray_core "v2ray.com/core"
+
+import (
+	context "golang.org/x/net/context"
+	grpc "google.golang.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type AddUserOperation struct {
+	User *v2ray_core_common_protocol.User `protobuf:"bytes,1,opt,name=user" json:"user,omitempty"`
+}
+
+func (m *AddUserOperation) Reset()                    { *m = AddUserOperation{} }
+func (m *AddUserOperation) String() string            { return proto.CompactTextString(m) }
+func (*AddUserOperation) ProtoMessage()               {}
+func (*AddUserOperation) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *AddUserOperation) GetUser() *v2ray_core_common_protocol.User {
+	if m != nil {
+		return m.User
+	}
+	return nil
+}
+
+type RemoveUserOperation struct {
+	Email string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"`
+}
+
+func (m *RemoveUserOperation) Reset()                    { *m = RemoveUserOperation{} }
+func (m *RemoveUserOperation) String() string            { return proto.CompactTextString(m) }
+func (*RemoveUserOperation) ProtoMessage()               {}
+func (*RemoveUserOperation) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *RemoveUserOperation) GetEmail() string {
+	if m != nil {
+		return m.Email
+	}
+	return ""
+}
+
+type AddInboundRequest struct {
+	Inbound *v2ray_core.InboundHandlerConfig `protobuf:"bytes,1,opt,name=inbound" json:"inbound,omitempty"`
+}
+
+func (m *AddInboundRequest) Reset()                    { *m = AddInboundRequest{} }
+func (m *AddInboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*AddInboundRequest) ProtoMessage()               {}
+func (*AddInboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func (m *AddInboundRequest) GetInbound() *v2ray_core.InboundHandlerConfig {
+	if m != nil {
+		return m.Inbound
+	}
+	return nil
+}
+
+type AddInboundResponse struct {
+}
+
+func (m *AddInboundResponse) Reset()                    { *m = AddInboundResponse{} }
+func (m *AddInboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*AddInboundResponse) ProtoMessage()               {}
+func (*AddInboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+
+type RemoveInboundRequest struct {
+	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+}
+
+func (m *RemoveInboundRequest) Reset()                    { *m = RemoveInboundRequest{} }
+func (m *RemoveInboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*RemoveInboundRequest) ProtoMessage()               {}
+func (*RemoveInboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+
+func (m *RemoveInboundRequest) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+type RemoveInboundResponse struct {
+}
+
+func (m *RemoveInboundResponse) Reset()                    { *m = RemoveInboundResponse{} }
+func (m *RemoveInboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*RemoveInboundResponse) ProtoMessage()               {}
+func (*RemoveInboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+
+type AlterInboundRequest struct {
+	Tag       string                                 `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	Operation *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=operation" json:"operation,omitempty"`
+}
+
+func (m *AlterInboundRequest) Reset()                    { *m = AlterInboundRequest{} }
+func (m *AlterInboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*AlterInboundRequest) ProtoMessage()               {}
+func (*AlterInboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
+
+func (m *AlterInboundRequest) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+func (m *AlterInboundRequest) GetOperation() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.Operation
+	}
+	return nil
+}
+
+type AlterInboundResponse struct {
+}
+
+func (m *AlterInboundResponse) Reset()                    { *m = AlterInboundResponse{} }
+func (m *AlterInboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*AlterInboundResponse) ProtoMessage()               {}
+func (*AlterInboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
+
+type AddOutboundRequest struct {
+	Outbound *v2ray_core.OutboundHandlerConfig `protobuf:"bytes,1,opt,name=outbound" json:"outbound,omitempty"`
+}
+
+func (m *AddOutboundRequest) Reset()                    { *m = AddOutboundRequest{} }
+func (m *AddOutboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*AddOutboundRequest) ProtoMessage()               {}
+func (*AddOutboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
+
+func (m *AddOutboundRequest) GetOutbound() *v2ray_core.OutboundHandlerConfig {
+	if m != nil {
+		return m.Outbound
+	}
+	return nil
+}
+
+type AddOutboundResponse struct {
+}
+
+func (m *AddOutboundResponse) Reset()                    { *m = AddOutboundResponse{} }
+func (m *AddOutboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*AddOutboundResponse) ProtoMessage()               {}
+func (*AddOutboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
+
+type RemoveOutboundRequest struct {
+	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+}
+
+func (m *RemoveOutboundRequest) Reset()                    { *m = RemoveOutboundRequest{} }
+func (m *RemoveOutboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*RemoveOutboundRequest) ProtoMessage()               {}
+func (*RemoveOutboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
+
+func (m *RemoveOutboundRequest) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+type RemoveOutboundResponse struct {
+}
+
+func (m *RemoveOutboundResponse) Reset()                    { *m = RemoveOutboundResponse{} }
+func (m *RemoveOutboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*RemoveOutboundResponse) ProtoMessage()               {}
+func (*RemoveOutboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
+
+type AlterOutboundRequest struct {
+	Tag       string                                 `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
+	Operation *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=operation" json:"operation,omitempty"`
+}
+
+func (m *AlterOutboundRequest) Reset()                    { *m = AlterOutboundRequest{} }
+func (m *AlterOutboundRequest) String() string            { return proto.CompactTextString(m) }
+func (*AlterOutboundRequest) ProtoMessage()               {}
+func (*AlterOutboundRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
+
+func (m *AlterOutboundRequest) GetTag() string {
+	if m != nil {
+		return m.Tag
+	}
+	return ""
+}
+
+func (m *AlterOutboundRequest) GetOperation() *v2ray_core_common_serial.TypedMessage {
+	if m != nil {
+		return m.Operation
+	}
+	return nil
+}
+
+type AlterOutboundResponse struct {
+}
+
+func (m *AlterOutboundResponse) Reset()                    { *m = AlterOutboundResponse{} }
+func (m *AlterOutboundResponse) String() string            { return proto.CompactTextString(m) }
+func (*AlterOutboundResponse) ProtoMessage()               {}
+func (*AlterOutboundResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
+
+type Config struct {
+}
+
+func (m *Config) Reset()                    { *m = Config{} }
+func (m *Config) String() string            { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()               {}
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
+
+func init() {
+	proto.RegisterType((*AddUserOperation)(nil), "v2ray.core.app.proxyman.command.AddUserOperation")
+	proto.RegisterType((*RemoveUserOperation)(nil), "v2ray.core.app.proxyman.command.RemoveUserOperation")
+	proto.RegisterType((*AddInboundRequest)(nil), "v2ray.core.app.proxyman.command.AddInboundRequest")
+	proto.RegisterType((*AddInboundResponse)(nil), "v2ray.core.app.proxyman.command.AddInboundResponse")
+	proto.RegisterType((*RemoveInboundRequest)(nil), "v2ray.core.app.proxyman.command.RemoveInboundRequest")
+	proto.RegisterType((*RemoveInboundResponse)(nil), "v2ray.core.app.proxyman.command.RemoveInboundResponse")
+	proto.RegisterType((*AlterInboundRequest)(nil), "v2ray.core.app.proxyman.command.AlterInboundRequest")
+	proto.RegisterType((*AlterInboundResponse)(nil), "v2ray.core.app.proxyman.command.AlterInboundResponse")
+	proto.RegisterType((*AddOutboundRequest)(nil), "v2ray.core.app.proxyman.command.AddOutboundRequest")
+	proto.RegisterType((*AddOutboundResponse)(nil), "v2ray.core.app.proxyman.command.AddOutboundResponse")
+	proto.RegisterType((*RemoveOutboundRequest)(nil), "v2ray.core.app.proxyman.command.RemoveOutboundRequest")
+	proto.RegisterType((*RemoveOutboundResponse)(nil), "v2ray.core.app.proxyman.command.RemoveOutboundResponse")
+	proto.RegisterType((*AlterOutboundRequest)(nil), "v2ray.core.app.proxyman.command.AlterOutboundRequest")
+	proto.RegisterType((*AlterOutboundResponse)(nil), "v2ray.core.app.proxyman.command.AlterOutboundResponse")
+	proto.RegisterType((*Config)(nil), "v2ray.core.app.proxyman.command.Config")
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for HandlerService service
+
+type HandlerServiceClient interface {
+	AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error)
+	RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error)
+	AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error)
+	AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error)
+	RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error)
+	AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error)
+}
+
+type handlerServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewHandlerServiceClient(cc *grpc.ClientConn) HandlerServiceClient {
+	return &handlerServiceClient{cc}
+}
+
+func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) {
+	out := new(AddInboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AddInbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) {
+	out := new(RemoveInboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/RemoveInbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) {
+	out := new(AlterInboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AlterInbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) {
+	out := new(AddOutboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AddOutbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) {
+	out := new(RemoveOutboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/RemoveOutbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) {
+	out := new(AlterOutboundResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.proxyman.command.HandlerService/AlterOutbound", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for HandlerService service
+
+type HandlerServiceServer interface {
+	AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error)
+	RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error)
+	AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error)
+	AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error)
+	RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error)
+	AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error)
+}
+
+func RegisterHandlerServiceServer(s *grpc.Server, srv HandlerServiceServer) {
+	s.RegisterService(&_HandlerService_serviceDesc, srv)
+}
+
+func _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(AddInboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).AddInbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AddInbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(RemoveInboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).RemoveInbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/RemoveInbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(AlterInboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).AlterInbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AlterInbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(AddOutboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).AddOutbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AddOutbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(RemoveOutboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).RemoveOutbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/RemoveOutbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(AlterOutboundRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(HandlerServiceServer).AlterOutbound(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.proxyman.command.HandlerService/AlterOutbound",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _HandlerService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "v2ray.core.app.proxyman.command.HandlerService",
+	HandlerType: (*HandlerServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "AddInbound",
+			Handler:    _HandlerService_AddInbound_Handler,
+		},
+		{
+			MethodName: "RemoveInbound",
+			Handler:    _HandlerService_RemoveInbound_Handler,
+		},
+		{
+			MethodName: "AlterInbound",
+			Handler:    _HandlerService_AlterInbound_Handler,
+		},
+		{
+			MethodName: "AddOutbound",
+			Handler:    _HandlerService_AddOutbound_Handler,
+		},
+		{
+			MethodName: "RemoveOutbound",
+			Handler:    _HandlerService_RemoveOutbound_Handler,
+		},
+		{
+			MethodName: "AlterOutbound",
+			Handler:    _HandlerService_AlterOutbound_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "v2ray.com/core/app/proxyman/command/command.proto",
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/app/proxyman/command/command.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 557 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdf, 0x6b, 0xd3, 0x40,
+	0x1c, 0xb7, 0x53, 0xbb, 0xed, 0x3b, 0x1d, 0xf3, 0xda, 0x6e, 0x25, 0x3e, 0x6c, 0x46, 0x90, 0x0d,
+	0xe1, 0xa2, 0x59, 0x37, 0x41, 0xf0, 0xa1, 0xd6, 0x87, 0xf9, 0x20, 0x1d, 0x99, 0xfa, 0xe0, 0x8b,
+	0xdc, 0x92, 0xb3, 0x04, 0x92, 0xbb, 0xf3, 0x92, 0x56, 0x2b, 0x08, 0x82, 0xff, 0x80, 0x7f, 0x87,
+	0x7f, 0xa5, 0x24, 0x77, 0xd7, 0x25, 0x69, 0x21, 0x0d, 0xf8, 0xd4, 0xf4, 0xfa, 0xf9, 0xf5, 0xfd,
+	0xde, 0x27, 0x14, 0x9e, 0xcf, 0x5c, 0x49, 0xe6, 0xd8, 0xe7, 0xb1, 0xe3, 0x73, 0x49, 0x1d, 0x22,
+	0x84, 0x23, 0x24, 0xff, 0x3e, 0x8f, 0x09, 0x73, 0x7c, 0x1e, 0xc7, 0x84, 0x05, 0xe6, 0x13, 0x0b,
+	0xc9, 0x53, 0x8e, 0x0e, 0x0d, 0x45, 0x52, 0x4c, 0x84, 0xc0, 0x06, 0x8e, 0x35, 0xcc, 0x3a, 0xa9,
+	0x68, 0x66, 0xe7, 0x9c, 0x39, 0x39, 0xdb, 0xe7, 0x91, 0x33, 0x4d, 0xa8, 0x54, 0x5a, 0xd6, 0xb3,
+	0xd5, 0xd0, 0x84, 0xca, 0x90, 0x44, 0x4e, 0x3a, 0x17, 0x34, 0xf8, 0x1c, 0xd3, 0x24, 0x21, 0x13,
+	0xaa, 0x19, 0x0f, 0x97, 0x18, 0xec, 0x4b, 0x38, 0x51, 0x3f, 0xda, 0x17, 0xb0, 0x37, 0x0c, 0x82,
+	0x0f, 0x09, 0x95, 0x63, 0x41, 0x25, 0x49, 0x43, 0xce, 0xd0, 0x00, 0xee, 0x64, 0x86, 0xfd, 0xd6,
+	0x51, 0xeb, 0x78, 0xc7, 0x3d, 0xc2, 0x85, 0xf4, 0xca, 0x0d, 0x9b, 0x60, 0x38, 0x23, 0x7a, 0x39,
+	0xda, 0x7e, 0x0a, 0x1d, 0x8f, 0xc6, 0x7c, 0x46, 0xcb, 0x62, 0x5d, 0xb8, 0x4b, 0x63, 0x12, 0x46,
+	0xb9, 0xda, 0xb6, 0xa7, 0xbe, 0xd8, 0x63, 0x78, 0x30, 0x0c, 0x82, 0xb7, 0xec, 0x9a, 0x4f, 0x59,
+	0xe0, 0xd1, 0xaf, 0x53, 0x9a, 0xa4, 0xe8, 0x25, 0x6c, 0x86, 0xea, 0x64, 0x95, 0xb5, 0x06, 0x5f,
+	0x10, 0x16, 0x44, 0x54, 0x8e, 0xf2, 0x21, 0x3c, 0x43, 0xb0, 0xbb, 0x80, 0x8a, 0x82, 0x89, 0xe0,
+	0x2c, 0xa1, 0xf6, 0x31, 0x74, 0x55, 0xa6, 0x8a, 0xd3, 0x1e, 0xdc, 0x4e, 0xc9, 0x44, 0x47, 0xca,
+	0x1e, 0xed, 0x03, 0xe8, 0x55, 0x90, 0x5a, 0x22, 0x86, 0xce, 0x30, 0x4a, 0xa9, 0xac, 0x53, 0x40,
+	0x6f, 0x60, 0x9b, 0x9b, 0xa9, 0xfb, 0x1b, 0x79, 0xfe, 0x27, 0x2b, 0x56, 0xa7, 0x2e, 0x0a, 0xbf,
+	0xcf, 0x2e, 0xea, 0x9d, 0xba, 0x27, 0xef, 0x86, 0x68, 0xef, 0x43, 0xb7, 0x6c, 0xa7, 0x63, 0x5c,
+	0xe5, 0xf3, 0x8d, 0xa7, 0x69, 0x29, 0xc5, 0x2b, 0xd8, 0xe2, 0xfa, 0x48, 0xaf, 0xec, 0x51, 0xd1,
+	0xd2, 0xc0, 0xcb, 0x3b, 0x5b, 0x50, 0xec, 0x1e, 0x74, 0x4a, 0xa2, 0xda, 0xeb, 0xc4, 0xec, 0xa2,
+	0x6a, 0xb7, 0xbc, 0xb6, 0x3e, 0xec, 0x57, 0xa1, 0x5a, 0x84, 0xe9, 0x41, 0x6a, 0x35, 0xfe, 0xd3,
+	0xe2, 0x0e, 0xa0, 0x57, 0xf1, 0xd3, 0x41, 0xb6, 0xa0, 0xad, 0x06, 0x77, 0xff, 0xb4, 0x61, 0x57,
+	0xaf, 0xe2, 0x8a, 0xca, 0x59, 0xe8, 0x53, 0xf4, 0x0d, 0xe0, 0xa6, 0x36, 0xc8, 0xc5, 0x35, 0x2f,
+	0x2a, 0x5e, 0x2a, 0xad, 0x75, 0xda, 0x88, 0xa3, 0x33, 0xdd, 0x42, 0xbf, 0x5a, 0x70, 0xbf, 0x54,
+	0x38, 0x74, 0x56, 0x2b, 0xb4, 0xaa, 0xca, 0xd6, 0x79, 0x53, 0xda, 0x22, 0xc2, 0x4f, 0xb8, 0x57,
+	0xac, 0x1a, 0x1a, 0xd4, 0x4f, 0xb2, 0xfc, 0x22, 0x58, 0x67, 0x0d, 0x59, 0x0b, 0xfb, 0x1f, 0xb0,
+	0x53, 0x28, 0x1f, 0x5a, 0x6b, 0x8f, 0x95, 0x32, 0x59, 0x83, 0x66, 0xa4, 0x85, 0xf7, 0xef, 0x16,
+	0xec, 0x96, 0x7b, 0x8b, 0xd6, 0xdd, 0x63, 0x35, 0xc2, 0x8b, 0xc6, 0xbc, 0x52, 0x07, 0x4a, 0x9d,
+	0x45, 0x6b, 0x2e, 0xb3, 0x9a, 0xe1, 0xbc, 0x29, 0xcd, 0x44, 0x78, 0xed, 0xc1, 0x63, 0x9f, 0xc7,
+	0x75, 0xf4, 0xcb, 0xd6, 0xa7, 0x4d, 0xfd, 0xf8, 0x77, 0xe3, 0xf0, 0xa3, 0xeb, 0x91, 0x39, 0x1e,
+	0x65, 0xe0, 0xa1, 0x10, 0xf8, 0xd2, 0x80, 0x47, 0x0a, 0x71, 0xdd, 0xce, 0xff, 0x1d, 0x4e, 0xff,
+	0x05, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x05, 0xaa, 0x44, 0x29, 0x07, 0x00, 0x00,
+}

+ 80 - 0
app/proxyman/command/command.proto

@@ -0,0 +1,80 @@
+syntax = "proto3";
+
+package v2ray.core.app.proxyman.command;
+option csharp_namespace = "V2Ray.Core.App.Proxyman.Command";
+option go_package = "command";
+option java_package = "com.v2ray.core.app.proxyman.command";
+option java_multiple_files = true;
+
+import "v2ray.com/core/common/protocol/user.proto";
+import "v2ray.com/core/common/serial/typed_message.proto";
+import "v2ray.com/core/config.proto";
+
+message AddUserOperation {
+  v2ray.core.common.protocol.User user = 1;
+}
+
+message RemoveUserOperation {
+  string email = 1;
+}
+
+message AddInboundRequest {
+  core.InboundHandlerConfig inbound = 1;
+}
+
+message AddInboundResponse{
+
+}
+
+message RemoveInboundRequest {
+  string tag = 1;
+}
+
+message RemoveInboundResponse {}
+
+message AlterInboundRequest {
+  string tag = 1;
+  v2ray.core.common.serial.TypedMessage operation = 2;
+}
+
+message AlterInboundResponse {
+}
+
+message AddOutboundRequest {
+  core.OutboundHandlerConfig outbound = 1;
+}
+
+message AddOutboundResponse {
+
+}
+
+message RemoveOutboundRequest {
+  string tag = 1;
+}
+
+message RemoveOutboundResponse {
+}
+
+message AlterOutboundRequest {
+  string tag = 1;
+  v2ray.core.common.serial.TypedMessage operation = 2;
+}
+
+message AlterOutboundResponse {
+}
+
+service HandlerService {
+  rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}
+
+  rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {}
+
+  rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}
+
+  rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {}
+
+  rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}
+
+  rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}
+}
+
+message Config {}

+ 3 - 0
app/proxyman/command/doc.go

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

+ 7 - 0
app/proxyman/command/errors.generated.go

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

+ 0 - 17
app/proxyman/config.go

@@ -1,11 +1,5 @@
 package proxyman
 package proxyman
 
 
-import (
-	"context"
-
-	"v2ray.com/core/proxy"
-)
-
 func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
 func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
 	if s == nil || s.Concurrency == nil {
 	if s == nil || s.Concurrency == nil {
 		return 3
 		return 3
@@ -19,14 +13,3 @@ func (s *AllocationStrategy) GetRefreshValue() uint32 {
 	}
 	}
 	return s.Refresh.Value
 	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 proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
 import fmt "fmt"
 import math "math"
 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_net "v2ray.com/core/common/net"
 import v2ray_core_common_net1 "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"
 import v2ray_core_transport_internet "v2ray.com/core/transport/internet"
@@ -211,45 +210,13 @@ func (m *ReceiverConfig) GetDomainOverride() []KnownProtocols {
 	return nil
 	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 {
 type OutboundConfig struct {
 }
 }
 
 
 func (m *OutboundConfig) Reset()                    { *m = OutboundConfig{} }
 func (m *OutboundConfig) Reset()                    { *m = OutboundConfig{} }
 func (m *OutboundConfig) String() string            { return proto.CompactTextString(m) }
 func (m *OutboundConfig) String() string            { return proto.CompactTextString(m) }
 func (*OutboundConfig) ProtoMessage()               {}
 func (*OutboundConfig) ProtoMessage()               {}
-func (*OutboundConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+func (*OutboundConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
 
 
 type SenderConfig struct {
 type SenderConfig struct {
 	// Send traffic through the given IP. Only IP is allowed.
 	// 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) Reset()                    { *m = SenderConfig{} }
 func (m *SenderConfig) String() string            { return proto.CompactTextString(m) }
 func (m *SenderConfig) String() string            { return proto.CompactTextString(m) }
 func (*SenderConfig) ProtoMessage()               {}
 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 {
 func (m *SenderConfig) GetVia() *v2ray_core_common_net.IPOrDomain {
 	if m != nil {
 	if m != nil {
@@ -292,54 +259,6 @@ func (m *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
 	return nil
 	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 {
 type MultiplexingConfig struct {
 	// Whether or not Mux is enabled.
 	// Whether or not Mux is enabled.
 	Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"`
 	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) Reset()                    { *m = MultiplexingConfig{} }
 func (m *MultiplexingConfig) String() string            { return proto.CompactTextString(m) }
 func (m *MultiplexingConfig) String() string            { return proto.CompactTextString(m) }
 func (*MultiplexingConfig) ProtoMessage()               {}
 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 {
 func (m *MultiplexingConfig) GetEnabled() bool {
 	if m != nil {
 	if m != nil {
@@ -372,10 +291,8 @@ func init() {
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyConcurrency)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyConcurrency)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyRefresh)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh")
 	proto.RegisterType((*AllocationStrategy_AllocationStrategyRefresh)(nil), "v2ray.core.app.proxyman.AllocationStrategy.AllocationStrategyRefresh")
 	proto.RegisterType((*ReceiverConfig)(nil), "v2ray.core.app.proxyman.ReceiverConfig")
 	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((*OutboundConfig)(nil), "v2ray.core.app.proxyman.OutboundConfig")
 	proto.RegisterType((*SenderConfig)(nil), "v2ray.core.app.proxyman.SenderConfig")
 	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.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.KnownProtocols", KnownProtocols_name, KnownProtocols_value)
 	proto.RegisterEnum("v2ray.core.app.proxyman.AllocationStrategy_Type", AllocationStrategy_Type_name, AllocationStrategy_Type_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) }
 func init() { proto.RegisterFile("v2ray.com/core/app/proxyman/config.proto", fileDescriptor0) }
 
 
 var fileDescriptor0 = []byte{
 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_package = "com.v2ray.core.app.proxyman";
 option java_multiple_files = true;
 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/address.proto";
 import "v2ray.com/core/common/net/port.proto";
 import "v2ray.com/core/common/net/port.proto";
 import "v2ray.com/core/transport/internet/config.proto";
 import "v2ray.com/core/transport/internet/config.proto";
@@ -61,15 +60,6 @@ message ReceiverConfig {
   repeated KnownProtocols domain_override = 7;
   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 {
 message OutboundConfig {
   
   
 }
 }
@@ -82,19 +72,6 @@ message SenderConfig {
   MultiplexingConfig multiplex_settings = 4;
   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 {
 message MultiplexingConfig {
   // Whether or not Mux is enabled.
   // Whether or not Mux is enabled.
   bool enabled = 1;
   bool enabled = 1;

+ 20 - 3
app/proxyman/inbound/always.go

@@ -5,6 +5,7 @@ import (
 
 
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/app/proxyman/mux"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
@@ -14,17 +15,23 @@ type AlwaysOnInboundHandler struct {
 	proxy   proxy.Inbound
 	proxy   proxy.Inbound
 	workers []worker
 	workers []worker
 	mux     *mux.Server
 	mux     *mux.Server
+	tag     string
 }
 }
 
 
 func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
 func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
-	p, err := proxy.CreateInboundHandler(ctx, proxyConfig)
+	rawProxy, err := common.CreateObject(ctx, proxyConfig)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	p, ok := rawProxy.(proxy.Inbound)
+	if !ok {
+		return nil, newError("not an inbound proxy.")
+	}
 
 
 	h := &AlwaysOnInboundHandler{
 	h := &AlwaysOnInboundHandler{
 		proxy: p,
 		proxy: p,
 		mux:   mux.NewServer(ctx),
 		mux:   mux.NewServer(ctx),
+		tag:   tag,
 	}
 	}
 
 
 	nl := p.Network()
 	nl := p.Network()
@@ -74,16 +81,26 @@ func (h *AlwaysOnInboundHandler) Start() error {
 	return nil
 	return nil
 }
 }
 
 
-func (h *AlwaysOnInboundHandler) Close() {
+func (h *AlwaysOnInboundHandler) Close() error {
 	for _, worker := range h.workers {
 	for _, worker := range h.workers {
 		worker.Close()
 		worker.Close()
 	}
 	}
+	h.mux.Close()
+	return nil
 }
 }
 
 
-func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port, int) {
+func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
 	if len(h.workers) == 0 {
 	if len(h.workers) == 0 {
 		return nil, 0, 0
 		return nil, 0, 0
 	}
 	}
 	w := h.workers[dice.Roll(len(h.workers))]
 	w := h.workers[dice.Roll(len(h.workers))]
 	return w.Proxy(), w.Port(), 9999
 	return w.Proxy(), w.Port(), 9999
 }
 }
+
+func (h *AlwaysOnInboundHandler) Tag() string {
+	return h.tag
+}
+
+func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound {
+	return h.proxy
+}

+ 28 - 31
app/proxyman/inbound/dynamic.go

@@ -5,17 +5,18 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
 )
 )
 
 
 type DynamicInboundHandler struct {
 type DynamicInboundHandler struct {
 	tag            string
 	tag            string
-	ctx            context.Context
-	cancel         context.CancelFunc
+	v              *core.Instance
 	proxyConfig    interface{}
 	proxyConfig    interface{}
 	receiverConfig *proxyman.ReceiverConfig
 	receiverConfig *proxyman.ReceiverConfig
 	portMutex      sync.Mutex
 	portMutex      sync.Mutex
@@ -24,18 +25,26 @@ type DynamicInboundHandler struct {
 	worker         []worker
 	worker         []worker
 	lastRefresh    time.Time
 	lastRefresh    time.Time
 	mux            *mux.Server
 	mux            *mux.Server
+	task           *signal.PeriodicTask
 }
 }
 
 
 func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
 func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
-	ctx, cancel := context.WithCancel(ctx)
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
 	h := &DynamicInboundHandler{
 	h := &DynamicInboundHandler{
-		ctx:            ctx,
 		tag:            tag,
 		tag:            tag,
-		cancel:         cancel,
 		proxyConfig:    proxyConfig,
 		proxyConfig:    proxyConfig,
 		receiverConfig: receiverConfig,
 		receiverConfig: receiverConfig,
 		portsInUse:     make(map[net.Port]bool),
 		portsInUse:     make(map[net.Port]bool),
 		mux:            mux.NewServer(ctx),
 		mux:            mux.NewServer(ctx),
+		v:              v,
+	}
+
+	h.task = &signal.PeriodicTask{
+		Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()),
+		Execute:  h.refresh,
 	}
 	}
 
 
 	return h, nil
 	return h, nil
@@ -59,9 +68,7 @@ func (h *DynamicInboundHandler) allocatePort() net.Port {
 	}
 	}
 }
 }
 
 
-func (h *DynamicInboundHandler) waitAnyCloseWorkers(ctx context.Context, cancel context.CancelFunc, workers []worker, duration time.Duration) {
-	time.Sleep(duration)
-	cancel()
+func (h *DynamicInboundHandler) closeWorkers(workers []worker) {
 	ports2Del := make([]net.Port, len(workers))
 	ports2Del := make([]net.Port, len(workers))
 	for idx, worker := range workers {
 	for idx, worker := range workers {
 		ports2Del[idx] = worker.Port()
 		ports2Del[idx] = worker.Port()
@@ -80,7 +87,6 @@ func (h *DynamicInboundHandler) refresh() error {
 
 
 	timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2
 	timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2
 	concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue()
 	concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue()
-	ctx, cancel := context.WithTimeout(h.ctx, timeout)
 	workers := make([]worker, 0, concurrency)
 	workers := make([]worker, 0, concurrency)
 
 
 	address := h.receiverConfig.Listen.AsAddress()
 	address := h.receiverConfig.Listen.AsAddress()
@@ -89,11 +95,12 @@ func (h *DynamicInboundHandler) refresh() error {
 	}
 	}
 	for i := uint32(0); i < concurrency; i++ {
 	for i := uint32(0); i < concurrency; i++ {
 		port := h.allocatePort()
 		port := h.allocatePort()
-		p, err := proxy.CreateInboundHandler(ctx, h.proxyConfig)
+		rawProxy, err := h.v.CreateObject(h.proxyConfig)
 		if err != nil {
 		if err != nil {
 			newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog()
 			newError("failed to create proxy instance").Base(err).AtWarning().WriteToLog()
 			continue
 			continue
 		}
 		}
+		p := rawProxy.(proxy.Inbound)
 		nl := p.Network()
 		nl := p.Network()
 		if nl.HasNetwork(net.Network_TCP) {
 		if nl.HasNetwork(net.Network_TCP) {
 			worker := &tcpWorker{
 			worker := &tcpWorker{
@@ -134,36 +141,22 @@ func (h *DynamicInboundHandler) refresh() error {
 	h.worker = workers
 	h.worker = workers
 	h.workerMutex.Unlock()
 	h.workerMutex.Unlock()
 
 
-	go h.waitAnyCloseWorkers(ctx, cancel, workers, timeout)
+	time.AfterFunc(timeout, func() {
+		h.closeWorkers(workers)
+	})
 
 
 	return nil
 	return nil
 }
 }
 
 
-func (h *DynamicInboundHandler) monitor() {
-	timer := time.NewTicker(time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()))
-	defer timer.Stop()
-
-	for {
-		select {
-		case <-h.ctx.Done():
-			return
-		case <-timer.C:
-			h.refresh()
-		}
-	}
-}
-
 func (h *DynamicInboundHandler) Start() error {
 func (h *DynamicInboundHandler) Start() error {
-	err := h.refresh()
-	go h.monitor()
-	return err
+	return h.task.Start()
 }
 }
 
 
-func (h *DynamicInboundHandler) Close() {
-	h.cancel()
+func (h *DynamicInboundHandler) Close() error {
+	return h.task.Close()
 }
 }
 
 
-func (h *DynamicInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port, int) {
+func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
 	h.workerMutex.RLock()
 	h.workerMutex.RLock()
 	defer h.workerMutex.RUnlock()
 	defer h.workerMutex.RUnlock()
 
 
@@ -174,3 +167,7 @@ func (h *DynamicInboundHandler) GetRandomInboundProxy() (proxy.Inbound, net.Port
 	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
 	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
 	return w.Proxy(), w.Port(), int(expire)
 	return w.Proxy(), w.Port(), int(expire)
 }
 }
+
+func (h *DynamicInboundHandler) Tag() string {
+	return h.tag
+}

+ 100 - 43
app/proxyman/inbound/inbound.go

@@ -4,65 +4,60 @@ package inbound
 
 
 import (
 import (
 	"context"
 	"context"
+	"sync"
 
 
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 )
 )
 
 
 // Manager is to manage all inbound handlers.
 // Manager is to manage all inbound handlers.
 type Manager struct {
 type Manager struct {
-	handlers       []proxyman.InboundHandler
-	taggedHandlers map[string]proxyman.InboundHandler
+	access          sync.RWMutex
+	untaggedHandler []core.InboundHandler
+	taggedHandlers  map[string]core.InboundHandler
+	running         bool
 }
 }
 
 
+// New returns a new Manager for inbound handlers.
 func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) {
 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
+	m := &Manager{
+		taggedHandlers: make(map[string]core.InboundHandler),
 	}
 	}
-	receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
-	if !ok {
-		return newError("not a ReceiverConfig").AtError()
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context")
 	}
 	}
-	proxySettings, err := config.ProxySettings.GetInstance()
-	if err != nil {
-		return err
-	}
-	var handler proxyman.InboundHandler
-	tag := config.Tag
-	allocStrategy := receiverSettings.AllocationStrategy
-	if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
-		h, err := NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
-		if err != nil {
-			return err
-		}
-		handler = h
-	} else if allocStrategy.Type == proxyman.AllocationStrategy_Random {
-		h, err := NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
-		if err != nil {
-			return err
-		}
-		handler = h
+	if err := v.RegisterFeature((*core.InboundHandlerManager)(nil), m); err != nil {
+		return nil, newError("unable to register InboundHandlerManager").Base(err)
 	}
 	}
+	return m, nil
+}
 
 
-	if handler == nil {
-		return newError("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
-	}
+// AddHandler implements core.InboundHandlerManager.
+func (m *Manager) AddHandler(ctx context.Context, handler core.InboundHandler) error {
+	m.access.Lock()
+	defer m.access.Unlock()
 
 
-	m.handlers = append(m.handlers, handler)
+	tag := handler.Tag()
 	if len(tag) > 0 {
 	if len(tag) > 0 {
 		m.taggedHandlers[tag] = handler
 		m.taggedHandlers[tag] = handler
+	} else {
+		m.untaggedHandler = append(m.untaggedHandler, handler)
+	}
+
+	if m.running {
+		return handler.Start()
 	}
 	}
+
 	return nil
 	return nil
 }
 }
 
 
-func (m *Manager) GetHandler(ctx context.Context, tag string) (proxyman.InboundHandler, error) {
+// GetHandler returns core.InboundHandlerManager.
+func (m *Manager) GetHandler(ctx context.Context, tag string) (core.InboundHandler, error) {
+	m.access.RLock()
+	defer m.access.RUnlock()
+
 	handler, found := m.taggedHandlers[tag]
 	handler, found := m.taggedHandlers[tag]
 	if !found {
 	if !found {
 		return nil, newError("handler not found: ", tag)
 		return nil, newError("handler not found: ", tag)
@@ -70,8 +65,36 @@ func (m *Manager) GetHandler(ctx context.Context, tag string) (proxyman.InboundH
 	return handler, nil
 	return handler, nil
 }
 }
 
 
+func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
+	if len(tag) == 0 {
+		return core.ErrNoClue
+	}
+
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	if handler, found := m.taggedHandlers[tag]; found {
+		handler.Close()
+		delete(m.taggedHandlers, tag)
+		return nil
+	}
+
+	return core.ErrNoClue
+}
+
 func (m *Manager) Start() error {
 func (m *Manager) Start() error {
-	for _, handler := range m.handlers {
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	m.running = true
+
+	for _, handler := range m.taggedHandlers {
+		if err := handler.Start(); err != nil {
+			return err
+		}
+	}
+
+	for _, handler := range m.untaggedHandler {
 		if err := handler.Start(); err != nil {
 		if err := handler.Start(); err != nil {
 			return err
 			return err
 		}
 		}
@@ -79,18 +102,52 @@ func (m *Manager) Start() error {
 	return nil
 	return nil
 }
 }
 
 
-func (m *Manager) Close() {
-	for _, handler := range m.handlers {
+func (m *Manager) Close() error {
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	m.running = false
+
+	for _, handler := range m.taggedHandlers {
+		handler.Close()
+	}
+	for _, handler := range m.untaggedHandler {
 		handler.Close()
 		handler.Close()
 	}
 	}
+
+	return nil
 }
 }
 
 
-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() {
 func init() {
 	common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 	common.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		return New(ctx, config.(*proxyman.InboundConfig))
 		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))
+	}))
 }
 }

+ 44 - 63
app/proxyman/inbound/worker.go

@@ -7,10 +7,12 @@ import (
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet/tcp"
 	"v2ray.com/core/transport/internet/tcp"
@@ -19,7 +21,7 @@ import (
 
 
 type worker interface {
 type worker interface {
 	Start() error
 	Start() error
-	Close()
+	Close() error
 	Port() net.Port
 	Port() net.Port
 	Proxy() proxy.Inbound
 	Proxy() proxy.Inbound
 }
 }
@@ -31,16 +33,14 @@ type tcpWorker struct {
 	stream       *internet.StreamConfig
 	stream       *internet.StreamConfig
 	recvOrigDest bool
 	recvOrigDest bool
 	tag          string
 	tag          string
-	dispatcher   dispatcher.Interface
+	dispatcher   core.Dispatcher
 	sniffers     []proxyman.KnownProtocols
 	sniffers     []proxyman.KnownProtocols
 
 
-	ctx    context.Context
-	cancel context.CancelFunc
-	hub    internet.Listener
+	hub internet.Listener
 }
 }
 
 
 func (w *tcpWorker) callback(conn internet.Connection) {
 func (w *tcpWorker) callback(conn internet.Connection) {
-	ctx, cancel := context.WithCancel(w.ctx)
+	ctx, cancel := context.WithCancel(context.Background())
 	if w.recvOrigDest {
 	if w.recvOrigDest {
 		dest, err := tcp.GetOriginalDestination(conn)
 		dest, err := tcp.GetOriginalDestination(conn)
 		if err != nil {
 		if err != nil {
@@ -70,45 +70,24 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
 }
 }
 
 
 func (w *tcpWorker) Start() error {
 func (w *tcpWorker) Start() error {
-	ctx, cancel := context.WithCancel(context.Background())
-	w.ctx = ctx
-	w.cancel = cancel
-	ctx = internet.ContextWithStreamSettings(ctx, w.stream)
-	conns := make(chan internet.Connection, 16)
-	hub, err := internet.ListenTCP(ctx, w.address, w.port, conns)
+	ctx := internet.ContextWithStreamSettings(context.Background(), w.stream)
+	hub, err := internet.ListenTCP(ctx, w.address, w.port, func(conn internet.Connection) {
+		go w.callback(conn)
+	})
 	if err != nil {
 	if err != nil {
 		return newError("failed to listen TCP on ", w.port).AtWarning().Base(err)
 		return newError("failed to listen TCP on ", w.port).AtWarning().Base(err)
 	}
 	}
-	go w.handleConnections(conns)
 	w.hub = hub
 	w.hub = hub
 	return nil
 	return nil
 }
 }
 
 
-func (w *tcpWorker) handleConnections(conns <-chan internet.Connection) {
-	for {
-		select {
-		case <-w.ctx.Done():
-			w.hub.Close()
-		L:
-			for {
-				select {
-				case conn := <-conns:
-					conn.Close()
-				default:
-					break L
-				}
-			}
-			return
-		case conn := <-conns:
-			go w.callback(conn)
-		}
-	}
-}
-
-func (w *tcpWorker) Close() {
+func (w *tcpWorker) Close() error {
 	if w.hub != nil {
 	if w.hub != nil {
-		w.cancel()
+		common.Close(w.hub)
+		common.Close(w.proxy)
 	}
 	}
+
+	return nil
 }
 }
 
 
 func (w *tcpWorker) Port() net.Port {
 func (w *tcpWorker) Port() net.Port {
@@ -121,7 +100,7 @@ type udpConn struct {
 	output           func([]byte) (int, error)
 	output           func([]byte) (int, error)
 	remote           net.Addr
 	remote           net.Addr
 	local            net.Addr
 	local            net.Addr
-	cancel           context.CancelFunc
+	done             *signal.Done
 }
 }
 
 
 func (c *udpConn) updateActivity() {
 func (c *udpConn) updateActivity() {
@@ -129,13 +108,14 @@ func (c *udpConn) updateActivity() {
 }
 }
 
 
 func (c *udpConn) Read(buf []byte) (int, error) {
 func (c *udpConn) Read(buf []byte) (int, error) {
-	in, open := <-c.input
-	if !open {
+	select {
+	case in := <-c.input:
+		defer in.Release()
+		c.updateActivity()
+		return copy(buf, in.Bytes()), nil
+	case <-c.done.C():
 		return 0, io.EOF
 		return 0, io.EOF
 	}
 	}
-	defer in.Release()
-	c.updateActivity()
-	return copy(buf, in.Bytes()), nil
 }
 }
 
 
 // Write implements io.Writer.
 // Write implements io.Writer.
@@ -148,6 +128,7 @@ func (c *udpConn) Write(buf []byte) (int, error) {
 }
 }
 
 
 func (c *udpConn) Close() error {
 func (c *udpConn) Close() error {
+	common.Close(c.done)
 	return nil
 	return nil
 }
 }
 
 
@@ -171,7 +152,7 @@ func (*udpConn) SetWriteDeadline(time.Time) error {
 	return nil
 	return nil
 }
 }
 
 
-type connId struct {
+type connID struct {
 	src  net.Destination
 	src  net.Destination
 	dest net.Destination
 	dest net.Destination
 }
 }
@@ -185,14 +166,13 @@ type udpWorker struct {
 	port         net.Port
 	port         net.Port
 	recvOrigDest bool
 	recvOrigDest bool
 	tag          string
 	tag          string
-	dispatcher   dispatcher.Interface
+	dispatcher   core.Dispatcher
 
 
-	ctx        context.Context
-	cancel     context.CancelFunc
-	activeConn map[connId]*udpConn
+	done       *signal.Done
+	activeConn map[connID]*udpConn
 }
 }
 
 
-func (w *udpWorker) getConnection(id connId) (*udpConn, bool) {
+func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
 	w.Lock()
 	w.Lock()
 	defer w.Unlock()
 	defer w.Unlock()
 
 
@@ -213,6 +193,7 @@ func (w *udpWorker) getConnection(id connId) (*udpConn, bool) {
 			IP:   w.address.IP(),
 			IP:   w.address.IP(),
 			Port: int(w.port),
 			Port: int(w.port),
 		},
 		},
+		done: signal.NewDone(),
 	}
 	}
 	w.activeConn[id] = conn
 	w.activeConn[id] = conn
 
 
@@ -221,22 +202,22 @@ func (w *udpWorker) getConnection(id connId) (*udpConn, bool) {
 }
 }
 
 
 func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) {
 func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) {
-	id := connId{
+	id := connID{
 		src:  source,
 		src:  source,
 		dest: originalDest,
 		dest: originalDest,
 	}
 	}
 	conn, existing := w.getConnection(id)
 	conn, existing := w.getConnection(id)
 	select {
 	select {
 	case conn.input <- b:
 	case conn.input <- b:
+	case <-conn.done.C():
+		b.Release()
 	default:
 	default:
 		b.Release()
 		b.Release()
 	}
 	}
 
 
 	if !existing {
 	if !existing {
 		go func() {
 		go func() {
-			ctx := w.ctx
-			ctx, cancel := context.WithCancel(ctx)
-			conn.cancel = cancel
+			ctx := context.Background()
 			if originalDest.IsValid() {
 			if originalDest.IsValid() {
 				ctx = proxy.ContextWithOriginalTarget(ctx, originalDest)
 				ctx = proxy.ContextWithOriginalTarget(ctx, originalDest)
 			}
 			}
@@ -248,23 +229,21 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
 			if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
 			if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
 				newError("connection ends").Base(err).WriteToLog()
 				newError("connection ends").Base(err).WriteToLog()
 			}
 			}
+			conn.Close()
 			w.removeConn(id)
 			w.removeConn(id)
-			cancel()
 		}()
 		}()
 	}
 	}
 }
 }
 
 
-func (w *udpWorker) removeConn(id connId) {
+func (w *udpWorker) removeConn(id connID) {
 	w.Lock()
 	w.Lock()
 	delete(w.activeConn, id)
 	delete(w.activeConn, id)
 	w.Unlock()
 	w.Unlock()
 }
 }
 
 
 func (w *udpWorker) Start() error {
 func (w *udpWorker) Start() error {
-	w.activeConn = make(map[connId]*udpConn, 16)
-	ctx, cancel := context.WithCancel(context.Background())
-	w.ctx = ctx
-	w.cancel = cancel
+	w.activeConn = make(map[connID]*udpConn, 16)
+	w.done = signal.NewDone()
 	h, err := udp.ListenUDP(w.address, w.port, udp.ListenOption{
 	h, err := udp.ListenUDP(w.address, w.port, udp.ListenOption{
 		Callback:            w.callback,
 		Callback:            w.callback,
 		ReceiveOriginalDest: w.recvOrigDest,
 		ReceiveOriginalDest: w.recvOrigDest,
@@ -277,11 +256,13 @@ func (w *udpWorker) Start() error {
 	return nil
 	return nil
 }
 }
 
 
-func (w *udpWorker) Close() {
+func (w *udpWorker) Close() error {
 	if w.hub != nil {
 	if w.hub != nil {
 		w.hub.Close()
 		w.hub.Close()
-		w.cancel()
+		w.done.Close()
+		common.Close(w.proxy)
 	}
 	}
+	return nil
 }
 }
 
 
 func (w *udpWorker) monitor() {
 func (w *udpWorker) monitor() {
@@ -290,7 +271,7 @@ func (w *udpWorker) monitor() {
 
 
 	for {
 	for {
 		select {
 		select {
-		case <-w.ctx.Done():
+		case <-w.done.C():
 			return
 			return
 		case <-timer.C:
 		case <-timer.C:
 			nowSec := time.Now().Unix()
 			nowSec := time.Now().Unix()
@@ -298,7 +279,7 @@ func (w *udpWorker) monitor() {
 			for addr, conn := range w.activeConn {
 			for addr, conn := range w.activeConn {
 				if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 8 {
 				if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 8 {
 					delete(w.activeConn, addr)
 					delete(w.activeConn, addr)
-					conn.cancel()
+					conn.Close()
 				}
 				}
 			}
 			}
 			w.Unlock()
 			w.Unlock()

+ 34 - 40
app/proxyman/mux/mux.go

@@ -8,13 +8,13 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/ray"
 	"v2ray.com/core/transport/ray"
 )
 )
@@ -75,8 +75,7 @@ func (m *ClientManager) onClientFinish() {
 type Client struct {
 type Client struct {
 	sessionManager *SessionManager
 	sessionManager *SessionManager
 	inboundRay     ray.InboundRay
 	inboundRay     ray.InboundRay
-	ctx            context.Context
-	cancel         context.CancelFunc
+	done           *signal.Done
 	manager        *ClientManager
 	manager        *ClientManager
 	concurrency    uint32
 	concurrency    uint32
 }
 }
@@ -86,26 +85,26 @@ var muxCoolPort = net.Port(9527)
 
 
 // NewClient creates a new mux.Client.
 // NewClient creates a new mux.Client.
 func NewClient(p proxy.Outbound, dialer proxy.Dialer, m *ClientManager) (*Client, error) {
 func NewClient(p proxy.Outbound, dialer proxy.Dialer, m *ClientManager) (*Client, error) {
-	ctx, cancel := context.WithCancel(context.Background())
-	ctx = proxy.ContextWithTarget(ctx, net.TCPDestination(muxCoolAddress, muxCoolPort))
+	ctx := proxy.ContextWithTarget(context.Background(), net.TCPDestination(muxCoolAddress, muxCoolPort))
+	ctx, cancel := context.WithCancel(ctx)
 	pipe := ray.NewRay(ctx)
 	pipe := ray.NewRay(ctx)
 
 
-	go func() {
-		if err := p.Process(ctx, pipe, dialer); err != nil {
-			cancel()
-
-			errors.New("failed to handler mux client connection").Base(err).WriteToLog()
-		}
-	}()
-
 	c := &Client{
 	c := &Client{
 		sessionManager: NewSessionManager(),
 		sessionManager: NewSessionManager(),
 		inboundRay:     pipe,
 		inboundRay:     pipe,
-		ctx:            ctx,
-		cancel:         cancel,
+		done:           signal.NewDone(),
 		manager:        m,
 		manager:        m,
 		concurrency:    m.config.Concurrency,
 		concurrency:    m.config.Concurrency,
 	}
 	}
+
+	go func() {
+		if err := p.Process(ctx, pipe, dialer); err != nil {
+			errors.New("failed to handler mux client connection").Base(err).WriteToLog()
+		}
+		c.done.Close()
+		cancel()
+	}()
+
 	go c.fetchOutput()
 	go c.fetchOutput()
 	go c.monitor()
 	go c.monitor()
 	return c, nil
 	return c, nil
@@ -113,12 +112,7 @@ func NewClient(p proxy.Outbound, dialer proxy.Dialer, m *ClientManager) (*Client
 
 
 // Closed returns true if this Client is closed.
 // Closed returns true if this Client is closed.
 func (m *Client) Closed() bool {
 func (m *Client) Closed() bool {
-	select {
-	case <-m.ctx.Done():
-		return true
-	default:
-		return false
-	}
+	return m.done.Done()
 }
 }
 
 
 func (m *Client) monitor() {
 func (m *Client) monitor() {
@@ -129,7 +123,7 @@ func (m *Client) monitor() {
 
 
 	for {
 	for {
 		select {
 		select {
-		case <-m.ctx.Done():
+		case <-m.done.C():
 			m.sessionManager.Close()
 			m.sessionManager.Close()
 			m.inboundRay.InboundInput().Close()
 			m.inboundRay.InboundInput().Close()
 			m.inboundRay.InboundOutput().CloseError()
 			m.inboundRay.InboundOutput().CloseError()
@@ -137,7 +131,8 @@ func (m *Client) monitor() {
 		case <-timer.C:
 		case <-timer.C:
 			size := m.sessionManager.Size()
 			size := m.sessionManager.Size()
 			if size == 0 && m.sessionManager.CloseIfNoSession() {
 			if size == 0 && m.sessionManager.CloseIfNoSession() {
-				m.cancel()
+				m.done.Close()
+				return
 			}
 			}
 		}
 		}
 	}
 	}
@@ -171,10 +166,8 @@ func (m *Client) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) bool
 		return false
 		return false
 	}
 	}
 
 
-	select {
-	case <-m.ctx.Done():
+	if m.done.Done() {
 		return false
 		return false
-	default:
 	}
 	}
 
 
 	s := sm.Allocate()
 	s := sm.Allocate()
@@ -227,7 +220,7 @@ func (m *Client) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader
 }
 }
 
 
 func (m *Client) fetchOutput() {
 func (m *Client) fetchOutput() {
-	defer m.cancel()
+	defer m.done.Close()
 
 
 	reader := buf.NewBufferedReader(m.inboundRay.InboundOutput())
 	reader := buf.NewBufferedReader(m.inboundRay.InboundOutput())
 
 
@@ -262,21 +255,14 @@ func (m *Client) fetchOutput() {
 }
 }
 
 
 type Server struct {
 type Server struct {
-	dispatcher dispatcher.Interface
+	dispatcher core.Dispatcher
 }
 }
 
 
 // NewServer creates a new mux.Server.
 // NewServer creates a new mux.Server.
 func NewServer(ctx context.Context) *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
 	return s
 }
 }
 
 
@@ -295,8 +281,16 @@ func (s *Server) Dispatch(ctx context.Context, dest net.Destination) (ray.Inboun
 	return ray, nil
 	return ray, nil
 }
 }
 
 
+func (s *Server) Start() error {
+	return nil
+}
+
+func (s *Server) Close() error {
+	return nil
+}
+
 type ServerWorker struct {
 type ServerWorker struct {
-	dispatcher     dispatcher.Interface
+	dispatcher     core.Dispatcher
 	outboundRay    ray.OutboundRay
 	outboundRay    ray.OutboundRay
 	sessionManager *SessionManager
 	sessionManager *SessionManager
 }
 }

+ 5 - 3
app/proxyman/mux/session.go

@@ -103,12 +103,12 @@ func (m *SessionManager) CloseIfNoSession() bool {
 	return true
 	return true
 }
 }
 
 
-func (m *SessionManager) Close() {
+func (m *SessionManager) Close() error {
 	m.Lock()
 	m.Lock()
 	defer m.Unlock()
 	defer m.Unlock()
 
 
 	if m.closed {
 	if m.closed {
-		return
+		return nil
 	}
 	}
 
 
 	m.closed = true
 	m.closed = true
@@ -119,6 +119,7 @@ func (m *SessionManager) Close() {
 	}
 	}
 
 
 	m.sessions = nil
 	m.sessions = nil
+	return nil
 }
 }
 
 
 // Session represents a client connection in a Mux connection.
 // Session represents a client connection in a Mux connection.
@@ -131,10 +132,11 @@ type Session struct {
 }
 }
 
 
 // Close closes all resources associated with this session.
 // Close closes all resources associated with this session.
-func (s *Session) Close() {
+func (s *Session) Close() error {
 	s.output.Close()
 	s.output.Close()
 	s.input.Close()
 	s.input.Close()
 	s.parent.Remove(s.ID)
 	s.parent.Remove(s.ID)
+	return nil
 }
 }
 
 
 // NewReader creates a buf.Reader based on the transfer type of this Session.
 // NewReader creates a buf.Reader based on the transfer type of this Session.

+ 2 - 1
app/proxyman/mux/writer.go

@@ -100,7 +100,7 @@ func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
 	return nil
 	return nil
 }
 }
 
 
-func (w *Writer) Close() {
+func (w *Writer) Close() error {
 	meta := FrameMetadata{
 	meta := FrameMetadata{
 		SessionID:     w.id,
 		SessionID:     w.id,
 		SessionStatus: SessionStatusEnd,
 		SessionStatus: SessionStatusEnd,
@@ -110,4 +110,5 @@ func (w *Writer) Close() {
 	common.Must(frame.Reset(meta.AsSupplier()))
 	common.Must(frame.Reset(meta.AsSupplier()))
 
 
 	w.writer.WriteMultiBuffer(buf.NewMultiBufferValue(frame))
 	w.writer.WriteMultiBuffer(buf.NewMultiBufferValue(frame))
+	return nil
 }
 }

+ 36 - 109
app/proxyman/outbound/handler.go

@@ -3,12 +3,11 @@ package outbound
 import (
 import (
 	"context"
 	"context"
 	"io"
 	"io"
-	"time"
 
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/app/proxyman/mux"
-	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
@@ -17,29 +16,22 @@ import (
 )
 )
 
 
 type Handler struct {
 type Handler struct {
-	config          *proxyman.OutboundHandlerConfig
+	config          *core.OutboundHandlerConfig
 	senderSettings  *proxyman.SenderConfig
 	senderSettings  *proxyman.SenderConfig
 	proxy           proxy.Outbound
 	proxy           proxy.Outbound
-	outboundManager proxyman.OutboundHandlerManager
+	outboundManager core.OutboundHandlerManager
 	mux             *mux.ClientManager
 	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 {
 	if config.SenderSettings != nil {
 		senderSettings, err := config.SenderSettings.GetInstance()
 		senderSettings, err := config.SenderSettings.GetInstance()
@@ -54,11 +46,21 @@ 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
+	}
+
+	rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	proxyHandler, ok := rawProxyHandler.(proxy.Outbound)
+	if !ok {
+		return nil, newError("not an outbound handler")
+	}
+
 	if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil && h.senderSettings.MultiplexSettings.Enabled {
 	if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil && h.senderSettings.MultiplexSettings.Enabled {
 		config := h.senderSettings.MultiplexSettings
 		config := h.senderSettings.MultiplexSettings
 		if config.Concurrency < 1 || config.Concurrency > 1024 {
 		if config.Concurrency < 1 || config.Concurrency > 1024 {
@@ -71,6 +73,11 @@ func NewHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) (*H
 	return h, nil
 	return h, nil
 }
 }
 
 
+// Tag implements core.OutboundHandler.
+func (h *Handler) Tag() string {
+	return h.config.Tag
+}
+
 // Dispatch implements proxy.Outbound.Dispatch.
 // Dispatch implements proxy.Outbound.Dispatch.
 func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
 func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
 	if h.mux != nil {
 	if h.mux != nil {
@@ -103,7 +110,7 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
 				ctx = proxy.ContextWithTarget(ctx, dest)
 				ctx = proxy.ContextWithTarget(ctx, dest)
 				stream := ray.NewRay(ctx)
 				stream := ray.NewRay(ctx)
 				go handler.Dispatch(ctx, stream)
 				go handler.Dispatch(ctx, stream)
-				return NewConnection(stream), nil
+				return ray.NewConnection(stream.InboundOutput(), stream.InboundInput()), nil
 			}
 			}
 
 
 			newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog()
 			newError("failed to get outbound handler with tag: ", tag).AtWarning().WriteToLog()
@@ -121,98 +128,18 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
 	return internet.Dial(ctx, dest)
 	return internet.Dial(ctx, dest)
 }
 }
 
 
-var (
-	_ buf.Reader = (*Connection)(nil)
-	_ buf.Writer = (*Connection)(nil)
-)
-
-type Connection struct {
-	stream     ray.Ray
-	closed     bool
-	localAddr  net.Addr
-	remoteAddr net.Addr
-
-	reader *buf.BufferedReader
-	writer buf.Writer
-}
-
-func NewConnection(stream ray.Ray) *Connection {
-	return &Connection{
-		stream: stream,
-		localAddr: &net.TCPAddr{
-			IP:   []byte{0, 0, 0, 0},
-			Port: 0,
-		},
-		remoteAddr: &net.TCPAddr{
-			IP:   []byte{0, 0, 0, 0},
-			Port: 0,
-		},
-		reader: buf.NewBufferedReader(stream.InboundOutput()),
-		writer: stream.InboundInput(),
-	}
-}
-
-// Read implements net.Conn.Read().
-func (v *Connection) Read(b []byte) (int, error) {
-	if v.closed {
-		return 0, io.EOF
-	}
-	return v.reader.Read(b)
-}
-
-func (v *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
-	return v.reader.ReadMultiBuffer()
-}
-
-// Write implements net.Conn.Write().
-func (v *Connection) Write(b []byte) (int, error) {
-	if v.closed {
-		return 0, io.ErrClosedPipe
-	}
-
-	l := len(b)
-	mb := buf.NewMultiBufferCap(l/buf.Size + 1)
-	mb.Write(b)
-	return l, v.writer.WriteMultiBuffer(mb)
-}
-
-func (v *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
-	if v.closed {
-		return io.ErrClosedPipe
-	}
-
-	return v.writer.WriteMultiBuffer(mb)
-}
-
-// Close implements net.Conn.Close().
-func (v *Connection) Close() error {
-	v.closed = true
-	v.stream.InboundInput().Close()
-	v.stream.InboundOutput().CloseError()
-	return nil
-}
-
-// LocalAddr implements net.Conn.LocalAddr().
-func (v *Connection) LocalAddr() net.Addr {
-	return v.localAddr
-}
-
-// RemoteAddr implements net.Conn.RemoteAddr().
-func (v *Connection) RemoteAddr() net.Addr {
-	return v.remoteAddr
-}
-
-// SetDeadline implements net.Conn.SetDeadline().
-func (v *Connection) SetDeadline(t time.Time) error {
-	return nil
+// GetOutbound implements proxy.GetOutbound.
+func (h *Handler) GetOutbound() proxy.Outbound {
+	return h.proxy
 }
 }
 
 
-// SetReadDeadline implements net.Conn.SetReadDeadline().
-func (v *Connection) SetReadDeadline(t time.Time) error {
+// Start implements common.Runnable.
+func (h *Handler) Start() error {
 	return nil
 	return nil
 }
 }
 
 
-// SetWriteDeadline implement net.Conn.SetWriteDeadline().
-func (v *Connection) SetWriteDeadline(t time.Time) error {
+// Close implements common.Runnable.
+func (h *Handler) Close() error {
+	common.Close(h.mux)
 	return nil
 	return nil
 }
 }

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

@@ -0,0 +1,16 @@
+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))
+	assert((*Manager)(nil), Implements, (*core.OutboundHandlerManager)(nil))
+}

+ 94 - 28
app/proxyman/outbound/outbound.go

@@ -6,67 +6,130 @@ import (
 	"context"
 	"context"
 	"sync"
 	"sync"
 
 
+	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 )
 )
 
 
 // Manager is to manage all outbound handlers.
 // Manager is to manage all outbound handlers.
 type Manager struct {
 type Manager struct {
-	sync.RWMutex
-	defaultHandler *Handler
-	taggedHandler  map[string]*Handler
+	access           sync.RWMutex
+	defaultHandler   core.OutboundHandler
+	taggedHandler    map[string]core.OutboundHandler
+	untaggedHandlers []core.OutboundHandler
+	running          bool
 }
 }
 
 
 // New creates a new Manager.
 // New creates a new Manager.
 func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) {
 func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) {
-	return &Manager{
-		taggedHandler: make(map[string]*Handler),
-	}, 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
 }
 }
 
 
-// Interface implements Application.Interface.
-func (*Manager) Interface() interface{} {
-	return (*proxyman.OutboundHandlerManager)(nil)
+// Start implements core.Feature
+func (m *Manager) Start() error {
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	m.running = true
+
+	for _, h := range m.taggedHandler {
+		if err := h.Start(); err != nil {
+			return err
+		}
+	}
+
+	for _, h := range m.untaggedHandlers {
+		if err := h.Start(); err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 }
 
 
-// Start implements Application.Start
-func (*Manager) Start() error { return nil }
+// Close implements core.Feature
+func (m *Manager) Close() error {
+	m.access.Lock()
+	defer m.access.Unlock()
 
 
-// Close implements Application.Close
-func (*Manager) Close() {}
+	m.running = false
+
+	for _, h := range m.taggedHandler {
+		h.Close()
+	}
+
+	for _, h := range m.untaggedHandlers {
+		h.Close()
+	}
+
+	return nil
+}
+
+// GetDefaultHandler implements core.OutboundHandlerManager.
+func (m *Manager) GetDefaultHandler() core.OutboundHandler {
+	m.access.RLock()
+	defer m.access.RUnlock()
 
 
-func (m *Manager) GetDefaultHandler() proxyman.OutboundHandler {
-	m.RLock()
-	defer m.RUnlock()
 	if m.defaultHandler == nil {
 	if m.defaultHandler == nil {
 		return nil
 		return nil
 	}
 	}
 	return m.defaultHandler
 	return m.defaultHandler
 }
 }
 
 
-func (m *Manager) GetHandler(tag string) proxyman.OutboundHandler {
-	m.RLock()
-	defer m.RUnlock()
+// GetHandler implements core.OutboundHandlerManager.
+func (m *Manager) GetHandler(tag string) core.OutboundHandler {
+	m.access.RLock()
+	defer m.access.RUnlock()
 	if handler, found := m.taggedHandler[tag]; found {
 	if handler, found := m.taggedHandler[tag]; found {
 		return handler
 		return handler
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (m *Manager) AddHandler(ctx context.Context, config *proxyman.OutboundHandlerConfig) error {
-	m.Lock()
-	defer m.Unlock()
+// AddHandler implements core.OutboundHandlerManager.
+func (m *Manager) AddHandler(ctx context.Context, handler core.OutboundHandler) error {
+	m.access.Lock()
+	defer m.access.Unlock()
 
 
-	handler, err := NewHandler(ctx, config)
-	if err != nil {
-		return err
-	}
 	if m.defaultHandler == nil {
 	if m.defaultHandler == nil {
 		m.defaultHandler = handler
 		m.defaultHandler = handler
 	}
 	}
 
 
-	if len(config.Tag) > 0 {
-		m.taggedHandler[config.Tag] = handler
+	tag := handler.Tag()
+	if len(tag) > 0 {
+		m.taggedHandler[tag] = handler
+	} else {
+		m.untaggedHandlers = append(m.untaggedHandlers, handler)
+	}
+
+	if m.running {
+		return handler.Start()
+	}
+
+	return nil
+}
+
+// RemoveHandler implements core.OutboundHandlerManager.
+func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
+	if len(tag) == 0 {
+		return core.ErrNoClue
+	}
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	delete(m.taggedHandler, tag)
+	if m.defaultHandler.Tag() == tag {
+		m.defaultHandler = nil
 	}
 	}
 
 
 	return nil
 	return nil
@@ -76,4 +139,7 @@ func init() {
 	common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 	common.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		return New(ctx, config.(*proxyman.OutboundConfig))
 		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 (
 import (
 	"context"
 	"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
 type key int
 
 
 const (
 const (

+ 28 - 35
app/router/router.go

@@ -5,46 +5,47 @@ package router
 import (
 import (
 	"context"
 	"context"
 
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
 )
 )
 
 
-var (
-	ErrNoRuleApplicable = newError("No rule applicable")
-)
-
 type Router struct {
 type Router struct {
 	domainStrategy Config_DomainStrategy
 	domainStrategy Config_DomainStrategy
 	rules          []Rule
 	rules          []Rule
+	dns            core.DNSClient
 }
 }
 
 
 func NewRouter(ctx context.Context, config *Config) (*Router, error) {
 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{
 	r := &Router{
 		domainStrategy: config.DomainStrategy,
 		domainStrategy: config.DomainStrategy,
 		rules:          make([]Rule, len(config.Rule)),
 		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
 	return r, nil
 }
 }
 
 
 type ipResolver struct {
 type ipResolver struct {
+	dns      core.DNSClient
 	ip       []net.Address
 	ip       []net.Address
 	domain   string
 	domain   string
 	resolved bool
 	resolved bool
@@ -57,7 +58,7 @@ func (r *ipResolver) Resolve() []net.Address {
 
 
 	newError("looking for IP for domain: ", r.domain).WriteToLog()
 	newError("looking for IP for domain: ", r.domain).WriteToLog()
 	r.resolved = true
 	r.resolved = true
-	ips, err := net.LookupIP(r.domain)
+	ips, err := r.dns.LookupIP(r.domain)
 	if err != nil {
 	if err != nil {
 		newError("failed to get IP address").Base(err).WriteToLog()
 		newError("failed to get IP address").Base(err).WriteToLog()
 	}
 	}
@@ -71,8 +72,10 @@ func (r *ipResolver) Resolve() []net.Address {
 	return r.ip
 	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 r.domainStrategy == Config_IpOnDemand {
 		if dest, ok := proxy.TargetFromContext(ctx); ok && dest.Address.Family().IsDomain() {
 		if dest, ok := proxy.TargetFromContext(ctx); ok && dest.Address.Family().IsDomain() {
 			resolver.domain = dest.Address.Domain()
 			resolver.domain = dest.Address.Domain()
@@ -88,7 +91,7 @@ func (r *Router) TakeDetour(ctx context.Context) (string, error) {
 
 
 	dest, ok := proxy.TargetFromContext(ctx)
 	dest, ok := proxy.TargetFromContext(ctx)
 	if !ok {
 	if !ok {
-		return "", ErrNoRuleApplicable
+		return "", core.ErrNoClue
 	}
 	}
 
 
 	if r.domainStrategy == Config_IpIfNonMatch && dest.Address.Family().IsDomain() {
 	if r.domainStrategy == Config_IpIfNonMatch && dest.Address.Family().IsDomain() {
@@ -104,25 +107,15 @@ 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 {
 func (*Router) Start() error {
 	return nil
 	return nil
 }
 }
 
 
-func (*Router) Close() {}
-
-func FromSpace(space app.Space) *Router {
-	app := space.GetApplication((*Router)(nil))
-	if app == nil {
-		return nil
-	}
-	return app.(*Router)
+func (*Router) Close() error {
+	return nil
 }
 }
 
 
 func init() {
 func init() {

+ 21 - 18
app/router/router_test.go

@@ -4,13 +4,14 @@ import (
 	"context"
 	"context"
 	"testing"
 	"testing"
 
 
-	"v2ray.com/core/app"
+	"v2ray.com/core"
 	"v2ray.com/core/app/dispatcher"
 	"v2ray.com/core/app/dispatcher"
-	_ "v2ray.com/core/app/dispatcher/impl"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	_ "v2ray.com/core/app/proxyman/outbound"
 	_ "v2ray.com/core/app/proxyman/outbound"
 	. "v2ray.com/core/app/router"
 	. "v2ray.com/core/app/router"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/serial"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
 	. "v2ray.com/ext/assert"
 	. "v2ray.com/ext/assert"
 )
 )
@@ -18,28 +19,30 @@ import (
 func TestSimpleRouter(t *testing.T) {
 func TestSimpleRouter(t *testing.T) {
 	assert := With(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(err, IsNil)
 	assert(tag, Equals, "test")
 	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)
-}

+ 59 - 0
clock.go

@@ -0,0 +1,59 @@
+package core
+
+import (
+	"sync"
+	"time"
+)
+
+// Clock is a V2Ray feature that returns current time.
+type Clock interface {
+	Feature
+
+	// Now returns current time.
+	Now() time.Time
+}
+
+type syncClock struct {
+	sync.RWMutex
+	Clock
+}
+
+func (c *syncClock) Now() time.Time {
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.Clock == nil {
+		return time.Now()
+	}
+
+	return c.Clock.Now()
+}
+
+func (c *syncClock) Start() error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.Clock == nil {
+		return nil
+	}
+
+	return c.Clock.Start()
+}
+
+func (c *syncClock) Close() error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.Clock == nil {
+		return nil
+	}
+
+	return c.Clock.Close()
+}
+
+func (c *syncClock) Set(clock Clock) {
+	c.Lock()
+	defer c.Unlock()
+
+	c.Clock = clock
+}

+ 44 - 0
commander.go

@@ -0,0 +1,44 @@
+package core
+
+import (
+	"sync"
+)
+
+// Commander is a feature that accepts commands from external source.
+type Commander interface {
+	Feature
+}
+
+type syncCommander struct {
+	sync.RWMutex
+	Commander
+}
+
+func (c *syncCommander) Start() error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.Commander == nil {
+		return nil
+	}
+
+	return c.Commander.Start()
+}
+
+func (c *syncCommander) Close() error {
+	c.RLock()
+	defer c.RUnlock()
+
+	if c.Commander == nil {
+		return nil
+	}
+
+	return c.Commander.Close()
+}
+
+func (c *syncCommander) Set(commander Commander) {
+	c.Lock()
+	defer c.Unlock()
+
+	c.Commander = commander
+}

+ 0 - 46
common/event/event.go

@@ -1,46 +0,0 @@
-package event
-
-import "sync"
-
-type Event uint16
-
-type Handler func(data interface{}) error
-
-type Registry interface {
-	On(Event, Handler)
-}
-
-type Listener struct {
-	sync.RWMutex
-	events map[Event][]Handler
-}
-
-func (l *Listener) On(e Event, h Handler) {
-	l.Lock()
-	defer l.Unlock()
-
-	if l.events == nil {
-		l.events = make(map[Event][]Handler)
-	}
-
-	handlers := l.events[e]
-	handlers = append(handlers, h)
-	l.events[e] = handlers
-}
-
-func (l *Listener) Fire(e Event, data interface{}) error {
-	l.RLock()
-	defer l.RUnlock()
-
-	if l.events == nil {
-		return nil
-	}
-
-	for _, h := range l.events[e] {
-		if err := h(data); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}

+ 23 - 0
common/interfaces.go

@@ -0,0 +1,23 @@
+package common
+
+// Closable is the interface for objects that can release its resources.
+type Closable interface {
+	// Close release all resources used by this object, including goroutines.
+	Close() error
+}
+
+// Close closes the obj if it is a Closable.
+func Close(obj interface{}) error {
+	if c, ok := obj.(Closable); ok {
+		return c.Close()
+	}
+	return nil
+}
+
+// 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
+
+	Closable
+}

+ 25 - 6
common/log/log.go

@@ -1,8 +1,7 @@
 package log
 package log
 
 
 import (
 import (
-	"sync/atomic"
-	"unsafe"
+	"sync"
 
 
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/serial"
 )
 )
@@ -30,12 +29,11 @@ func (m *GeneralMessage) String() string {
 
 
 // Record writes a message into log stream.
 // Record writes a message into log stream.
 func Record(msg Message) {
 func Record(msg Message) {
-	h := (*Handler)(atomic.LoadPointer(&logHandler))
-	(*h).Handle(msg)
+	logHandler.Handle(msg)
 }
 }
 
 
 var (
 var (
-	logHandler unsafe.Pointer
+	logHandler syncHandler
 )
 )
 
 
 // RegisterHandler register a new handler as current log handler. Previous registered handler will be discarded.
 // RegisterHandler register a new handler as current log handler. Previous registered handler will be discarded.
@@ -43,5 +41,26 @@ func RegisterHandler(handler Handler) {
 	if handler == nil {
 	if handler == nil {
 		panic("Log handler is nil")
 		panic("Log handler is nil")
 	}
 	}
-	atomic.StorePointer(&logHandler, unsafe.Pointer(&handler))
+	logHandler.Set(handler)
+}
+
+type syncHandler struct {
+	sync.RWMutex
+	Handler
+}
+
+func (h *syncHandler) Handle(msg Message) {
+	h.RLock()
+	defer h.RUnlock()
+
+	if h.Handler != nil {
+		h.Handler.Handle(msg)
+	}
+}
+
+func (h *syncHandler) Set(handler Handler) {
+	h.Lock()
+	defer h.Unlock()
+
+	h.Handler = handler
 }
 }

+ 0 - 44
common/net/dns.go

@@ -1,44 +0,0 @@
-package net
-
-import (
-	"net"
-	"sync/atomic"
-	"unsafe"
-)
-
-// IPResolver is the interface to resolve host name to IPs.
-type IPResolver interface {
-	LookupIP(host string) ([]net.IP, error)
-}
-
-type systemIPResolver int
-
-func (s systemIPResolver) LookupIP(host string) ([]net.IP, error) {
-	return net.LookupIP(host)
-}
-
-const (
-	systemIPResolverInstance = systemIPResolver(0)
-)
-
-// SystemIPResolver returns an IPResolver that resolves IP through underlying system.
-func SystemIPResolver() IPResolver {
-	return systemIPResolverInstance
-}
-
-var (
-	ipResolver unsafe.Pointer
-)
-
-func LookupIP(host string) ([]net.IP, error) {
-	r := (*IPResolver)(atomic.LoadPointer(&ipResolver))
-	return (*r).LookupIP(host)
-}
-
-func RegisterIPResolver(resolver IPResolver) {
-	atomic.StorePointer(&ipResolver, unsafe.Pointer(&resolver))
-}
-
-func init() {
-	RegisterIPResolver(systemIPResolverInstance)
-}

+ 2 - 0
common/net/system.go

@@ -11,6 +11,8 @@ var Listen = net.Listen
 var ListenTCP = net.ListenTCP
 var ListenTCP = net.ListenTCP
 var ListenUDP = net.ListenUDP
 var ListenUDP = net.ListenUDP
 
 
+var LookupIP = net.LookupIP
+
 var FileConn = net.FileConn
 var FileConn = net.FileConn
 
 
 var ParseIP = net.ParseIP
 var ParseIP = net.ParseIP

+ 7 - 4
common/protocol/headers.go

@@ -18,11 +18,14 @@ const (
 )
 )
 
 
 func (c RequestCommand) TransferType() TransferType {
 func (c RequestCommand) TransferType() TransferType {
-	if c == RequestCommandTCP {
+	switch c {
+	case RequestCommandTCP, RequestCommandMux:
+		return TransferTypeStream
+	case RequestCommandUDP:
+		return TransferTypePacket
+	default:
 		return TransferTypeStream
 		return TransferTypeStream
 	}
 	}
-
-	return TransferTypePacket
 }
 }
 
 
 const (
 const (
@@ -79,7 +82,7 @@ type ResponseHeader struct {
 type CommandSwitchAccount struct {
 type CommandSwitchAccount struct {
 	Host     net.Address
 	Host     net.Address
 	Port     net.Port
 	Port     net.Port
-	ID       *uuid.UUID
+	ID       uuid.UUID
 	Level    uint32
 	Level    uint32
 	AlterIds uint16
 	AlterIds uint16
 	ValidMin byte
 	ValidMin byte

+ 4 - 4
common/protocol/id.go

@@ -21,13 +21,13 @@ func DefaultIDHash(key []byte) hash.Hash {
 
 
 // The ID of en entity, in the form of an UUID.
 // The ID of en entity, in the form of an UUID.
 type ID struct {
 type ID struct {
-	uuid   *uuid.UUID
+	uuid   uuid.UUID
 	cmdKey [IDBytesLen]byte
 	cmdKey [IDBytesLen]byte
 }
 }
 
 
 // Equals returns true if this ID equals to the other one.
 // Equals returns true if this ID equals to the other one.
 func (id *ID) Equals(another *ID) bool {
 func (id *ID) Equals(another *ID) bool {
-	return id.uuid.Equals(another.uuid)
+	return id.uuid.Equals(&(another.uuid))
 }
 }
 
 
 func (id *ID) Bytes() []byte {
 func (id *ID) Bytes() []byte {
@@ -38,7 +38,7 @@ func (id *ID) String() string {
 	return id.uuid.String()
 	return id.uuid.String()
 }
 }
 
 
-func (id *ID) UUID() *uuid.UUID {
+func (id *ID) UUID() uuid.UUID {
 	return id.uuid
 	return id.uuid
 }
 }
 
 
@@ -47,7 +47,7 @@ func (id ID) CmdKey() []byte {
 }
 }
 
 
 // NewID returns an ID with given UUID.
 // NewID returns an ID with given UUID.
-func NewID(uuid *uuid.UUID) *ID {
+func NewID(uuid uuid.UUID) *ID {
 	id := &ID{uuid: uuid}
 	id := &ID{uuid: uuid}
 	md5hash := md5.New()
 	md5hash := md5.New()
 	common.Must2(md5hash.Write(uuid.Bytes()))
 	common.Must2(md5hash.Write(uuid.Bytes()))

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

@@ -14,7 +14,7 @@ var _ = math.Inf
 type User struct {
 type User struct {
 	Level uint32 `protobuf:"varint,1,opt,name=level" json:"level,omitempty"`
 	Level uint32 `protobuf:"varint,1,opt,name=level" json:"level,omitempty"`
 	Email string `protobuf:"bytes,2,opt,name=email" json:"email,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"`
 	Account *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=account" json:"account,omitempty"`
 }
 }
 
 

+ 1 - 0
common/protocol/user_validator.go

@@ -3,4 +3,5 @@ package protocol
 type UserValidator interface {
 type UserValidator interface {
 	Add(user *User) error
 	Add(user *User) error
 	Get(timeHash []byte) (*User, Timestamp, bool)
 	Get(timeHash []byte) (*User, Timestamp, bool)
+	Remove(email string) bool
 }
 }

+ 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)
+}

+ 48 - 0
common/signal/done.go

@@ -0,0 +1,48 @@
+package signal
+
+import (
+	"sync"
+)
+
+type Done struct {
+	access sync.Mutex
+	c      chan struct{}
+	closed bool
+}
+
+func NewDone() *Done {
+	return &Done{
+		c: make(chan struct{}),
+	}
+}
+
+func (d *Done) Done() bool {
+	select {
+	case <-d.c:
+		return true
+	default:
+		return false
+	}
+}
+
+func (d *Done) C() chan struct{} {
+	return d.c
+}
+
+func (d *Done) Wait() {
+	<-d.c
+}
+
+func (d *Done) Close() error {
+	d.access.Lock()
+	defer d.access.Unlock()
+
+	if d.closed {
+		return nil
+	}
+
+	d.closed = true
+	close(d.c)
+
+	return nil
+}

+ 4 - 4
common/signal/notifier.go

@@ -1,22 +1,22 @@
 package signal
 package signal
 
 
 type Notifier struct {
 type Notifier struct {
-	c chan bool
+	c chan struct{}
 }
 }
 
 
 func NewNotifier() *Notifier {
 func NewNotifier() *Notifier {
 	return &Notifier{
 	return &Notifier{
-		c: make(chan bool, 1),
+		c: make(chan struct{}, 1),
 	}
 	}
 }
 }
 
 
 func (n *Notifier) Signal() {
 func (n *Notifier) Signal() {
 	select {
 	select {
-	case n.c <- true:
+	case n.c <- struct{}{}:
 	default:
 	default:
 	}
 	}
 }
 }
 
 
-func (n *Notifier) Wait() <-chan bool {
+func (n *Notifier) Wait() <-chan struct{} {
 	return n.c
 	return n.c
 }
 }

+ 5 - 5
common/signal/semaphore.go

@@ -1,23 +1,23 @@
 package signal
 package signal
 
 
 type Semaphore struct {
 type Semaphore struct {
-	token chan bool
+	token chan struct{}
 }
 }
 
 
 func NewSemaphore(n int) *Semaphore {
 func NewSemaphore(n int) *Semaphore {
 	s := &Semaphore{
 	s := &Semaphore{
-		token: make(chan bool, n),
+		token: make(chan struct{}, n),
 	}
 	}
 	for i := 0; i < n; i++ {
 	for i := 0; i < n; i++ {
-		s.token <- true
+		s.token <- struct{}{}
 	}
 	}
 	return s
 	return s
 }
 }
 
 
-func (s *Semaphore) Wait() <-chan bool {
+func (s *Semaphore) Wait() <-chan struct{} {
 	return s.token
 	return s.token
 }
 }
 
 
 func (s *Semaphore) Signal() {
 func (s *Semaphore) Signal() {
-	s.token <- true
+	s.token <- struct{}{}
 }
 }

+ 60 - 0
common/signal/task.go

@@ -0,0 +1,60 @@
+package signal
+
+import (
+	"sync"
+	"time"
+)
+
+type PeriodicTask struct {
+	Interval time.Duration
+	Execute  func() error
+
+	access sync.Mutex
+	timer  *time.Timer
+	closed bool
+}
+
+func (t *PeriodicTask) checkedExecute() error {
+	t.access.Lock()
+	defer t.access.Unlock()
+
+	if t.closed {
+		return nil
+	}
+
+	if err := t.Execute(); err != nil {
+		return err
+	}
+
+	t.timer = time.AfterFunc(t.Interval, func() {
+		t.checkedExecute()
+	})
+
+	return nil
+}
+
+func (t *PeriodicTask) Start() error {
+	t.access.Lock()
+	t.closed = false
+	t.access.Unlock()
+
+	if err := t.checkedExecute(); err != nil {
+		t.closed = true
+		return err
+	}
+
+	return nil
+}
+
+func (t *PeriodicTask) Close() error {
+	t.access.Lock()
+	defer t.access.Unlock()
+
+	t.closed = true
+	if t.timer != nil {
+		t.timer.Stop()
+		t.timer = nil
+	}
+
+	return nil
+}

+ 29 - 0
common/signal/task_test.go

@@ -0,0 +1,29 @@
+package signal_test
+
+import (
+	"testing"
+	"time"
+
+	"v2ray.com/core/common"
+	. "v2ray.com/core/common/signal"
+	. "v2ray.com/ext/assert"
+)
+
+func TestPeriodicTaskStop(t *testing.T) {
+	assert := With(t)
+
+	value := 0
+	task := &PeriodicTask{
+		Interval: time.Second * 2,
+		Execute: func() error {
+			value++
+			return nil
+		},
+	}
+	common.Must(task.Start())
+	time.Sleep(time.Second * 5)
+	common.Must(task.Close())
+	assert(value, Equals, 3)
+	time.Sleep(time.Second * 4)
+	assert(value, Equals, 3)
+}

+ 13 - 5
common/signal/timer.go

@@ -10,23 +10,30 @@ type ActivityUpdater interface {
 }
 }
 
 
 type ActivityTimer struct {
 type ActivityTimer struct {
-	updated chan bool
+	updated chan struct{}
 	timeout chan time.Duration
 	timeout chan time.Duration
+	closing chan struct{}
 }
 }
 
 
 func (t *ActivityTimer) Update() {
 func (t *ActivityTimer) Update() {
 	select {
 	select {
-	case t.updated <- true:
+	case t.updated <- struct{}{}:
 	default:
 	default:
 	}
 	}
 }
 }
 
 
 func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
 func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
-	t.timeout <- timeout
+	select {
+	case <-t.closing:
+	case t.timeout <- timeout:
+	}
 }
 }
 
 
 func (t *ActivityTimer) run(ctx context.Context, cancel context.CancelFunc) {
 func (t *ActivityTimer) run(ctx context.Context, cancel context.CancelFunc) {
-	defer cancel()
+	defer func() {
+		cancel()
+		close(t.closing)
+	}()
 
 
 	timeout := <-t.timeout
 	timeout := <-t.timeout
 	if timeout == 0 {
 	if timeout == 0 {
@@ -65,7 +72,8 @@ func (t *ActivityTimer) run(ctx context.Context, cancel context.CancelFunc) {
 func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
 func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
 	timer := &ActivityTimer{
 	timer := &ActivityTimer{
 		timeout: make(chan time.Duration, 1),
 		timeout: make(chan time.Duration, 1),
-		updated: make(chan bool, 1),
+		updated: make(chan struct{}, 1),
+		closing: make(chan struct{}),
 	}
 	}
 	timer.timeout <- timeout
 	timer.timeout <- timeout
 	go timer.run(ctx, cancel)
 	go timer.run(ctx, cancel)

+ 12 - 0
common/signal/timer_test.go

@@ -32,3 +32,15 @@ func TestActivityTimerUpdate(t *testing.T) {
 	assert(ctx.Err(), IsNotNil)
 	assert(ctx.Err(), IsNotNil)
 	runtime.KeepAlive(timer)
 	runtime.KeepAlive(timer)
 }
 }
+
+func TestActivityTimerNonBlocking(t *testing.T) {
+	assert := With(t)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	timer := CancelAfterInactivity(ctx, cancel, 0)
+	time.Sleep(time.Second * 1)
+	assert(ctx, HasDone)
+	timer.SetTimeout(0)
+	timer.SetTimeout(1)
+	timer.SetTimeout(2)
+}

+ 14 - 12
common/uuid/uuid.go

@@ -6,6 +6,7 @@ import (
 	"crypto/rand"
 	"crypto/rand"
 	"encoding/hex"
 	"encoding/hex"
 
 
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/errors"
 )
 )
 
 
@@ -46,11 +47,11 @@ func (u *UUID) Equals(another *UUID) bool {
 }
 }
 
 
 // Next generates a deterministic random UUID based on this UUID.
 // Next generates a deterministic random UUID based on this UUID.
-func (u *UUID) Next() *UUID {
+func (u *UUID) Next() UUID {
 	md5hash := md5.New()
 	md5hash := md5.New()
 	md5hash.Write(u.Bytes())
 	md5hash.Write(u.Bytes())
 	md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))
 	md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))
-	newid := new(UUID)
+	var newid UUID
 	for {
 	for {
 		md5hash.Sum(newid[:0])
 		md5hash.Sum(newid[:0])
 		if !newid.Equals(u) {
 		if !newid.Equals(u) {
@@ -61,30 +62,31 @@ func (u *UUID) Next() *UUID {
 }
 }
 
 
 // New creates an UUID with random value.
 // New creates an UUID with random value.
-func New() *UUID {
-	uuid := new(UUID)
-	rand.Read(uuid.Bytes())
+func New() UUID {
+	var uuid UUID
+	common.Must2(rand.Read(uuid.Bytes()))
 	return uuid
 	return uuid
 }
 }
 
 
 // ParseBytes converts an UUID in byte form to object.
 // ParseBytes converts an UUID in byte form to object.
-func ParseBytes(b []byte) (*UUID, error) {
+func ParseBytes(b []byte) (UUID, error) {
+	var uuid UUID
 	if len(b) != 16 {
 	if len(b) != 16 {
-		return nil, errors.New("invalid UUID: ", b)
+		return uuid, errors.New("invalid UUID: ", b)
 	}
 	}
-	uuid := new(UUID)
 	copy(uuid[:], b)
 	copy(uuid[:], b)
 	return uuid, nil
 	return uuid, nil
 }
 }
 
 
 // ParseString converts an UUID in string form to object.
 // ParseString converts an UUID in string form to object.
-func ParseString(str string) (*UUID, error) {
+func ParseString(str string) (UUID, error) {
+	var uuid UUID
+
 	text := []byte(str)
 	text := []byte(str)
 	if len(text) < 32 {
 	if len(text) < 32 {
-		return nil, errors.New("invalid UUID: ", str)
+		return uuid, errors.New("invalid UUID: ", str)
 	}
 	}
 
 
-	uuid := new(UUID)
 	b := uuid.Bytes()
 	b := uuid.Bytes()
 
 
 	for _, byteGroup := range byteGroups {
 	for _, byteGroup := range byteGroups {
@@ -95,7 +97,7 @@ func ParseString(str string) (*UUID, error) {
 		_, err := hex.Decode(b[:byteGroup/2], text[:byteGroup])
 		_, err := hex.Decode(b[:byteGroup/2], text[:byteGroup])
 
 
 		if err != nil {
 		if err != nil {
-			return nil, err
+			return uuid, err
 		}
 		}
 
 
 		text = text[byteGroup:]
 		text = text[byteGroup:]

+ 4 - 2
common/uuid/uuid_test.go

@@ -65,7 +65,9 @@ func TestEquals(t *testing.T) {
 	var uuid *UUID = nil
 	var uuid *UUID = nil
 	var uuid2 *UUID = nil
 	var uuid2 *UUID = nil
 	assert(uuid.Equals(uuid2), IsTrue)
 	assert(uuid.Equals(uuid2), IsTrue)
-	assert(uuid.Equals(New()), IsFalse)
+
+	uuid3 := New()
+	assert(uuid.Equals(&uuid3), IsFalse)
 }
 }
 
 
 func TestNext(t *testing.T) {
 func TestNext(t *testing.T) {
@@ -73,5 +75,5 @@ func TestNext(t *testing.T) {
 
 
 	uuid := New()
 	uuid := New()
 	uuid2 := uuid.Next()
 	uuid2 := uuid.Next()
-	assert(uuid.Equals(uuid2), IsFalse)
+	assert(uuid.Equals(&uuid2), IsFalse)
 }
 }

+ 124 - 28
config.pb.go

@@ -3,7 +3,6 @@ package core
 import proto "github.com/golang/protobuf/proto"
 import proto "github.com/golang/protobuf/proto"
 import fmt "fmt"
 import fmt "fmt"
 import math "math"
 import math "math"
-import v2ray_core_app_proxyman "v2ray.com/core/app/proxyman"
 import v2ray_core_common_serial "v2ray.com/core/common/serial"
 import v2ray_core_common_serial "v2ray.com/core/common/serial"
 import v2ray_core_transport "v2ray.com/core/transport"
 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} }
 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 {
 type Config struct {
 	// Inbound handler configurations. Must have at least one item.
 	// 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 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 configuration. Must be one in the app directory.
 	App []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=app" json:"app,omitempty"`
 	App []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=app" json:"app,omitempty"`
 	// Transport settings.
 	// Transport settings.
@@ -60,14 +59,14 @@ func (m *Config) String() string            { return proto.CompactTextString(m)
 func (*Config) ProtoMessage()               {}
 func (*Config) ProtoMessage()               {}
 func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 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 {
 	if m != nil {
 		return m.Inbound
 		return m.Inbound
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (m *Config) GetOutbound() []*v2ray_core_app_proxyman.OutboundHandlerConfig {
+func (m *Config) GetOutbound() []*OutboundHandlerConfig {
 	if m != nil {
 	if m != nil {
 		return m.Outbound
 		return m.Outbound
 	}
 	}
@@ -95,34 +94,131 @@ func (m *Config) GetExtension() []*v2ray_core_common_serial.TypedMessage {
 	return nil
 	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() {
 func init() {
 	proto.RegisterType((*Config)(nil), "v2ray.core.Config")
 	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)
 	proto.RegisterEnum("v2ray.core.ConfigFormat", ConfigFormat_name, ConfigFormat_value)
 }
 }
 
 
 func init() { proto.RegisterFile("v2ray.com/core/config.proto", fileDescriptor0) }
 func init() { proto.RegisterFile("v2ray.com/core/config.proto", fileDescriptor0) }
 
 
 var fileDescriptor0 = []byte{
 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_package = "com.v2ray.core";
 option java_multiple_files = true;
 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/common/serial/typed_message.proto";
 import "v2ray.com/core/transport/config.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.
 // Master config of V2Ray. V2Ray takes this config as input and functions accordingly.
 message Config {
 message Config {
   // Inbound handler configurations. Must have at least one item.
   // 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.
   // 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;
   reserved 3;
 
 
@@ -36,3 +35,25 @@ message Config {
   // V2Ray will ignore such config during initialization.
   // V2Ray will ignore such config during initialization.
   repeated v2ray.core.common.serial.TypedMessage extension = 6;
   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
+}

+ 1 - 1
core.go

@@ -18,7 +18,7 @@ import (
 )
 )
 
 
 var (
 var (
-	version  = "3.6"
+	version  = "3.8"
 	build    = "Custom"
 	build    = "Custom"
 	codename = "die Commanderin"
 	codename = "die Commanderin"
 	intro    = "An unified platform for anti-censorship."
 	intro    = "An unified platform for anti-censorship."

+ 20 - 0
dial.go

@@ -0,0 +1,20 @@
+package core
+
+import (
+	"context"
+
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/transport/ray"
+)
+
+// Dial provides an easy way for upstream caller to create net.Conn through V2Ray.
+// It dispatches the request to the given destination by the given V2Ray instance.
+// Since it is under a proxy context, the LocalAddr() and RemoteAddr() in returned net.Conn
+// will not show real addresses being used for communication.
+func Dial(ctx context.Context, v *Instance, dest net.Destination) (net.Conn, error) {
+	r, err := v.Dispatcher().Dispatch(ctx, dest)
+	if err != nil {
+		return nil, err
+	}
+	return ray.NewConnection(r.InboundOutput(), r.InboundInput()), nil
+}

+ 59 - 0
dns.go

@@ -0,0 +1,59 @@
+package core
+
+import (
+	"net"
+	"sync"
+
+	"v2ray.com/core/common"
+)
+
+// 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() error {
+	d.RLock()
+	defer d.RUnlock()
+
+	return common.Close(d.DNSClient)
+}
+
+func (d *syncDNSClient) Set(client DNSClient) {
+	if client == nil {
+		return
+	}
+
+	d.Lock()
+	defer d.Unlock()
+
+	d.DNSClient = client
+}

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

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

+ 168 - 0
network.go

@@ -0,0 +1,168 @@
+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 {
+	common.Runnable
+	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
+
+	// RemoveHandler removes a handler from InboundHandlerManager.
+	RemoveHandler(ctx context.Context, tag string) 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() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	return common.Close(m.InboundHandlerManager)
+}
+
+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
+
+	// RemoveHandler removes a handler from OutboundHandlerManager.
+	RemoveHandler(ctx context.Context, tag string) 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() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	return common.Close(m.OutboundHandlerManager)
+}
+
+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"
+
+	"v2ray.com/core/common"
+)
+
+// 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() error {
+	m.RLock()
+	defer m.RUnlock()
+
+	return common.Close(m.PolicyManager)
+}
+
+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 (
 import (
 	"context"
 	"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"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
@@ -18,36 +17,29 @@ import (
 )
 )
 
 
 type DokodemoDoor struct {
 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) {
 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 {
 	if config.NetworkList == nil || config.NetworkList.Size() == 0 {
 		return nil, newError("no network specified")
 		return nil, newError("no network specified")
 	}
 	}
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
+	}
+
 	d := &DokodemoDoor{
 	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
 	return d, nil
 }
 }
 
 
@@ -55,7 +47,16 @@ func (d *DokodemoDoor) Network() net.NetworkList {
 	return *(d.config.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()
 	newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog()
 	dest := net.Destination{
 	dest := net.Destination{
 		Network: network,
 		Network: network,
@@ -72,7 +73,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in
 	}
 	}
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	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)
 	inboundRay, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 	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)
 			return newError("failed to transport request").Base(err)
 		}
 		}
 
 
-		timer.SetTimeout(d.policy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(d.policy().Timeouts.DownlinkOnly)
 
 
 		return nil
 		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)
 			return newError("failed to transport response").Base(err)
 		}
 		}
 
 
-		timer.SetTimeout(d.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(d.policy().Timeouts.UplinkOnly)
 
 
 		return nil
 		return nil
 	})
 	})

+ 28 - 30
proxy/freedom/freedom.go

@@ -4,9 +4,9 @@ package freedom
 
 
 import (
 import (
 	"context"
 	"context"
+	"time"
 
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/dice"
@@ -20,37 +20,35 @@ import (
 
 
 // Handler handles Freedom connections.
 // Handler handles Freedom connections.
 type Handler struct {
 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.
 // New creates a new Freedom handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
 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{
 	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
 	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 {
 func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address {
 	if resolver, ok := proxy.ResolvedIPsFromContext(ctx); ok {
 	if resolver, ok := proxy.ResolvedIPsFromContext(ctx); ok {
 		ips := resolver.Resolve()
 		ips := resolver.Resolve()
@@ -60,7 +58,7 @@ func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address {
 		return ips[dice.Roll(len(ips))]
 		return ips[dice.Roll(len(ips))]
 	}
 	}
 
 
-	ips, err := net.LookupIP(domain)
+	ips, err := h.dns.LookupIP(domain)
 	if err != nil {
 	if err != nil {
 		newError("failed to get IP address for domain ", domain).Base(err).WriteToLog()
 		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.
 // Process implements proxy.Outbound.
 func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dialer proxy.Dialer) error {
 func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dialer proxy.Dialer) error {
 	destination, _ := proxy.TargetFromContext(ctx)
 	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{
 		destination = net.Destination{
 			Network: destination.Network,
 			Network: destination.Network,
 			Address: server.Address.AsAddress(),
 			Address: server.Address.AsAddress(),
@@ -86,7 +84,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	input := outboundRay.OutboundInput()
 	input := outboundRay.OutboundInput()
 	output := outboundRay.OutboundOutput()
 	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())
 		ip := h.resolveIP(ctx, destination.Address.Domain())
 		if ip != nil {
 		if ip != nil {
 			destination = net.Destination{
 			destination = net.Destination{
@@ -113,7 +111,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	defer conn.Close()
 	defer conn.Close()
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	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 {
 	requestDone := signal.ExecuteAsync(func() error {
 		var writer buf.Writer
 		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 {
 		if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to process request").Base(err)
 			return newError("failed to process request").Base(err)
 		}
 		}
-		timer.SetTimeout(h.policy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(h.policy().Timeouts.DownlinkOnly)
 		return nil
 		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 {
 		if err := buf.Copy(v2reader, output, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to process response").Base(err)
 			return newError("failed to process response").Base(err)
 		}
 		}
-		timer.SetTimeout(h.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(h.policy().Timeouts.UplinkOnly)
 		return nil
 		return nil
 	})
 	})
 
 

+ 0 - 33
proxy/handler_cache.go

@@ -1,33 +0,0 @@
-package proxy
-
-import (
-	"context"
-
-	"v2ray.com/core/common"
-)
-
-func CreateInboundHandler(ctx context.Context, config interface{}) (Inbound, error) {
-	handler, err := common.CreateObject(ctx, config)
-	if err != nil {
-		return nil, newError("failed to create inbound handler").Base(err)
-	}
-	switch h := handler.(type) {
-	case Inbound:
-		return h, nil
-	default:
-		return nil, newError("not a InboundHandler")
-	}
-}
-
-func CreateOutboundHandler(ctx context.Context, config interface{}) (Outbound, error) {
-	handler, err := common.CreateObject(ctx, config)
-	if err != nil {
-		return nil, newError("failed to create outbound handler").Base(err)
-	}
-	switch h := handler.(type) {
-	case Outbound:
-		return h, nil
-	default:
-		return nil, newError("not a OutboundHandler")
-	}
-}

+ 23 - 26
proxy/http/server.go

@@ -10,9 +10,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"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"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/errors"
@@ -26,32 +24,31 @@ import (
 // Server is a HTTP proxy server.
 // Server is a HTTP proxy server.
 type Server struct {
 type Server struct {
 	config *ServerConfig
 	config *ServerConfig
-	policy policy.Policy
+	v      *core.Instance
 }
 }
 
 
 // NewServer creates a new HTTP inbound handler.
 // NewServer creates a new HTTP inbound handler.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 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{
 	s := &Server{
 		config: config,
 		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
 	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 {
 func (*Server) Network() net.NetworkList {
 	return net.NetworkList{
 	return net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
 		Network: []net.Network{net.Network_TCP},
@@ -104,11 +101,11 @@ type readerOnly struct {
 	io.Reader
 	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)
 	reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
 
 
 Start:
 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)
 	request, err := http.ReadRequest(reader)
 	if err != nil {
 	if err != nil {
@@ -165,14 +162,14 @@ Start:
 	return err
 	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"))
 	_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
 	if err != nil {
 	if err != nil {
 		return newError("failed to write back OK response").Base(err)
 		return newError("failed to write back OK response").Base(err)
 	}
 	}
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	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)
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -191,7 +188,7 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
 
 
 	requestDone := signal.ExecuteAsync(func() error {
 	requestDone := signal.ExecuteAsync(func() error {
 		defer ray.InboundInput().Close()
 		defer ray.InboundInput().Close()
-		defer timer.SetTimeout(s.policy.Timeout.DownlinkOnly.Duration())
+		defer timer.SetTimeout(s.policy().Timeouts.DownlinkOnly)
 
 
 		v2reader := buf.NewReader(conn)
 		v2reader := buf.NewReader(conn)
 		return buf.Copy(v2reader, ray.InboundInput(), buf.UpdateActivity(timer))
 		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 {
 		if err := buf.Copy(ray.InboundOutput(), v2writer, buf.UpdateActivity(timer)); err != nil {
 			return err
 			return err
 		}
 		}
-		timer.SetTimeout(s.policy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(s.policy().Timeouts.UplinkOnly)
 		return nil
 		return nil
 	})
 	})
 
 
@@ -217,7 +214,7 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade
 
 
 var errWaitAnother = newError("keep alive")
 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 {
 	if !s.config.AllowTransparent && len(request.URL.Host) <= 0 {
 		// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
 		// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.
 		response := &http.Response{
 		response := &http.Response{

+ 20 - 2
proxy/proxy.go

@@ -10,8 +10,9 @@ package proxy
 import (
 import (
 	"context"
 	"context"
 
 
-	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/ray"
 	"v2ray.com/core/transport/ray"
 )
 )
@@ -22,7 +23,7 @@ type Inbound interface {
 	Network() net.NetworkList
 	Network() net.NetworkList
 
 
 	// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
 	// 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.
 // An Outbound process outbound connections.
@@ -36,3 +37,20 @@ type Dialer interface {
 	// Dial dials a system connection to the given destination.
 	// Dial dials a system connection to the given destination.
 	Dial(ctx context.Context, destination net.Destination) (internet.Connection, error)
 	Dial(ctx context.Context, destination net.Destination) (internet.Connection, error)
 }
 }
+
+// UserManager is the interface for Inbounds and Outbounds that can manage their users.
+type UserManager interface {
+	// AddUser adds a new user.
+	AddUser(context.Context, *protocol.User) error
+
+	// RemoveUser removes an user by email.
+	RemoveUser(context.Context, string) error
+}
+
+type GetInbound interface {
+	GetInbound() Inbound
+}
+
+type GetOutbound interface {
+	GetOutbound() Outbound
+}

+ 11 - 20
proxy/shadowsocks/client.go

@@ -3,8 +3,7 @@ package shadowsocks
 import (
 import (
 	"context"
 	"context"
 
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
@@ -18,8 +17,8 @@ import (
 
 
 // Client is a inbound handler for Shadowsocks protocol
 // Client is a inbound handler for Shadowsocks protocol
 type Client struct {
 type Client struct {
-	serverPicker  protocol.ServerPicker
-	policyManager policy.Manager
+	serverPicker protocol.ServerPicker
+	v            *core.Instance
 }
 }
 
 
 // NewClient create a new Shadowsocks client.
 // NewClient create a new Shadowsocks client.
@@ -33,19 +32,11 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
 	}
 	}
 	client := &Client{
 	client := &Client{
 		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
 		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
 	return client, nil
 }
 }
@@ -103,9 +94,9 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale
 		request.Option |= RequestOptionOneTimeAuth
 		request.Option |= RequestOptionOneTimeAuth
 	}
 	}
 
 
-	sessionPolicy := v.policyManager.GetPolicy(user.Level)
+	sessionPolicy := v.v.PolicyManager().ForLevel(user.Level)
 	ctx, cancel := context.WithCancel(ctx)
 	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 {
 	if request.Command == protocol.RequestCommandTCP {
 		bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
 		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 {
 		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))
 			return buf.Copy(outboundRay.OutboundInput(), bodyWriter, buf.UpdateActivity(timer))
 		})
 		})
 
 
 		responseDone := signal.ExecuteAsync(func() error {
 		responseDone := signal.ExecuteAsync(func() error {
 			defer outboundRay.OutboundOutput().Close()
 			defer outboundRay.OutboundOutput().Close()
-			defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+			defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 
 			responseReader, err := ReadTCPResponse(user, conn)
 			responseReader, err := ReadTCPResponse(user, conn)
 			if err != nil {
 			if err != nil {
@@ -164,7 +155,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale
 				User:   user,
 				User:   user,
 			}
 			}
 
 
-			if err := buf.Copy(reader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer)); err != nil {
+			if err := buf.Copy(reader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer), buf.IgnoreReaderError()); err != nil {
 				return newError("failed to transport all UDP response").Base(err)
 				return newError("failed to transport all UDP response").Base(err)
 			}
 			}
 			return nil
 			return nil

+ 9 - 0
proxy/shadowsocks/config.go

@@ -140,6 +140,9 @@ func (v *AesCfb) EncodePacket(key []byte, b *buf.Buffer) error {
 }
 }
 
 
 func (v *AesCfb) DecodePacket(key []byte, b *buf.Buffer) error {
 func (v *AesCfb) DecodePacket(key []byte, b *buf.Buffer) error {
+	if b.Len() <= v.IVSize() {
+		return newError("insufficient data: ", b.Len())
+	}
 	iv := b.BytesTo(v.IVSize())
 	iv := b.BytesTo(v.IVSize())
 	stream := crypto.NewAesDecryptionStream(key, iv)
 	stream := crypto.NewAesDecryptionStream(key, iv)
 	stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
 	stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
@@ -203,6 +206,9 @@ func (c *AEADCipher) EncodePacket(key []byte, b *buf.Buffer) error {
 }
 }
 
 
 func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error {
 func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error {
+	if b.Len() <= c.IVSize() {
+		return newError("insufficient data: ", b.Len())
+	}
 	ivLen := c.IVSize()
 	ivLen := c.IVSize()
 	payloadLen := b.Len()
 	payloadLen := b.Len()
 	auth := c.createAuthenticator(key, b.BytesTo(ivLen))
 	auth := c.createAuthenticator(key, b.BytesTo(ivLen))
@@ -253,6 +259,9 @@ func (v *ChaCha20) EncodePacket(key []byte, b *buf.Buffer) error {
 }
 }
 
 
 func (v *ChaCha20) DecodePacket(key []byte, b *buf.Buffer) error {
 func (v *ChaCha20) DecodePacket(key []byte, b *buf.Buffer) error {
+	if b.Len() <= v.IVSize() {
+		return newError("insufficient data: ", b.Len())
+	}
 	iv := b.BytesTo(v.IVSize())
 	iv := b.BytesTo(v.IVSize())
 	stream := crypto.NewChaCha20Stream(key, iv)
 	stream := crypto.NewChaCha20Stream(key, iv)
 	stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))
 	stream.XORKeyStream(b.BytesFrom(v.IVSize()), b.BytesFrom(v.IVSize()))

+ 17 - 27
proxy/shadowsocks/server.go

@@ -4,9 +4,7 @@ import (
 	"context"
 	"context"
 	"time"
 	"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"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/log"
@@ -19,18 +17,14 @@ import (
 )
 )
 
 
 type Server struct {
 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.
 // NewServer create a new Shadowsocks server.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 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 {
 	if config.GetUser() == nil {
 		return nil, newError("user is not specified")
 		return nil, newError("user is not specified")
 	}
 	}
@@ -45,16 +39,12 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 		config:  config,
 		config:  config,
 		user:    config.GetUser(),
 		user:    config.GetUser(),
 		account: account,
 		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
 	return s, nil
 }
 }
@@ -69,7 +59,7 @@ func (s *Server) Network() net.NetworkList {
 	return list
 	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 {
 	switch network {
 	case net.Network_TCP:
 	case net.Network_TCP:
 		return s.handleConnection(ctx, conn, dispatcher)
 		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)
 	udpServer := udp.NewDispatcher(dispatcher)
 
 
 	reader := buf.NewReader(conn)
 	reader := buf.NewReader(conn)
@@ -148,9 +138,9 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection
 	return nil
 	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))
 	bufferedReader := buf.NewBufferedReader(buf.NewReader(conn))
 	request, bodyReader, err := ReadTCPSession(s.user, bufferedReader)
 	request, bodyReader, err := ReadTCPSession(s.user, bufferedReader)
 	if err != nil {
 	if err != nil {
@@ -178,7 +168,7 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection,
 	ctx = protocol.ContextWithUser(ctx, request.User)
 	ctx = protocol.ContextWithUser(ctx, request.User)
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	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)
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 	if err != nil {
 		return err
 		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)
 			return newError("failed to transport all TCP response").Base(err)
 		}
 		}
 
 
-		timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 
 		return nil
 		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 {
 		if err := buf.Copy(bodyReader, ray.InboundInput(), buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP request").Base(err)
 			return newError("failed to transport all TCP request").Base(err)
 		}
 		}
-		timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 		return nil
 		return nil
 	})
 	})
 
 

+ 23 - 27
proxy/socks/server.go

@@ -5,9 +5,7 @@ import (
 	"io"
 	"io"
 	"time"
 	"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"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/log"
@@ -22,32 +20,30 @@ import (
 // Server is a SOCKS 5 proxy server
 // Server is a SOCKS 5 proxy server
 type Server struct {
 type Server struct {
 	config *ServerConfig
 	config *ServerConfig
-	policy policy.Policy
+	v      *core.Instance
 }
 }
 
 
 // NewServer creates a new Server object.
 // NewServer creates a new Server object.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 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{
 	s := &Server{
 		config: config,
 		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
 	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 {
 func (s *Server) Network() net.NetworkList {
 	list := net.NetworkList{
 	list := net.NetworkList{
 		Network: []net.Network{net.Network_TCP},
 		Network: []net.Network{net.Network_TCP},
@@ -58,7 +54,7 @@ func (s *Server) Network() net.NetworkList {
 	return list
 	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 {
 	switch network {
 	case net.Network_TCP:
 	case net.Network_TCP:
 		return s.processTCP(ctx, conn, dispatcher)
 		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))
 	reader := buf.NewBufferedReader(buf.NewReader(conn))
 
 
 	inboundDest, ok := proxy.InboundEntryPointFromContext(ctx)
 	inboundDest, ok := proxy.InboundEntryPointFromContext(ctx)
@@ -125,9 +121,9 @@ func (*Server) handleUDP(c net.Conn) error {
 	return err
 	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)
 	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)
 	ray, err := dispatcher.Dispatch(ctx, dest)
 	if err != nil {
 	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 {
 		if err := buf.Copy(v2reader, input, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP request").Base(err)
 			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
 		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 {
 		if err := buf.Copy(output, v2writer, buf.UpdateActivity(timer)); err != nil {
 			return newError("failed to transport all TCP response").Base(err)
 			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
 		return nil
 	})
 	})
 
 
@@ -166,7 +162,7 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
 	return nil
 	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)
 	udpServer := udp.NewDispatcher(dispatcher)
 
 
 	if source, ok := proxy.SourceFromContext(ctx); ok {
 	if source, ok := proxy.SourceFromContext(ctx); ok {

+ 1 - 2
proxy/vmess/account.go

@@ -31,8 +31,7 @@ func (a *InternalAccount) Equals(account protocol.Account) bool {
 func (a *Account) AsAccount() (protocol.Account, error) {
 func (a *Account) AsAccount() (protocol.Account, error) {
 	id, err := uuid.ParseString(a.Id)
 	id, err := uuid.ParseString(a.Id)
 	if err != nil {
 	if err != nil {
-		newError("failed to parse ID").Base(err).AtError().WriteToLog()
-		return nil, err
+		return nil, newError("failed to parse ID").Base(err).AtError()
 	}
 	}
 	protoID := protocol.NewID(id)
 	protoID := protocol.NewID(id)
 	return &InternalAccount{
 	return &InternalAccount{

+ 2 - 2
proxy/vmess/encoding/client.go

@@ -131,7 +131,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write
 	}
 	}
 	if request.Security.Is(protocol.SecurityType_NONE) {
 	if request.Security.Is(protocol.SecurityType_NONE) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
-			if request.Command == protocol.RequestCommandTCP {
+			if request.Command.TransferType() == protocol.TransferTypeStream {
 				return crypto.NewChunkStreamWriter(sizeParser, writer)
 				return crypto.NewChunkStreamWriter(sizeParser, writer)
 			}
 			}
 			auth := &crypto.AEADAuthenticator{
 			auth := &crypto.AEADAuthenticator{
@@ -236,7 +236,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read
 	}
 	}
 	if request.Security.Is(protocol.SecurityType_NONE) {
 	if request.Security.Is(protocol.SecurityType_NONE) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
-			if request.Command == protocol.RequestCommandTCP {
+			if request.Command.TransferType() == protocol.TransferTypeStream {
 				return crypto.NewChunkStreamReader(sizeParser, reader)
 				return crypto.NewChunkStreamReader(sizeParser, reader)
 			}
 			}
 
 

+ 92 - 6
proxy/vmess/encoding/encoding_test.go

@@ -1,7 +1,6 @@
 package encoding_test
 package encoding_test
 
 
 import (
 import (
-	"context"
 	"testing"
 	"testing"
 
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
@@ -22,8 +21,9 @@ func TestRequestSerialization(t *testing.T) {
 		Level: 0,
 		Level: 0,
 		Email: "test@v2ray.com",
 		Email: "test@v2ray.com",
 	}
 	}
+	id := uuid.New()
 	account := &vmess.Account{
 	account := &vmess.Account{
-		Id:      uuid.New().String(),
+		Id:      id.String(),
 		AlterId: 0,
 		AlterId: 0,
 	}
 	}
 	user.Account = serial.ToTypedMessage(account)
 	user.Account = serial.ToTypedMessage(account)
@@ -44,11 +44,12 @@ func TestRequestSerialization(t *testing.T) {
 	buffer2 := buf.New()
 	buffer2 := buf.New()
 	buffer2.Append(buffer.Bytes())
 	buffer2.Append(buffer.Bytes())
 
 
-	ctx, cancel := context.WithCancel(context.Background())
-	sessionHistory := NewSessionHistory(ctx)
+	sessionHistory := NewSessionHistory()
+	defer common.Close(sessionHistory)
 
 
-	userValidator := vmess.NewTimedUserValidator(ctx, protocol.DefaultIDHash)
+	userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
 	userValidator.Add(user)
 	userValidator.Add(user)
+	defer common.Close(userValidator)
 
 
 	server := NewServerSession(userValidator, sessionHistory)
 	server := NewServerSession(userValidator, sessionHistory)
 	actualRequest, err := server.DecodeRequestHeader(buffer)
 	actualRequest, err := server.DecodeRequestHeader(buffer)
@@ -64,6 +65,91 @@ func TestRequestSerialization(t *testing.T) {
 	_, err = server.DecodeRequestHeader(buffer2)
 	_, err = server.DecodeRequestHeader(buffer2)
 	// anti replay attack
 	// anti replay attack
 	assert(err, IsNotNil)
 	assert(err, IsNotNil)
+}
+
+func TestInvalidRequest(t *testing.T) {
+	assert := With(t)
+
+	user := &protocol.User{
+		Level: 0,
+		Email: "test@v2ray.com",
+	}
+	id := uuid.New()
+	account := &vmess.Account{
+		Id:      id.String(),
+		AlterId: 0,
+	}
+	user.Account = serial.ToTypedMessage(account)
 
 
-	cancel()
+	expectedRequest := &protocol.RequestHeader{
+		Version:  1,
+		User:     user,
+		Command:  protocol.RequestCommand(100),
+		Address:  net.DomainAddress("www.v2ray.com"),
+		Port:     net.Port(443),
+		Security: protocol.Security(protocol.SecurityType_AES128_GCM),
+	}
+
+	buffer := buf.New()
+	client := NewClientSession(protocol.DefaultIDHash)
+	common.Must(client.EncodeRequestHeader(expectedRequest, buffer))
+
+	buffer2 := buf.New()
+	buffer2.Append(buffer.Bytes())
+
+	sessionHistory := NewSessionHistory()
+	defer common.Close(sessionHistory)
+
+	userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
+	userValidator.Add(user)
+	defer common.Close(userValidator)
+
+	server := NewServerSession(userValidator, sessionHistory)
+	_, err := server.DecodeRequestHeader(buffer)
+	assert(err, IsNotNil)
+}
+
+func TestMuxRequest(t *testing.T) {
+	assert := With(t)
+
+	user := &protocol.User{
+		Level: 0,
+		Email: "test@v2ray.com",
+	}
+	id := uuid.New()
+	account := &vmess.Account{
+		Id:      id.String(),
+		AlterId: 0,
+	}
+	user.Account = serial.ToTypedMessage(account)
+
+	expectedRequest := &protocol.RequestHeader{
+		Version:  1,
+		User:     user,
+		Command:  protocol.RequestCommandMux,
+		Security: protocol.Security(protocol.SecurityType_AES128_GCM),
+	}
+
+	buffer := buf.New()
+	client := NewClientSession(protocol.DefaultIDHash)
+	common.Must(client.EncodeRequestHeader(expectedRequest, buffer))
+
+	buffer2 := buf.New()
+	buffer2.Append(buffer.Bytes())
+
+	sessionHistory := NewSessionHistory()
+	defer common.Close(sessionHistory)
+
+	userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash)
+	userValidator.Add(user)
+	defer common.Close(userValidator)
+
+	server := NewServerSession(userValidator, sessionHistory)
+	actualRequest, err := server.DecodeRequestHeader(buffer)
+	assert(err, IsNil)
+
+	assert(expectedRequest.Version, Equals, actualRequest.Version)
+	assert(byte(expectedRequest.Command), Equals, byte(actualRequest.Command))
+	assert(byte(expectedRequest.Option), Equals, byte(actualRequest.Option))
+	assert(byte(expectedRequest.Security), Equals, byte(actualRequest.Security))
 }
 }

+ 88 - 68
proxy/vmess/encoding/server.go

@@ -1,7 +1,6 @@
 package encoding
 package encoding
 
 
 import (
 import (
-	"context"
 	"crypto/aes"
 	"crypto/aes"
 	"crypto/cipher"
 	"crypto/cipher"
 	"crypto/md5"
 	"crypto/md5"
@@ -10,6 +9,8 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
+	"v2ray.com/core/common/dice"
+
 	"golang.org/x/crypto/chacha20poly1305"
 	"golang.org/x/crypto/chacha20poly1305"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/bitmask"
 	"v2ray.com/core/common/bitmask"
@@ -31,29 +32,34 @@ type sessionId struct {
 type SessionHistory struct {
 type SessionHistory struct {
 	sync.RWMutex
 	sync.RWMutex
 	cache map[sessionId]time.Time
 	cache map[sessionId]time.Time
-	token *signal.Semaphore
-	ctx   context.Context
+	task  *signal.PeriodicTask
 }
 }
 
 
-func NewSessionHistory(ctx context.Context) *SessionHistory {
+func NewSessionHistory() *SessionHistory {
 	h := &SessionHistory{
 	h := &SessionHistory{
 		cache: make(map[sessionId]time.Time, 128),
 		cache: make(map[sessionId]time.Time, 128),
-		token: signal.NewSemaphore(1),
-		ctx:   ctx,
 	}
 	}
+	h.task = &signal.PeriodicTask{
+		Interval: time.Second * 30,
+		Execute: func() error {
+			h.removeExpiredEntries()
+			return nil
+		},
+	}
+	common.Must(h.task.Start())
 	return h
 	return h
 }
 }
 
 
+// Close implements common.Closable.
+func (h *SessionHistory) Close() error {
+	return h.task.Close()
+}
+
 func (h *SessionHistory) add(session sessionId) {
 func (h *SessionHistory) add(session sessionId) {
 	h.Lock()
 	h.Lock()
-	h.cache[session] = time.Now().Add(time.Minute * 3)
-	h.Unlock()
+	defer h.Unlock()
 
 
-	select {
-	case <-h.token.Wait():
-		go h.run()
-	default:
-	}
+	h.cache[session] = time.Now().Add(time.Minute * 3)
 }
 }
 
 
 func (h *SessionHistory) has(session sessionId) bool {
 func (h *SessionHistory) has(session sessionId) bool {
@@ -66,31 +72,16 @@ func (h *SessionHistory) has(session sessionId) bool {
 	return false
 	return false
 }
 }
 
 
-func (h *SessionHistory) run() {
-	defer h.token.Signal()
+func (h *SessionHistory) removeExpiredEntries() {
+	now := time.Now()
 
 
-	for {
-		select {
-		case <-h.ctx.Done():
-			return
-		case <-time.After(time.Second * 30):
-		}
-		session2Remove := make([]sessionId, 0, 16)
-		now := time.Now()
-		h.Lock()
-		if len(h.cache) == 0 {
-			h.Unlock()
-			return
-		}
-		for session, expire := range h.cache {
-			if expire.Before(now) {
-				session2Remove = append(session2Remove, session)
-			}
-		}
-		for _, session := range session2Remove {
+	h.Lock()
+	defer h.Unlock()
+
+	for session, expire := range h.cache {
+		if expire.Before(now) {
 			delete(h.cache, session)
 			delete(h.cache, session)
 		}
 		}
-		h.Unlock()
 	}
 	}
 }
 }
 
 
@@ -114,6 +105,44 @@ func NewServerSession(validator protocol.UserValidator, sessionHistory *SessionH
 	}
 	}
 }
 }
 
 
+func readAddress(buffer *buf.Buffer, reader io.Reader) (net.Address, net.Port, error) {
+	var address net.Address
+	var port net.Port
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 3)); err != nil {
+		return address, port, newError("failed to read port and address type").Base(err)
+	}
+	port = net.PortFromBytes(buffer.BytesRange(-3, -1))
+
+	addressType := protocol.AddressType(buffer.Byte(buffer.Len() - 1))
+	switch addressType {
+	case protocol.AddressTypeIPv4:
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil {
+			return address, port, newError("failed to read IPv4 address").Base(err)
+		}
+		address = net.IPAddress(buffer.BytesFrom(-4))
+	case protocol.AddressTypeIPv6:
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil {
+			return address, port, newError("failed to read IPv6 address").Base(err)
+		}
+		address = net.IPAddress(buffer.BytesFrom(-16))
+	case protocol.AddressTypeDomain:
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+			return address, port, newError("failed to read domain address").Base(err)
+		}
+		domainLength := int(buffer.Byte(buffer.Len() - 1))
+		if domainLength == 0 {
+			return address, port, newError("zero length domain")
+		}
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil {
+			return address, port, newError("failed to read domain address").Base(err)
+		}
+		address = net.DomainAddress(string(buffer.BytesFrom(-domainLength)))
+	default:
+		return address, port, newError("invalid address type", addressType)
+	}
+	return address, port, nil
+}
+
 func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
 func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) {
 	buffer := buf.New()
 	buffer := buf.New()
 	defer buffer.Release()
 	defer buffer.Release()
@@ -139,7 +168,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
 	aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv)
 	aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv)
 	decryptor := crypto.NewCryptionReader(aesStream, reader)
 	decryptor := crypto.NewCryptionReader(aesStream, reader)
 
 
-	if err := buffer.Reset(buf.ReadFullFrom(decryptor, 41)); err != nil {
+	if err := buffer.Reset(buf.ReadFullFrom(decryptor, 38)); err != nil {
 		return nil, newError("failed to read request header").Base(err)
 		return nil, newError("failed to read request header").Base(err)
 	}
 	}
 
 
@@ -148,10 +177,6 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
 		Version: buffer.Byte(0),
 		Version: buffer.Byte(0),
 	}
 	}
 
 
-	if request.Version != Version {
-		return nil, newError("invalid protocol version ", request.Version)
-	}
-
 	s.requestBodyIV = append([]byte(nil), buffer.BytesRange(1, 17)...)   // 16 bytes
 	s.requestBodyIV = append([]byte(nil), buffer.BytesRange(1, 17)...)   // 16 bytes
 	s.requestBodyKey = append([]byte(nil), buffer.BytesRange(17, 33)...) // 16 bytes
 	s.requestBodyKey = append([]byte(nil), buffer.BytesRange(17, 33)...) // 16 bytes
 	var sid sessionId
 	var sid sessionId
@@ -170,33 +195,28 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request
 	// 1 bytes reserved
 	// 1 bytes reserved
 	request.Command = protocol.RequestCommand(buffer.Byte(37))
 	request.Command = protocol.RequestCommand(buffer.Byte(37))
 
 
-	if request.Command != protocol.RequestCommandMux {
-		request.Port = net.PortFromBytes(buffer.BytesRange(38, 40))
-
-		switch protocol.AddressType(buffer.Byte(40)) {
-		case protocol.AddressTypeIPv4:
-			if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, 4)); err != nil {
-				return nil, newError("failed to read IPv4 address").Base(err)
-			}
-			request.Address = net.IPAddress(buffer.BytesFrom(-4))
-		case protocol.AddressTypeIPv6:
-			if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, 16)); err != nil {
-				return nil, newError("failed to read IPv6 address").Base(err)
-			}
-			request.Address = net.IPAddress(buffer.BytesFrom(-16))
-		case protocol.AddressTypeDomain:
-			if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, 1)); err != nil {
-				return nil, newError("failed to read domain address").Base(err)
-			}
-			domainLength := int(buffer.Byte(buffer.Len() - 1))
-			if domainLength == 0 {
-				return nil, newError("zero length domain").Base(err)
-			}
-			if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, domainLength)); err != nil {
-				return nil, newError("failed to read domain address").Base(err)
-			}
-			request.Address = net.DomainAddress(string(buffer.BytesFrom(-domainLength)))
+	invalidRequest := false
+	switch request.Command {
+	case protocol.RequestCommandMux:
+		request.Address = net.DomainAddress("v1.mux.cool")
+		request.Port = 0
+	case protocol.RequestCommandTCP, protocol.RequestCommandUDP:
+		if addr, port, err := readAddress(buffer, decryptor); err == nil {
+			request.Address = addr
+			request.Port = port
+		} else {
+			invalidRequest = true
+			newError("failed to read address").Base(err).WriteToLog()
 		}
 		}
+	default:
+		invalidRequest = true
+	}
+
+	if invalidRequest {
+		randomLen := dice.Roll(32) + 1
+		// Read random number of bytes for prevent detection.
+		buffer.AppendSupplier(buf.ReadFullFrom(decryptor, randomLen))
+		return nil, newError("invalid request")
 	}
 	}
 
 
 	if padingLen > 0 {
 	if padingLen > 0 {
@@ -232,7 +252,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade
 	}
 	}
 	if request.Security.Is(protocol.SecurityType_NONE) {
 	if request.Security.Is(protocol.SecurityType_NONE) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
-			if request.Command == protocol.RequestCommandTCP {
+			if request.Command.TransferType() == protocol.TransferTypeStream {
 				return crypto.NewChunkStreamReader(sizeParser, reader)
 				return crypto.NewChunkStreamReader(sizeParser, reader)
 			}
 			}
 
 
@@ -318,7 +338,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ
 	}
 	}
 	if request.Security.Is(protocol.SecurityType_NONE) {
 	if request.Security.Is(protocol.SecurityType_NONE) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
 		if request.Option.Has(protocol.RequestOptionChunkStream) {
-			if request.Command == protocol.RequestCommandTCP {
+			if request.Command.TransferType() == protocol.TransferTypeStream {
 				return crypto.NewChunkStreamWriter(sizeParser, writer)
 				return crypto.NewChunkStreamWriter(sizeParser, writer)
 			}
 			}
 
 

+ 101 - 70
proxy/vmess/inbound/inbound.go

@@ -5,13 +5,11 @@ package inbound
 import (
 import (
 	"context"
 	"context"
 	"io"
 	"io"
+	"strings"
 	"sync"
 	"sync"
 	"time"
 	"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"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/errors"
@@ -28,96 +26,116 @@ import (
 )
 )
 
 
 type userByEmail struct {
 type userByEmail struct {
-	sync.RWMutex
+	sync.Mutex
 	cache           map[string]*protocol.User
 	cache           map[string]*protocol.User
 	defaultLevel    uint32
 	defaultLevel    uint32
 	defaultAlterIDs uint16
 	defaultAlterIDs uint16
 }
 }
 
 
-func newUserByEmail(users []*protocol.User, config *DefaultConfig) *userByEmail {
-	cache := make(map[string]*protocol.User)
-	for _, user := range users {
-		cache[user.Email] = user
-	}
+func newUserByEmail(config *DefaultConfig) *userByEmail {
 	return &userByEmail{
 	return &userByEmail{
-		cache:           cache,
+		cache:           make(map[string]*protocol.User),
 		defaultLevel:    config.Level,
 		defaultLevel:    config.Level,
 		defaultAlterIDs: uint16(config.AlterId),
 		defaultAlterIDs: uint16(config.AlterId),
 	}
 	}
 }
 }
 
 
+func (v *userByEmail) addNoLock(u *protocol.User) bool {
+	email := strings.ToLower(u.Email)
+	user, found := v.cache[email]
+	if found {
+		return false
+	}
+	v.cache[email] = user
+	return true
+}
+
+func (v *userByEmail) Add(u *protocol.User) bool {
+	v.Lock()
+	defer v.Unlock()
+
+	return v.addNoLock(u)
+}
+
 func (v *userByEmail) Get(email string) (*protocol.User, bool) {
 func (v *userByEmail) Get(email string) (*protocol.User, bool) {
-	var user *protocol.User
-	var found bool
-	v.RLock()
-	user, found = v.cache[email]
-	v.RUnlock()
+	email = strings.ToLower(email)
+
+	v.Lock()
+	defer v.Unlock()
+
+	user, found := v.cache[email]
 	if !found {
 	if !found {
-		v.Lock()
-		user, found = v.cache[email]
-		if !found {
-			account := &vmess.Account{
-				Id:      uuid.New().String(),
-				AlterId: uint32(v.defaultAlterIDs),
-			}
-			user = &protocol.User{
-				Level:   v.defaultLevel,
-				Email:   email,
-				Account: serial.ToTypedMessage(account),
-			}
-			v.cache[email] = user
+		id := uuid.New()
+		account := &vmess.Account{
+			Id:      id.String(),
+			AlterId: uint32(v.defaultAlterIDs),
+		}
+		user = &protocol.User{
+			Level:   v.defaultLevel,
+			Email:   email,
+			Account: serial.ToTypedMessage(account),
 		}
 		}
-		v.Unlock()
+		v.cache[email] = user
 	}
 	}
 	return user, found
 	return user, found
 }
 }
 
 
+func (v *userByEmail) Remove(email string) bool {
+	email = strings.ToLower(email)
+
+	v.Lock()
+	defer v.Unlock()
+
+	if _, found := v.cache[email]; !found {
+		return false
+	}
+	delete(v.cache, email)
+	return true
+}
+
 // Handler is an inbound connection handler that handles messages in VMess protocol.
 // Handler is an inbound connection handler that handles messages in VMess protocol.
 type Handler struct {
 type Handler struct {
-	inboundHandlerManager proxyman.InboundHandlerManager
+	policyManager         core.PolicyManager
+	inboundHandlerManager core.InboundHandlerManager
 	clients               protocol.UserValidator
 	clients               protocol.UserValidator
 	usersByEmail          *userByEmail
 	usersByEmail          *userByEmail
 	detours               *DetourConfig
 	detours               *DetourConfig
 	sessionHistory        *encoding.SessionHistory
 	sessionHistory        *encoding.SessionHistory
-	policyManager         policy.Manager
 }
 }
 
 
 // New creates a new VMess inbound handler.
 // New creates a new VMess inbound handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
 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 {
-			return nil, newError("failed to initiate user").Base(err)
-		}
+	v := core.FromContext(ctx)
+	if v == nil {
+		return nil, newError("V is not in context.")
 	}
 	}
 
 
 	handler := &Handler{
 	handler := &Handler{
-		clients:        allowedClients,
-		detours:        config.Detour,
-		usersByEmail:   newUserByEmail(config.User, config.GetDefaultValue()),
-		sessionHistory: encoding.NewSessionHistory(ctx),
+		policyManager:         v.PolicyManager(),
+		inboundHandlerManager: v.InboundHandlerManager(),
+		clients:               vmess.NewTimedUserValidator(protocol.DefaultIDHash),
+		detours:               config.Detour,
+		usersByEmail:          newUserByEmail(config.GetDefaultValue()),
+		sessionHistory:        encoding.NewSessionHistory(),
 	}
 	}
 
 
-	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.")
+	for _, user := range config.User {
+		if err := handler.AddUser(ctx, user); err != nil {
+			return nil, newError("failed to initiate user").Base(err)
 		}
 		}
-		return nil
-	})
+	}
 
 
 	return handler, nil
 	return handler, nil
 }
 }
 
 
+// Close implements common.Closable.
+func (h *Handler) Close() error {
+	common.Close(h.clients)
+	common.Close(h.sessionHistory)
+	common.Close(h.usersByEmail)
+	return nil
+}
+
 // Network implements proxy.Inbound.Network().
 // Network implements proxy.Inbound.Network().
 func (*Handler) Network() net.NetworkList {
 func (*Handler) Network() net.NetworkList {
 	return net.NetworkList{
 	return net.NetworkList{
@@ -133,6 +151,24 @@ func (h *Handler) GetUser(email string) *protocol.User {
 	return user
 	return user
 }
 }
 
 
+func (h *Handler) AddUser(ctx context.Context, user *protocol.User) error {
+	if len(user.Email) > 0 && !h.usersByEmail.Add(user) {
+		return newError("User ", user.Email, " already exists.")
+	}
+	return h.clients.Add(user)
+}
+
+func (h *Handler) RemoveUser(ctx context.Context, email string) error {
+	if len(email) == 0 {
+		return newError("Email must not be empty.")
+	}
+	if !h.usersByEmail.Remove(email) {
+		return newError("User ", email, " not found.")
+	}
+	h.clients.Remove(email)
+	return nil
+}
+
 func transferRequest(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, input io.Reader, output ray.OutputStream) error {
 func transferRequest(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, input io.Reader, output ray.OutputStream) error {
 	defer output.Close()
 	defer output.Close()
 
 
@@ -179,9 +215,9 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess
 }
 }
 
 
 // Process implements proxy.Inbound.Process().
 // 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()
 		return newError("unable to set read deadline").Base(err).AtWarning()
 	}
 	}
 
 
@@ -198,16 +234,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
 				Status: log.AccessRejected,
 				Status: log.AccessRejected,
 				Reason: err,
 				Reason: err,
 			})
 			})
-			newError("invalid request from ", connection.RemoteAddr(), ": ", err).AtInfo().WriteToLog()
+			err = newError("invalid request from ", connection.RemoteAddr()).Base(err).AtInfo()
 		}
 		}
 		return err
 		return err
 	}
 	}
 
 
-	if request.Command == protocol.RequestCommandMux {
-		request.Address = net.DomainAddress("v1.mux.com")
-		request.Port = net.Port(0)
-	}
-
 	log.Record(&log.AccessMessage{
 	log.Record(&log.AccessMessage{
 		From:   connection.RemoteAddr(),
 		From:   connection.RemoteAddr(),
 		To:     request.Destination(),
 		To:     request.Destination(),
@@ -221,11 +252,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
 		newError("unable to set back read deadline").Base(err).WriteToLog()
 		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 = protocol.ContextWithUser(ctx, request.User)
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	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())
 	ray, err := dispatcher.Dispatch(ctx, request.Destination())
 	if err != nil {
 	if err != nil {
 		return newError("failed to dispatch request to ", request.Destination()).Base(err)
 		return newError("failed to dispatch request to ", request.Destination()).Base(err)
@@ -235,14 +266,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i
 	output := ray.InboundOutput()
 	output := ray.InboundOutput()
 
 
 	requestDone := signal.ExecuteAsync(func() error {
 	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)
 		return transferRequest(timer, session, request, reader, input)
 	})
 	})
 
 
 	responseDone := signal.ExecuteAsync(func() error {
 	responseDone := signal.ExecuteAsync(func() error {
 		writer := buf.NewBufferedWriter(buf.NewWriter(connection))
 		writer := buf.NewBufferedWriter(buf.NewWriter(connection))
 		defer writer.Flush()
 		defer writer.Flush()
-		defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 
 		response := &protocol.ResponseHeader{
 		response := &protocol.ResponseHeader{
 			Command: h.generateCommand(ctx, request),
 			Command: h.generateCommand(ctx, request),
@@ -265,7 +296,7 @@ func (h *Handler) generateCommand(ctx context.Context, request *protocol.Request
 		if h.inboundHandlerManager != nil {
 		if h.inboundHandlerManager != nil {
 			handler, err := h.inboundHandlerManager.GetHandler(ctx, tag)
 			handler, err := h.inboundHandlerManager.GetHandler(ctx, tag)
 			if err != nil {
 			if err != nil {
-				newError("failed to get detour handler: ", tag, err).AtWarning().WriteToLog()
+				newError("failed to get detour handler: ", tag).Base(err).AtWarning().WriteToLog()
 				return nil
 				return nil
 			}
 			}
 			proxyHandler, port, availableMin := handler.GetRandomInboundProxy()
 			proxyHandler, port, availableMin := handler.GetRandomInboundProxy()

+ 15 - 25
proxy/vmess/outbound/outbound.go

@@ -6,8 +6,7 @@ import (
 	"context"
 	"context"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/app"
-	"v2ray.com/core/app/policy"
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
@@ -23,17 +22,12 @@ import (
 
 
 // Handler is an outbound connection handler for VMess protocol.
 // Handler is an outbound connection handler for VMess protocol.
 type Handler struct {
 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) {
 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()
 	serverList := protocol.NewServerList()
 	for _, rec := range config.Receiver {
 	for _, rec := range config.Receiver {
 		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
 		serverList.AddServer(protocol.NewServerSpecFromPB(*rec))
@@ -41,16 +35,12 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 	handler := &Handler{
 	handler := &Handler{
 		serverList:   serverList,
 		serverList:   serverList,
 		serverPicker: protocol.NewRoundRobinServerPicker(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
 	return handler, nil
 }
 }
@@ -85,9 +75,9 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	if target.Network == net.Network_UDP {
 	if target.Network == net.Network_UDP {
 		command = protocol.RequestCommandUDP
 		command = protocol.RequestCommandUDP
 	}
 	}
-	if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.com" {
-		command = protocol.RequestCommandMux
-	}
+	//if target.Address.Family().IsDomain() && target.Address.Domain() == "v1.mux.cool" {
+	//	command = protocol.RequestCommandMux
+	//}
 	request := &protocol.RequestHeader{
 	request := &protocol.RequestHeader{
 		Version: encoding.Version,
 		Version: encoding.Version,
 		User:    rec.PickUser(),
 		User:    rec.PickUser(),
@@ -112,10 +102,10 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 	output := outboundRay.OutboundOutput()
 	output := outboundRay.OutboundOutput()
 
 
 	session := encoding.NewClientSession(protocol.DefaultIDHash)
 	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)
 	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 {
 	requestDone := signal.ExecuteAsync(func() error {
 		writer := buf.NewBufferedWriter(buf.NewWriter(conn))
 		writer := buf.NewBufferedWriter(buf.NewWriter(conn))
@@ -148,13 +138,13 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial
 				return err
 				return err
 			}
 			}
 		}
 		}
-		timer.SetTimeout(sessionPolicy.Timeout.DownlinkOnly.Duration())
+		timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
 		return nil
 		return nil
 	})
 	})
 
 
 	responseDone := signal.ExecuteAsync(func() error {
 	responseDone := signal.ExecuteAsync(func() error {
 		defer output.Close()
 		defer output.Close()
-		defer timer.SetTimeout(sessionPolicy.Timeout.UplinkOnly.Duration())
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
 
 
 		reader := buf.NewBufferedReader(buf.NewReader(conn))
 		reader := buf.NewBufferedReader(buf.NewReader(conn))
 		header, err := session.DecodeResponseHeader(reader)
 		header, err := session.DecodeResponseHeader(reader)

+ 104 - 75
proxy/vmess/vmess.go

@@ -8,99 +8,110 @@ package vmess
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg vmess -path Proxy,VMess
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg vmess -path Proxy,VMess
 
 
 import (
 import (
-	"context"
+	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/signal"
 )
 )
 
 
 const (
 const (
-	updateIntervalSec = 10
-	cacheDurationSec  = 120
+	updateInterval   = 10 * time.Second
+	cacheDurationSec = 120
 )
 )
 
 
-type idEntry struct {
-	id             *protocol.ID
-	userIdx        int
-	lastSec        protocol.Timestamp
-	lastSecRemoval protocol.Timestamp
+type user struct {
+	user    *protocol.User
+	account *InternalAccount
+	lastSec protocol.Timestamp
 }
 }
 
 
 type TimedUserValidator struct {
 type TimedUserValidator struct {
 	sync.RWMutex
 	sync.RWMutex
-	validUsers []*protocol.User
-	userHash   map[[16]byte]indexTimePair
-	ids        []*idEntry
-	hasher     protocol.IDHash
-	baseTime   protocol.Timestamp
+	users    []*user
+	userHash map[[16]byte]indexTimePair
+	hasher   protocol.IDHash
+	baseTime protocol.Timestamp
+	task     *signal.PeriodicTask
 }
 }
 
 
 type indexTimePair struct {
 type indexTimePair struct {
-	index   int
+	user    *user
 	timeInc uint32
 	timeInc uint32
 }
 }
 
 
-func NewTimedUserValidator(ctx context.Context, hasher protocol.IDHash) protocol.UserValidator {
-	tus := &TimedUserValidator{
-		validUsers: make([]*protocol.User, 0, 16),
-		userHash:   make(map[[16]byte]indexTimePair, 512),
-		ids:        make([]*idEntry, 0, 512),
-		hasher:     hasher,
-		baseTime:   protocol.Timestamp(time.Now().Unix() - cacheDurationSec*3),
+func NewTimedUserValidator(hasher protocol.IDHash) protocol.UserValidator {
+	tuv := &TimedUserValidator{
+		users:    make([]*user, 0, 16),
+		userHash: make(map[[16]byte]indexTimePair, 1024),
+		hasher:   hasher,
+		baseTime: protocol.Timestamp(time.Now().Unix() - cacheDurationSec*3),
 	}
 	}
-	go tus.updateUserHash(ctx, updateIntervalSec*time.Second)
-	return tus
+	tuv.task = &signal.PeriodicTask{
+		Interval: updateInterval,
+		Execute: func() error {
+			tuv.updateUserHash()
+			return nil
+		},
+	}
+	tuv.task.Start()
+	return tuv
 }
 }
 
 
-func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, idx int, entry *idEntry) {
+func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, user *user) {
 	var hashValue [16]byte
 	var hashValue [16]byte
-	var hashValueRemoval [16]byte
-	idHash := v.hasher(entry.id.Bytes())
-	for entry.lastSec <= nowSec {
-		common.Must2(idHash.Write(entry.lastSec.Bytes(nil)))
-		idHash.Sum(hashValue[:0])
-		idHash.Reset()
-
-		common.Must2(idHash.Write(entry.lastSecRemoval.Bytes(nil)))
-		idHash.Sum(hashValueRemoval[:0])
-		idHash.Reset()
-
-		delete(v.userHash, hashValueRemoval)
-		v.userHash[hashValue] = indexTimePair{
-			index:   idx,
-			timeInc: uint32(entry.lastSec - v.baseTime),
+	genHashForID := func(id *protocol.ID) {
+		idHash := v.hasher(id.Bytes())
+		for ts := user.lastSec; ts <= nowSec; ts++ {
+			common.Must2(idHash.Write(ts.Bytes(nil)))
+			idHash.Sum(hashValue[:0])
+			idHash.Reset()
+
+			v.userHash[hashValue] = indexTimePair{
+				user:    user,
+				timeInc: uint32(ts - v.baseTime),
+			}
 		}
 		}
+	}
 
 
-		entry.lastSec++
-		entry.lastSecRemoval++
+	genHashForID(user.account.ID)
+	for _, id := range user.account.AlterIDs {
+		genHashForID(id)
 	}
 	}
+	user.lastSec = nowSec
 }
 }
 
 
-func (v *TimedUserValidator) updateUserHash(ctx context.Context, interval time.Duration) {
-	for {
-		select {
-		case now := <-time.After(interval):
-			nowSec := protocol.Timestamp(now.Unix() + cacheDurationSec)
-			v.Lock()
-			for _, entry := range v.ids {
-				v.generateNewHashes(nowSec, entry.userIdx, entry)
-			}
-			v.Unlock()
-		case <-ctx.Done():
-			return
+func (v *TimedUserValidator) removeExpiredHashes(expire uint32) {
+	for key, pair := range v.userHash {
+		if pair.timeInc < expire {
+			delete(v.userHash, key)
 		}
 		}
 	}
 	}
 }
 }
 
 
-func (v *TimedUserValidator) Add(user *protocol.User) error {
+func (v *TimedUserValidator) updateUserHash() {
+	now := time.Now()
+	nowSec := protocol.Timestamp(now.Unix() + cacheDurationSec)
 	v.Lock()
 	v.Lock()
 	defer v.Unlock()
 	defer v.Unlock()
 
 
-	idx := len(v.validUsers)
-	v.validUsers = append(v.validUsers, user)
-	rawAccount, err := user.GetTypedAccount()
+	for _, user := range v.users {
+		v.generateNewHashes(nowSec, user)
+	}
+
+	expire := protocol.Timestamp(now.Unix() - cacheDurationSec*3)
+	if expire > v.baseTime {
+		v.removeExpiredHashes(uint32(expire - v.baseTime))
+	}
+}
+
+func (v *TimedUserValidator) Add(u *protocol.User) error {
+	v.Lock()
+	defer v.Unlock()
+
+	rawAccount, err := u.GetTypedAccount()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -108,24 +119,13 @@ func (v *TimedUserValidator) Add(user *protocol.User) error {
 
 
 	nowSec := time.Now().Unix()
 	nowSec := time.Now().Unix()
 
 
-	entry := &idEntry{
-		id:             account.ID,
-		userIdx:        idx,
-		lastSec:        protocol.Timestamp(nowSec - cacheDurationSec),
-		lastSecRemoval: protocol.Timestamp(nowSec - cacheDurationSec*3),
-	}
-	v.generateNewHashes(protocol.Timestamp(nowSec+cacheDurationSec), idx, entry)
-	v.ids = append(v.ids, entry)
-	for _, alterid := range account.AlterIDs {
-		entry := &idEntry{
-			id:             alterid,
-			userIdx:        idx,
-			lastSec:        protocol.Timestamp(nowSec - cacheDurationSec),
-			lastSecRemoval: protocol.Timestamp(nowSec - cacheDurationSec*3),
-		}
-		v.generateNewHashes(protocol.Timestamp(nowSec+cacheDurationSec), idx, entry)
-		v.ids = append(v.ids, entry)
+	uu := &user{
+		user:    u,
+		account: account,
+		lastSec: protocol.Timestamp(nowSec - cacheDurationSec),
 	}
 	}
+	v.users = append(v.users, uu)
+	v.generateNewHashes(protocol.Timestamp(nowSec+cacheDurationSec), uu)
 
 
 	return nil
 	return nil
 }
 }
@@ -138,7 +138,36 @@ func (v *TimedUserValidator) Get(userHash []byte) (*protocol.User, protocol.Time
 	copy(fixedSizeHash[:], userHash)
 	copy(fixedSizeHash[:], userHash)
 	pair, found := v.userHash[fixedSizeHash]
 	pair, found := v.userHash[fixedSizeHash]
 	if found {
 	if found {
-		return v.validUsers[pair.index], protocol.Timestamp(pair.timeInc) + v.baseTime, true
+		return pair.user.user, protocol.Timestamp(pair.timeInc) + v.baseTime, true
 	}
 	}
 	return nil, 0, false
 	return nil, 0, false
 }
 }
+
+func (v *TimedUserValidator) Remove(email string) bool {
+	v.Lock()
+	defer v.Unlock()
+
+	email = strings.ToLower(email)
+	idx := -1
+	for i, u := range v.users {
+		if strings.ToLower(u.user.Email) == email {
+			idx = i
+			break
+		}
+	}
+	if idx == -1 {
+		return false
+	}
+	ulen := len(v.users)
+	if idx < len(v.users) {
+		v.users[idx] = v.users[ulen-1]
+		v.users[ulen-1] = nil
+		v.users = v.users[:ulen-1]
+	}
+	return true
+}
+
+// Close implements common.Closable.
+func (v *TimedUserValidator) Close() error {
+	return v.task.Close()
+}

+ 58 - 0
proxy/vmess/vmess_test.go

@@ -0,0 +1,58 @@
+package vmess_test
+
+import (
+	"testing"
+	"time"
+
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/serial"
+	"v2ray.com/core/common/uuid"
+
+	"v2ray.com/core/common/protocol"
+	. "v2ray.com/core/proxy/vmess"
+	. "v2ray.com/ext/assert"
+)
+
+func TestUserValidator(t *testing.T) {
+	assert := With(t)
+
+	hasher := protocol.DefaultIDHash
+	v := NewTimedUserValidator(hasher)
+	defer common.Close(v)
+
+	id := uuid.New()
+	user := &protocol.User{
+		Email: "test",
+		Account: serial.ToTypedMessage(&Account{
+			Id:      id.String(),
+			AlterId: 8,
+		}),
+	}
+	common.Must(v.Add(user))
+
+	{
+		ts := protocol.Timestamp(time.Now().Unix())
+		idHash := hasher(id.Bytes())
+		idHash.Write(ts.Bytes(nil))
+		userHash := idHash.Sum(nil)
+
+		euser, ets, found := v.Get(userHash)
+		assert(found, IsTrue)
+		assert(euser.Email, Equals, user.Email)
+		assert(int64(ets), Equals, int64(ts))
+	}
+
+	{
+		ts := protocol.Timestamp(time.Now().Add(time.Second * 500).Unix())
+		idHash := hasher(id.Bytes())
+		idHash.Write(ts.Bytes(nil))
+		userHash := idHash.Sum(nil)
+
+		euser, _, found := v.Get(userHash)
+		assert(found, IsFalse)
+		assert(euser, IsNil)
+	}
+
+	assert(v.Remove(user.Email), IsTrue)
+	assert(v.Remove(user.Email), IsFalse)
+}

+ 2 - 2
release/release-ci.sh

@@ -25,7 +25,7 @@ echo ${SIGN_KEY_PASS} | gpg --passphrase-fd 0 --batch --import /v2ray/build/sign
 curl -L -o /v2ray/build/releases https://api.github.com/repos/v2ray/v2ray-core/releases
 curl -L -o /v2ray/build/releases https://api.github.com/repos/v2ray/v2ray-core/releases
 
 
 GO_INSTALL=golang.tar.gz
 GO_INSTALL=golang.tar.gz
-curl -L -o ${GO_INSTALL} https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz
+curl -L -o ${GO_INSTALL} https://storage.googleapis.com/golang/go1.9.4.linux-amd64.tar.gz
 tar -C /usr/local -xzf ${GO_INSTALL}
 tar -C /usr/local -xzf ${GO_INSTALL}
 export PATH=$PATH:/usr/local/go/bin
 export PATH=$PATH:/usr/local/go/bin
 
 
@@ -65,7 +65,7 @@ $GOPATH/bin/vbuild --os=openbsd --arch=amd64 --zip --sign #--encrypt
 RELBODY="https://www.v2ray.com/chapter_00/01_versions.html"
 RELBODY="https://www.v2ray.com/chapter_00/01_versions.html"
 JSON_DATA=$(echo "{}" | jq -c ".tag_name=\"${RELEASE_TAG}\"")
 JSON_DATA=$(echo "{}" | jq -c ".tag_name=\"${RELEASE_TAG}\"")
 JSON_DATA=$(echo ${JSON_DATA} | jq -c ".prerelease=${PRERELEASE}")
 JSON_DATA=$(echo ${JSON_DATA} | jq -c ".prerelease=${PRERELEASE}")
-JSON_DATA=$(echo ${JSON_DATA} | jq -c ".body=${RELBODY}")
+JSON_DATA=$(echo ${JSON_DATA} | jq -c ".body=\"${RELBODY}\"")
 RELEASE_ID=$(curl --data "${JSON_DATA}" -H "Authorization: token ${GITHUB_TOKEN}" -X POST https://api.github.com/repos/v2ray/v2ray-core/releases | jq ".id")
 RELEASE_ID=$(curl --data "${JSON_DATA}" -H "Authorization: token ${GITHUB_TOKEN}" -X POST https://api.github.com/repos/v2ray/v2ray-core/releases | jq ".id")
 
 
 function upload() {
 function upload() {

+ 115 - 0
router.go

@@ -0,0 +1,115 @@
+package core
+
+import (
+	"context"
+	"sync"
+
+	"v2ray.com/core/common"
+	"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() error {
+	d.RLock()
+	defer d.RUnlock()
+
+	return common.Close(d.Dispatcher)
+}
+
+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() error {
+	r.RLock()
+	defer r.RUnlock()
+
+	return common.Close(r.Router)
+}
+
+func (r *syncRouter) Set(router Router) {
+	r.Lock()
+	defer r.Unlock()
+
+	r.Router = router
+}

+ 130 - 0
testing/scenarios/command_test.go

@@ -0,0 +1,130 @@
+package scenarios
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"google.golang.org/grpc"
+	"v2ray.com/core"
+	"v2ray.com/core/app/commander"
+	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/app/proxyman/command"
+	"v2ray.com/core/app/router"
+	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/serial"
+	"v2ray.com/core/proxy/dokodemo"
+	"v2ray.com/core/proxy/freedom"
+	"v2ray.com/core/testing/servers/tcp"
+	. "v2ray.com/ext/assert"
+)
+
+func TestCommanderRemoveHandler(t *testing.T) {
+	assert := With(t)
+
+	tcpServer := tcp.Server{
+		MsgProcessor: xor,
+	}
+	dest, err := tcpServer.Start()
+	assert(err, IsNil)
+	defer tcpServer.Close()
+
+	clientPort := pickPort()
+	cmdPort := pickPort()
+	clientConfig := &core.Config{
+		App: []*serial.TypedMessage{
+			serial.ToTypedMessage(&commander.Config{
+				Tag: "api",
+				Service: []*serial.TypedMessage{
+					serial.ToTypedMessage(&command.Config{}),
+				},
+			}),
+			serial.ToTypedMessage(&router.Config{
+				Rule: []*router.RoutingRule{
+					{
+						InboundTag: []string{"api"},
+						Tag:        "api",
+					},
+				},
+			}),
+		},
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				Tag: "d",
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(clientPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP},
+					},
+				}),
+			},
+			{
+				Tag: "api",
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortRange: net.SinglePortRange(cmdPort),
+					Listen:    net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				Tag:           "default-outbound",
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	servers, err := InitializeServerConfigs(clientConfig)
+	assert(err, IsNil)
+
+	{
+		conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{
+			IP:   []byte{127, 0, 0, 1},
+			Port: int(clientPort),
+		})
+		assert(err, IsNil)
+
+		payload := "commander request."
+		nBytes, err := conn.Write([]byte(payload))
+		assert(err, IsNil)
+		assert(nBytes, Equals, len(payload))
+
+		response := make([]byte, 1024)
+		nBytes, err = conn.Read(response)
+		assert(err, IsNil)
+		assert(response[:nBytes], Equals, xor([]byte(payload)))
+		assert(conn.Close(), IsNil)
+	}
+
+	cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure())
+	assert(err, IsNil)
+
+	hsClient := command.NewHandlerServiceClient(cmdConn)
+	resp, err := hsClient.RemoveInbound(context.Background(), &command.RemoveInboundRequest{
+		Tag: "d",
+	})
+	assert(err, IsNil)
+	assert(resp, IsNotNil)
+
+	{
+		_, err := net.DialTCP("tcp", nil, &net.TCPAddr{
+			IP:   []byte{127, 0, 0, 1},
+			Port: int(clientPort),
+		})
+		assert(err, IsNotNil)
+	}
+
+	CloseAllServers(servers)
+}

+ 12 - 1
testing/scenarios/common.go

@@ -13,14 +13,17 @@ import (
 
 
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/proto"
 	"v2ray.com/core"
 	"v2ray.com/core"
+	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/retry"
 	"v2ray.com/core/common/retry"
+	"v2ray.com/core/common/serial"
 )
 )
 
 
 func pickPort() net.Port {
 func pickPort() net.Port {
-	listener, err := net.Listen("tcp4", ":0")
+	listener, err := net.Listen("tcp4", "127.0.0.1:0")
 	common.Must(err)
 	common.Must(err)
 	defer listener.Close()
 	defer listener.Close()
 
 
@@ -70,6 +73,7 @@ func InitializeServerConfig(config *core.Config) (*exec.Cmd, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	config = withDefaultApps(config)
 	configBytes, err := proto.Marshal(config)
 	configBytes, err := proto.Marshal(config)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -128,3 +132,10 @@ func CloseAllServers(servers []*exec.Cmd) {
 		Content:  "All server closed.",
 		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{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
 					PortRange: net.SinglePortRange(serverPort),
@@ -67,7 +67,7 @@ func TestResolveIP(t *testing.T) {
 				}),
 				}),
 			},
 			},
 		},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 			{
 				ProxySettings: serial.ToTypedMessage(&blackhole.Config{}),
 				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,
 				ErrorLogType:  log.LogType_Console,
 			}),
 			}),
 		},
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
 					PortRange: net.SinglePortRange(serverPort),
@@ -57,7 +57,7 @@ func TestDokodemoTCP(t *testing.T) {
 				}),
 				}),
 			},
 			},
 		},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 			},
@@ -73,7 +73,7 @@ func TestDokodemoTCP(t *testing.T) {
 				ErrorLogType:  log.LogType_Console,
 				ErrorLogType:  log.LogType_Console,
 			}),
 			}),
 		},
 		},
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange},
 					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{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 					Receiver: []*protocol.ServerEndpoint{
@@ -147,7 +147,7 @@ func TestDokodemoUDP(t *testing.T) {
 	userID := protocol.NewID(uuid.New())
 	userID := protocol.NewID(uuid.New())
 	serverPort := pickPort()
 	serverPort := pickPort()
 	serverConfig := &core.Config{
 	serverConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: net.SinglePortRange(serverPort),
 					PortRange: net.SinglePortRange(serverPort),
@@ -164,7 +164,7 @@ func TestDokodemoUDP(t *testing.T) {
 				}),
 				}),
 			},
 			},
 		},
 		},
-		Outbound: []*proxyman.OutboundHandlerConfig{
+		Outbound: []*core.OutboundHandlerConfig{
 			{
 			{
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
 			},
 			},
@@ -174,7 +174,7 @@ func TestDokodemoUDP(t *testing.T) {
 	clientPort := uint32(pickPort())
 	clientPort := uint32(pickPort())
 	clientPortRange := uint32(5)
 	clientPortRange := uint32(5)
 	clientConfig := &core.Config{
 	clientConfig := &core.Config{
-		Inbound: []*proxyman.InboundHandlerConfig{
+		Inbound: []*core.InboundHandlerConfig{
 			{
 			{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
 					PortRange: &net.PortRange{From: clientPort, To: clientPort + clientPortRange},
 					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{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
 					Receiver: []*protocol.ServerEndpoint{
 					Receiver: []*protocol.ServerEndpoint{

Some files were not shown because too many files changed in this diff