Bladeren bron

Merge branch 'master' of github.com:v2ray/v2ray-core into domainsocket

Shelikhoo 7 jaren geleden
bovenliggende
commit
c542c043f3
100 gewijzigde bestanden met toevoegingen van 2245 en 724 verwijderingen
  1. 1 1
      .travis.yml
  2. 12 6
      app/commander/commander.go
  3. 19 13
      app/commander/outbound.go
  4. 2 0
      app/commander/service.go
  5. 55 17
      app/dispatcher/default.go
  6. 2 2
      app/dispatcher/sniffer.go
  7. 18 19
      app/dns/nameserver.go
  8. 2 11
      app/dns/server.go
  9. 48 0
      app/log/command/command.go
  10. 38 0
      app/log/command/command_test.go
  11. 144 0
      app/log/command/config.pb.go
  12. 18 0
      app/log/command/config.proto
  13. 7 0
      app/log/command/errors.generated.go
  14. 51 16
      app/log/log.go
  15. 39 14
      app/policy/config.go
  16. 60 23
      app/policy/config.pb.go
  17. 6 0
      app/policy/config.proto
  18. 12 14
      app/policy/manager.go
  19. 37 0
      app/policy/manager_test.go
  20. 4 7
      app/proxyman/command/command.go
  21. 2 1
      app/proxyman/command/command.pb.go
  22. 1 4
      app/proxyman/inbound/dynamic.go
  23. 9 6
      app/proxyman/inbound/inbound.go
  24. 28 12
      app/proxyman/inbound/worker.go
  25. 46 71
      app/proxyman/mux/frame.go
  26. 45 17
      app/proxyman/mux/mux.go
  27. 7 12
      app/proxyman/mux/reader.go
  28. 9 4
      app/proxyman/mux/writer.go
  29. 8 13
      app/proxyman/outbound/handler.go
  30. 1 4
      app/proxyman/outbound/outbound.go
  31. 2 2
      app/proxyman/proxyman.go
  32. 6 5
      app/router/router.go
  33. 51 0
      app/stats/command/command.go
  34. 198 0
      app/stats/command/command.pb.go
  35. 29 0
      app/stats/command/command.proto
  36. 7 0
      app/stats/command/errors.generated.go
  37. 13 0
      app/stats/config.go
  38. 42 0
      app/stats/config.pb.go
  39. 11 0
      app/stats/config.proto
  40. 5 0
      app/stats/errors.generated.go
  41. 83 0
      app/stats/stats.go
  42. 32 0
      app/stats/stats_test.go
  43. 0 59
      clock.go
  44. 0 44
      commander.go
  45. 2 1
      common/buf/buf.go
  46. 27 31
      common/buf/buffer.go
  47. 46 35
      common/buf/buffer_pool.go
  48. 3 30
      common/buf/buffer_test.go
  49. 4 1
      common/buf/copy.go
  50. 2 1
      common/buf/io.go
  51. 30 8
      common/buf/multi_buffer.go
  52. 16 1
      common/buf/multi_buffer_test.go
  53. 50 23
      common/buf/reader.go
  54. 11 3
      common/buf/reader_test.go
  55. 4 1
      common/buf/writer.go
  56. 1 1
      common/buf/writer_test.go
  57. 4 5
      common/common.go
  58. 65 21
      common/crypto/auth.go
  59. 13 12
      common/crypto/auth_test.go
  60. 10 10
      common/crypto/chunk.go
  61. 4 4
      common/crypto/chunk_test.go
  62. 1 1
      common/crypto/crypto.go
  63. 1 1
      common/dice/dice.go
  64. 32 0
      common/dice/dice_test.go
  65. 51 2
      common/errors/errors.go
  66. 19 0
      common/interfaces.go
  67. 2 2
      common/log/log.go
  68. 1 1
      common/log/log_test.go
  69. 8 0
      common/log/logger.go
  70. 40 0
      common/log/logger_test.go
  71. 1 1
      common/net/address.go
  72. 3 3
      common/net/destination.go
  73. 1 1
      common/net/net.go
  74. 9 0
      common/net/network.go
  75. 3 3
      common/platform/platform.go
  76. 1 1
      common/predicate/predicate.go
  77. 1 1
      common/protocol/account.go
  78. 204 0
      common/protocol/address.go
  79. 118 0
      common/protocol/address_test.go
  80. 2 2
      common/protocol/context.go
  81. 6 19
      common/protocol/headers.go
  82. 1 1
      common/protocol/id.go
  83. 1 1
      common/protocol/payload.go
  84. 1 1
      common/protocol/protocol.go
  85. 4 4
      common/protocol/server_spec.go
  86. 1 1
      common/retry/retry.go
  87. 2 2
      common/serial/bytes.go
  88. 1 1
      common/serial/numbers.go
  89. 28 0
      common/serial/string_test.go
  90. 40 0
      common/session/session.go
  91. 2 2
      common/signal/done.go
  92. 1 1
      common/signal/exec.go
  93. 1 1
      common/signal/notifier.go
  94. 1 1
      common/signal/task.go
  95. 7 7
      common/uuid/uuid.go
  96. 88 0
      config.go
  97. 34 57
      config.pb.go
  98. 7 11
      config.proto
  99. 10 1
      context.go
  100. 9 9
      core.go

+ 1 - 1
.travis.yml

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

+ 12 - 6
app/commander/commander.go

@@ -13,6 +13,7 @@ import (
 	"v2ray.com/core/common/signal"
 )
 
+// Commander is a V2Ray feature that provides gRPC methods to external clients.
 type Commander struct {
 	sync.Mutex
 	server *grpc.Server
@@ -21,22 +22,26 @@ type Commander struct {
 	ohm    core.OutboundHandlerManager
 }
 
+// NewCommander creates a new Commander based on the given config.
 func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context.")
-	}
+	v := core.MustFromContext(ctx)
 	c := &Commander{
 		config: *config,
 		ohm:    v.OutboundHandlerManager(),
 		v:      v,
 	}
-	if err := v.RegisterFeature((*core.Commander)(nil), c); err != nil {
+	if err := v.RegisterFeature((*Commander)(nil), c); err != nil {
 		return nil, err
 	}
 	return c, nil
 }
 
+// Type implements common.HasType.
+func (c *Commander) Type() interface{} {
+	return (*Commander)(nil)
+}
+
+// Start implements common.Runnable.
 func (c *Commander) Start() error {
 	c.Lock()
 	c.server = grpc.NewServer()
@@ -69,13 +74,14 @@ func (c *Commander) Start() error {
 	}()
 
 	c.ohm.RemoveHandler(context.Background(), c.config.Tag)
-	c.ohm.AddHandler(context.Background(), &CommanderOutbound{
+	c.ohm.AddHandler(context.Background(), &Outbound{
 		tag:      c.config.Tag,
 		listener: listener,
 	})
 	return nil
 }
 
+// Close implements common.Closable.
 func (c *Commander) Close() error {
 	c.Lock()
 	defer c.Unlock()

+ 19 - 13
app/commander/outbound.go

@@ -5,6 +5,7 @@ import (
 	"net"
 	"sync"
 
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/signal"
 	"v2ray.com/core/transport/ray"
 )
@@ -24,17 +25,19 @@ func (l *OutboundListener) add(conn net.Conn) {
 	}
 }
 
+// Accept implements net.Listener.
 func (l *OutboundListener) Accept() (net.Conn, error) {
 	select {
 	case <-l.done.C():
-		return nil, newError("listern closed")
+		return nil, newError("listen closed")
 	case c := <-l.buffer:
 		return c, nil
 	}
 }
 
+// Close implement net.Listener.
 func (l *OutboundListener) Close() error {
-	l.done.Close()
+	common.Must(l.done.Close())
 L:
 	for {
 		select {
@@ -47,6 +50,7 @@ L:
 	return nil
 }
 
+// Addr implements net.Listener.
 func (l *OutboundListener) Addr() net.Addr {
 	return &net.TCPAddr{
 		IP:   net.IP{0, 0, 0, 0},
@@ -54,14 +58,16 @@ func (l *OutboundListener) Addr() net.Addr {
 	}
 }
 
-type CommanderOutbound struct {
+// Outbound is a core.OutboundHandler that handles gRPC connections.
+type Outbound struct {
 	tag      string
 	listener *OutboundListener
 	access   sync.RWMutex
 	closed   bool
 }
 
-func (co *CommanderOutbound) Dispatch(ctx context.Context, r ray.OutboundRay) {
+// Dispatch implements core.OutboundHandler.
+func (co *Outbound) Dispatch(ctx context.Context, r ray.OutboundRay) {
 	co.access.RLock()
 
 	if co.closed {
@@ -76,26 +82,26 @@ func (co *CommanderOutbound) Dispatch(ctx context.Context, r ray.OutboundRay) {
 	co.listener.add(c)
 	co.access.RUnlock()
 	<-closeSignal.Wait()
-
-	return
 }
 
-func (co *CommanderOutbound) Tag() string {
+// Tag implements core.OutboundHandler.
+func (co *Outbound) Tag() string {
 	return co.tag
 }
 
-func (co *CommanderOutbound) Start() error {
+// Start implements common.Runnable.
+func (co *Outbound) Start() error {
 	co.access.Lock()
 	co.closed = false
 	co.access.Unlock()
 	return nil
 }
 
-func (co *CommanderOutbound) Close() error {
+// Close implements common.Closable.
+func (co *Outbound) Close() error {
 	co.access.Lock()
-	co.closed = true
-	co.listener.Close()
-	co.access.Unlock()
+	defer co.access.Unlock()
 
-	return nil
+	co.closed = true
+	return co.listener.Close()
 }

+ 2 - 0
app/commander/service.go

@@ -4,6 +4,8 @@ import (
 	"google.golang.org/grpc"
 )
 
+// Service is a Commander service.
 type Service interface {
+	// Register registers the service itself to a gRPC server.
 	Register(*grpc.Server)
 }

+ 55 - 17
app/dispatcher/default.go

@@ -11,6 +11,7 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/ray"
 )
@@ -23,18 +24,18 @@ var (
 type DefaultDispatcher struct {
 	ohm    core.OutboundHandlerManager
 	router core.Router
+	policy core.PolicyManager
+	stats  core.StatManager
 }
 
 // NewDefaultDispatcher create a new DefaultDispatcher.
 func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context.")
-	}
-
+	v := core.MustFromContext(ctx)
 	d := &DefaultDispatcher{
 		ohm:    v.OutboundHandlerManager(),
 		router: v.Router(),
+		policy: v.PolicyManager(),
+		stats:  v.Stats(),
 	}
 
 	if err := v.RegisterFeature((*core.Dispatcher)(nil), d); err != nil {
@@ -43,14 +44,48 @@ func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatch
 	return d, nil
 }
 
-// Start implements app.Application.
+// Start implements common.Runnable.
 func (*DefaultDispatcher) Start() error {
 	return nil
 }
 
-// Close implements app.Application.
+// Close implements common.Closable.
 func (*DefaultDispatcher) Close() error { return nil }
 
+func (d *DefaultDispatcher) getStatCounter(name string) core.StatCounter {
+	c := d.stats.GetCounter(name)
+	if c != nil {
+		return c
+	}
+	c, err := d.stats.RegisterCounter(name)
+	if err != nil {
+		return nil
+	}
+	return c
+}
+
+func (d *DefaultDispatcher) getRayOption(user *protocol.User) []ray.Option {
+	var rayOptions []ray.Option
+
+	if user != nil && len(user.Email) > 0 {
+		p := d.policy.ForLevel(user.Level)
+		if p.Stats.UserUplink {
+			name := "user>>>" + user.Email + ">>>traffic>>>uplink"
+			if c := d.getStatCounter(name); c != nil {
+				rayOptions = append(rayOptions, ray.WithUplinkStatCounter(c))
+			}
+		}
+		if p.Stats.UserDownlink {
+			name := "user>>>" + user.Email + ">>>traffic>>>downlink"
+			if c := d.getStatCounter(name); c != nil {
+				rayOptions = append(rayOptions, ray.WithDownlinkStatCounter(c))
+			}
+		}
+	}
+
+	return rayOptions
+}
+
 // Dispatch implements core.Dispatcher.
 func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (ray.InboundRay, error) {
 	if !destination.IsValid() {
@@ -58,15 +93,18 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
 	}
 	ctx = proxy.ContextWithTarget(ctx, destination)
 
-	outbound := ray.NewRay(ctx)
-	sniferList := proxyman.ProtocoSniffersFromContext(ctx)
-	if destination.Address.Family().IsDomain() || len(sniferList) == 0 {
+	user := protocol.UserFromContext(ctx)
+	rayOptions := d.getRayOption(user)
+
+	outbound := ray.New(ctx, rayOptions...)
+	snifferList := proxyman.ProtocolSniffersFromContext(ctx)
+	if destination.Address.Family().IsDomain() || len(snifferList) == 0 {
 		go d.routedDispatch(ctx, outbound, destination)
 	} else {
 		go func() {
-			domain, err := snifer(ctx, sniferList, outbound)
+			domain, err := sniffer(ctx, snifferList, outbound)
 			if err == nil {
-				newError("sniffed domain: ", domain).WriteToLog()
+				newError("sniffed domain: ", domain).WithContext(ctx).WriteToLog()
 				destination.Address = net.ParseAddress(domain)
 				ctx = proxy.ContextWithTarget(ctx, destination)
 			}
@@ -76,11 +114,11 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
 	return outbound, nil
 }
 
-func snifer(ctx context.Context, sniferList []proxyman.KnownProtocols, outbound ray.OutboundRay) (string, error) {
+func sniffer(ctx context.Context, snifferList []proxyman.KnownProtocols, outbound ray.OutboundRay) (string, error) {
 	payload := buf.New()
 	defer payload.Release()
 
-	sniffer := NewSniffer(sniferList)
+	sniffer := NewSniffer(snifferList)
 	totalAttempt := 0
 	for {
 		select {
@@ -111,13 +149,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, outbound ray.Out
 	if d.router != nil {
 		if tag, err := d.router.PickRoute(ctx); err == nil {
 			if handler := d.ohm.GetHandler(tag); handler != nil {
-				newError("taking detour [", tag, "] for [", destination, "]").WriteToLog()
+				newError("taking detour [", tag, "] for [", destination, "]").WithContext(ctx).WriteToLog()
 				dispatcher = handler
 			} else {
-				newError("nonexisting tag: ", tag).AtWarning().WriteToLog()
+				newError("non existing tag: ", tag).AtWarning().WithContext(ctx).WriteToLog()
 			}
 		} else {
-			newError("default route for ", destination).WriteToLog()
+			newError("default route for ", destination).WithContext(ctx).WriteToLog()
 		}
 	}
 	dispatcher.Dispatch(ctx, outbound)

+ 2 - 2
app/dispatcher/sniffer.go

@@ -173,10 +173,10 @@ type Sniffer struct {
 	err   []error
 }
 
-func NewSniffer(sniferList []proxyman.KnownProtocols) *Sniffer {
+func NewSniffer(snifferList []proxyman.KnownProtocols) *Sniffer {
 	s := new(Sniffer)
 
-	for _, protocol := range sniferList {
+	for _, protocol := range snifferList {
 		var f func([]byte) (string, error)
 		switch protocol {
 		case proxyman.KnownProtocols_HTTP:

+ 18 - 19
app/dns/nameserver.go

@@ -7,17 +7,14 @@ import (
 
 	"github.com/miekg/dns"
 	"v2ray.com/core"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/common/signal"
 	"v2ray.com/core/transport/internet/udp"
 )
 
-const (
-	CleanupInterval  = time.Second * 120
-	CleanupThreshold = 512
-)
-
 var (
 	multiQuestionDNS = map[net.Address]bool{
 		net.IPAddress([]byte{8, 8, 8, 8}): true,
@@ -42,10 +39,10 @@ type PendingRequest struct {
 
 type UDPNameServer struct {
 	sync.Mutex
-	address     net.Destination
-	requests    map[uint16]*PendingRequest
-	udpServer   *udp.Dispatcher
-	nextCleanup time.Time
+	address   net.Destination
+	requests  map[uint16]*PendingRequest
+	udpServer *udp.Dispatcher
+	cleanup   *signal.PeriodicTask
 }
 
 func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPNameServer {
@@ -54,36 +51,35 @@ func NewUDPNameServer(address net.Destination, dispatcher core.Dispatcher) *UDPN
 		requests:  make(map[uint16]*PendingRequest),
 		udpServer: udp.NewDispatcher(dispatcher),
 	}
+	s.cleanup = &signal.PeriodicTask{
+		Interval: time.Minute,
+		Execute:  s.Cleanup,
+	}
+	common.Must(s.cleanup.Start())
 	return s
 }
 
-func (s *UDPNameServer) Cleanup() {
-	expiredRequests := make([]uint16, 0, 16)
+func (s *UDPNameServer) Cleanup() error {
 	now := time.Now()
 	s.Lock()
 	for id, r := range s.requests {
 		if r.expire.Before(now) {
-			expiredRequests = append(expiredRequests, id)
 			close(r.response)
+			delete(s.requests, id)
 		}
 	}
-	for _, id := range expiredRequests {
-		delete(s.requests, id)
-	}
 	s.Unlock()
+	return nil
 }
 
 func (s *UDPNameServer) AssignUnusedID(response chan<- *ARecord) uint16 {
 	var id uint16
 	s.Lock()
-	if len(s.requests) > CleanupThreshold && s.nextCleanup.Before(time.Now()) {
-		s.nextCleanup = time.Now().Add(CleanupInterval)
-		go s.Cleanup()
-	}
 
 	for {
 		id = dice.RollUint16()
 		if _, found := s.requests[id]; found {
+			time.Sleep(time.Millisecond * 500)
 			continue
 		}
 		newError("add pending request id ", id).AtDebug().WriteToLog()
@@ -182,6 +178,9 @@ func (s *UDPNameServer) QueryA(domain string) <-chan *ARecord {
 	b, err := msgToBuffer(msg)
 	if err != nil {
 		newError("failed to build A query for domain ", domain).Base(err).WriteToLog()
+		s.Lock()
+		delete(s.requests, id)
+		s.Unlock()
 		close(response)
 		return response
 	}

+ 2 - 11
app/dns/server.go

@@ -28,11 +28,6 @@ func (r *DomainRecord) Expired() bool {
 	return r.Expire.Before(time.Now())
 }
 
-func (r *DomainRecord) Inactive() bool {
-	now := time.Now()
-	return r.Expire.Before(now) || r.LastAccess.Add(time.Minute*5).Before(now)
-}
-
 type Server struct {
 	sync.Mutex
 	hosts   map[string]net.IP
@@ -54,11 +49,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 			return nil
 		},
 	}
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context.")
-	}
-
+	v := core.MustFromContext(ctx)
 	if err := v.RegisterFeature((*core.DNSClient)(nil), server); err != nil {
 		return nil, newError("unable to register DNSClient.").Base(err)
 	}
@@ -89,7 +80,7 @@ func (s *Server) Start() error {
 	return s.task.Start()
 }
 
-// Close implements common.Runnable.
+// Close implements common.Closable.
 func (s *Server) Close() error {
 	return s.task.Close()
 }

+ 48 - 0
app/log/command/command.go

@@ -0,0 +1,48 @@
+package command
+
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg command -path App,Log,Command
+
+import (
+	"context"
+
+	grpc "google.golang.org/grpc"
+
+	"v2ray.com/core"
+	"v2ray.com/core/app/log"
+	"v2ray.com/core/common"
+)
+
+type LoggerServer struct {
+	V *core.Instance
+}
+
+func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) {
+	logger := s.V.GetFeature((*log.Instance)(nil))
+	if logger == nil {
+		return nil, newError("unable to get logger instance")
+	}
+	if err := logger.Close(); err != nil {
+		return nil, newError("failed to close logger").Base(err)
+	}
+	if err := logger.Start(); err != nil {
+		return nil, newError("failed to start logger").Base(err)
+	}
+	return &RestartLoggerResponse{}, nil
+}
+
+type service struct {
+	v *core.Instance
+}
+
+func (s *service) Register(server *grpc.Server) {
+	RegisterLoggerServiceServer(server, &LoggerServer{
+		V: s.v,
+	})
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
+		s := core.MustFromContext(ctx)
+		return &service{v: s}, nil
+	}))
+}

+ 38 - 0
app/log/command/command_test.go

@@ -0,0 +1,38 @@
+package command_test
+
+import (
+	"context"
+	"testing"
+
+	"v2ray.com/core"
+	"v2ray.com/core/app/dispatcher"
+	"v2ray.com/core/app/log"
+	. "v2ray.com/core/app/log/command"
+	"v2ray.com/core/app/proxyman"
+	_ "v2ray.com/core/app/proxyman/inbound"
+	_ "v2ray.com/core/app/proxyman/outbound"
+	"v2ray.com/core/common/serial"
+	. "v2ray.com/ext/assert"
+)
+
+func TestLoggerRestart(t *testing.T) {
+	assert := With(t)
+
+	v, err := core.New(&core.Config{
+		App: []*serial.TypedMessage{
+			serial.ToTypedMessage(&log.Config{}),
+			serial.ToTypedMessage(&dispatcher.Config{}),
+			serial.ToTypedMessage(&proxyman.InboundConfig{}),
+			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
+		},
+	})
+
+	assert(err, IsNil)
+	assert(v.Start(), IsNil)
+
+	server := &LoggerServer{
+		V: v,
+	}
+	_, err = server.RestartLogger(context.Background(), &RestartLoggerRequest{})
+	assert(err, IsNil)
+}

+ 144 - 0
app/log/command/config.pb.go

@@ -0,0 +1,144 @@
+package command
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	"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 Config struct {
+}
+
+func (m *Config) Reset()                    { *m = Config{} }
+func (m *Config) String() string            { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()               {}
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+type RestartLoggerRequest struct {
+}
+
+func (m *RestartLoggerRequest) Reset()                    { *m = RestartLoggerRequest{} }
+func (m *RestartLoggerRequest) String() string            { return proto.CompactTextString(m) }
+func (*RestartLoggerRequest) ProtoMessage()               {}
+func (*RestartLoggerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+type RestartLoggerResponse struct {
+}
+
+func (m *RestartLoggerResponse) Reset()                    { *m = RestartLoggerResponse{} }
+func (m *RestartLoggerResponse) String() string            { return proto.CompactTextString(m) }
+func (*RestartLoggerResponse) ProtoMessage()               {}
+func (*RestartLoggerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func init() {
+	proto.RegisterType((*Config)(nil), "v2ray.core.app.log.command.Config")
+	proto.RegisterType((*RestartLoggerRequest)(nil), "v2ray.core.app.log.command.RestartLoggerRequest")
+	proto.RegisterType((*RestartLoggerResponse)(nil), "v2ray.core.app.log.command.RestartLoggerResponse")
+}
+
+// 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 LoggerService service
+
+type LoggerServiceClient interface {
+	RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)
+}
+
+type loggerServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewLoggerServiceClient(cc *grpc.ClientConn) LoggerServiceClient {
+	return &loggerServiceClient{cc}
+}
+
+func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) {
+	out := new(RestartLoggerResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.log.command.LoggerService/RestartLogger", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for LoggerService service
+
+type LoggerServiceServer interface {
+	RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
+}
+
+func RegisterLoggerServiceServer(s *grpc.Server, srv LoggerServiceServer) {
+	s.RegisterService(&_LoggerService_serviceDesc, srv)
+}
+
+func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(RestartLoggerRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(LoggerServiceServer).RestartLogger(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.log.command.LoggerService/RestartLogger",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _LoggerService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "v2ray.core.app.log.command.LoggerService",
+	HandlerType: (*LoggerServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "RestartLogger",
+			Handler:    _LoggerService_RestartLogger_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "v2ray.com/core/app/log/command/config.proto",
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/app/log/command/config.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 210 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2e, 0x33, 0x2a, 0x4a,
+	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0xcf,
+	0xc9, 0x4f, 0xd7, 0x4f, 0xce, 0xcf, 0xcd, 0x4d, 0xcc, 0x4b, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb,
+	0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x82, 0x29, 0x2e, 0x4a, 0xd5, 0x4b, 0x2c,
+	0x28, 0xd0, 0xcb, 0xc9, 0x4f, 0xd7, 0x83, 0x2a, 0x54, 0xe2, 0xe0, 0x62, 0x73, 0x06, 0xab, 0x55,
+	0x12, 0xe3, 0x12, 0x09, 0x4a, 0x2d, 0x2e, 0x49, 0x2c, 0x2a, 0xf1, 0xc9, 0x4f, 0x4f, 0x4f, 0x2d,
+	0x0a, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x51, 0x12, 0xe7, 0x12, 0x45, 0x13, 0x2f, 0x2e, 0xc8,
+	0xcf, 0x2b, 0x4e, 0x35, 0x6a, 0x67, 0xe4, 0xe2, 0x85, 0x08, 0x05, 0xa7, 0x16, 0x95, 0x65, 0x26,
+	0xa7, 0x0a, 0x95, 0x71, 0xf1, 0xa2, 0x28, 0x15, 0x32, 0xd0, 0xc3, 0x6d, 0xb5, 0x1e, 0x36, 0xdb,
+	0xa4, 0x0c, 0x49, 0xd0, 0x01, 0x71, 0x87, 0x12, 0x83, 0x93, 0x07, 0x97, 0x5c, 0x72, 0x7e, 0x2e,
+	0x1e, 0x9d, 0x01, 0x8c, 0x51, 0xec, 0x50, 0xe6, 0x2a, 0x26, 0xa9, 0x30, 0xa3, 0xa0, 0xc4, 0x4a,
+	0x3d, 0x67, 0x90, 0x3a, 0xc7, 0x82, 0x02, 0x3d, 0x9f, 0xfc, 0x74, 0x3d, 0x67, 0x88, 0x64, 0x12,
+	0x1b, 0x38, 0xc4, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x37, 0xc7, 0xfc, 0xda, 0x60, 0x01,
+	0x00, 0x00,
+}

+ 18 - 0
app/log/command/config.proto

@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+package v2ray.core.app.log.command;
+option csharp_namespace = "V2Ray.Core.App.Log.Command";
+option go_package = "command";
+option java_package = "com.v2ray.core.app.log.command";
+option java_multiple_files = true;
+
+message Config {
+}
+
+message RestartLoggerRequest {}
+
+message RestartLoggerResponse{}
+
+service LoggerService {
+  rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}
+}

+ 7 - 0
app/log/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", "Log", "Command")
+}

+ 51 - 16
app/log/log.go

@@ -6,11 +6,12 @@ import (
 	"context"
 	"sync"
 
+	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/log"
 )
 
-// Instance is an app.Application that handles logs.
+// Instance is a log.Handler that handles logs.
 type Instance struct {
 	sync.RWMutex
 	config       *Config
@@ -23,16 +24,14 @@ type Instance struct {
 func New(ctx context.Context, config *Config) (*Instance, error) {
 	g := &Instance{
 		config: config,
-		active: true,
+		active: false,
 	}
+	log.RegisterHandler(g)
 
-	if err := g.initAccessLogger(); err != nil {
-		return nil, newError("failed to initialize access logger").Base(err).AtWarning()
-	}
-	if err := g.initErrorLogger(); err != nil {
-		return nil, newError("failed to initialize error logger").Base(err).AtWarning()
+	v := core.FromContext(ctx)
+	if v != nil {
+		common.Must(v.RegisterFeature((*log.Handler)(nil), g))
 	}
-	log.RegisterHandler(g)
 
 	return g, nil
 }
@@ -67,24 +66,48 @@ func (g *Instance) initErrorLogger() error {
 	return nil
 }
 
-// Start implements app.Application.Start().
-func (g *Instance) Start() error {
+// Type implements common.HasType.
+func (*Instance) Type() interface{} {
+	return (*Instance)(nil)
+}
+
+func (g *Instance) startInternal() error {
 	g.Lock()
 	defer g.Unlock()
+
+	if g.active {
+		return nil
+	}
+
 	g.active = true
+
+	if err := g.initAccessLogger(); err != nil {
+		return newError("failed to initialize access logger").Base(err).AtWarning()
+	}
+	if err := g.initErrorLogger(); err != nil {
+		return newError("failed to initialize error logger").Base(err).AtWarning()
+	}
+
 	return nil
 }
 
-func (g *Instance) isActive() bool {
-	g.RLock()
-	defer g.RUnlock()
+// Start implements common.Runnable.Start().
+func (g *Instance) Start() error {
+	if err := g.startInternal(); err != nil {
+		return err
+	}
+
+	newError("Logger started").AtDebug().WriteToLog()
 
-	return g.active
+	return nil
 }
 
 // Handle implements log.Handler.
 func (g *Instance) Handle(msg log.Message) {
-	if !g.isActive() {
+	g.RLock()
+	defer g.RUnlock()
+
+	if !g.active {
 		return
 	}
 
@@ -102,13 +125,25 @@ func (g *Instance) Handle(msg log.Message) {
 	}
 }
 
-// Close implement app.Application.Close().
+// Close implements common.Closable.Close().
 func (g *Instance) Close() error {
+	newError("Logger closing").AtDebug().WriteToLog()
+
 	g.Lock()
 	defer g.Unlock()
 
+	if !g.active {
+		return nil
+	}
+
 	g.active = false
 
+	common.Close(g.accessLogger)
+	g.accessLogger = nil
+
+	common.Close(g.errorLogger)
+	g.errorLogger = nil
+
 	return nil
 }
 

+ 39 - 14
app/policy/config.go

@@ -14,24 +14,45 @@ func (s *Second) Duration() time.Duration {
 	return time.Second * time.Duration(s.Value)
 }
 
-// OverrideWith overrides current Policy with another one.
-func (p *Policy) OverrideWith(another *Policy) {
+func defaultPolicy() *Policy {
+	p := core.DefaultPolicy()
+
+	return &Policy{
+		Timeout: &Policy_Timeout{
+			Handshake:      &Second{Value: uint32(p.Timeouts.Handshake / time.Second)},
+			ConnectionIdle: &Second{Value: uint32(p.Timeouts.ConnectionIdle / time.Second)},
+			UplinkOnly:     &Second{Value: uint32(p.Timeouts.UplinkOnly / time.Second)},
+			DownlinkOnly:   &Second{Value: uint32(p.Timeouts.DownlinkOnly / time.Second)},
+		},
+	}
+}
+
+func (p *Policy_Timeout) overrideWith(another *Policy_Timeout) {
+	if another.Handshake != nil {
+		p.Handshake = &Second{Value: another.Handshake.Value}
+	}
+	if another.ConnectionIdle != nil {
+		p.ConnectionIdle = &Second{Value: another.ConnectionIdle.Value}
+	}
+	if another.UplinkOnly != nil {
+		p.UplinkOnly = &Second{Value: another.UplinkOnly.Value}
+	}
+	if another.DownlinkOnly != nil {
+		p.DownlinkOnly = &Second{Value: another.DownlinkOnly.Value}
+	}
+}
+
+func (p *Policy) overrideWith(another *Policy) {
 	if another.Timeout != nil {
-		if another.Timeout.Handshake != nil {
-			p.Timeout.Handshake = another.Timeout.Handshake
-		}
-		if another.Timeout.ConnectionIdle != nil {
-			p.Timeout.ConnectionIdle = another.Timeout.ConnectionIdle
-		}
-		if another.Timeout.UplinkOnly != nil {
-			p.Timeout.UplinkOnly = another.Timeout.UplinkOnly
-		}
-		if another.Timeout.DownlinkOnly != nil {
-			p.Timeout.DownlinkOnly = another.Timeout.DownlinkOnly
-		}
+		p.Timeout.overrideWith(another.Timeout)
+	}
+	if another.Stats != nil && p.Stats == nil {
+		p.Stats = new(Policy_Stats)
+		*p.Stats = *another.Stats
 	}
 }
 
+// ToCorePolicy converts this Policy to core.Policy.
 func (p *Policy) ToCorePolicy() core.Policy {
 	var cp core.Policy
 	if p.Timeout != nil {
@@ -40,5 +61,9 @@ func (p *Policy) ToCorePolicy() core.Policy {
 		cp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration()
 		cp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration()
 	}
+	if p.Stats != nil {
+		cp.Stats.UserUplink = p.Stats.UserUplink
+		cp.Stats.UserDownlink = p.Stats.UserDownlink
+	}
 	return cp
 }

+ 60 - 23
app/policy/config.pb.go

@@ -33,6 +33,7 @@ func (m *Second) GetValue() uint32 {
 
 type Policy struct {
 	Timeout *Policy_Timeout `protobuf:"bytes,1,opt,name=timeout" json:"timeout,omitempty"`
+	Stats   *Policy_Stats   `protobuf:"bytes,2,opt,name=stats" json:"stats,omitempty"`
 }
 
 func (m *Policy) Reset()                    { *m = Policy{} }
@@ -47,6 +48,13 @@ func (m *Policy) GetTimeout() *Policy_Timeout {
 	return nil
 }
 
+func (m *Policy) GetStats() *Policy_Stats {
+	if m != nil {
+		return m.Stats
+	}
+	return nil
+}
+
 // Timeout is a message for timeout settings in various stages, in seconds.
 type Policy_Timeout struct {
 	Handshake      *Second `protobuf:"bytes,1,opt,name=handshake" json:"handshake,omitempty"`
@@ -88,6 +96,30 @@ func (m *Policy_Timeout) GetDownlinkOnly() *Second {
 	return nil
 }
 
+type Policy_Stats struct {
+	UserUplink   bool `protobuf:"varint,1,opt,name=user_uplink,json=userUplink" json:"user_uplink,omitempty"`
+	UserDownlink bool `protobuf:"varint,2,opt,name=user_downlink,json=userDownlink" json:"user_downlink,omitempty"`
+}
+
+func (m *Policy_Stats) Reset()                    { *m = Policy_Stats{} }
+func (m *Policy_Stats) String() string            { return proto.CompactTextString(m) }
+func (*Policy_Stats) ProtoMessage()               {}
+func (*Policy_Stats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 1} }
+
+func (m *Policy_Stats) GetUserUplink() bool {
+	if m != nil {
+		return m.UserUplink
+	}
+	return false
+}
+
+func (m *Policy_Stats) GetUserDownlink() bool {
+	if m != nil {
+		return m.UserDownlink
+	}
+	return false
+}
+
 type Config struct {
 	Level map[uint32]*Policy `protobuf:"bytes,1,rep,name=level" json:"level,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
 }
@@ -108,33 +140,38 @@ func init() {
 	proto.RegisterType((*Second)(nil), "v2ray.core.app.policy.Second")
 	proto.RegisterType((*Policy)(nil), "v2ray.core.app.policy.Policy")
 	proto.RegisterType((*Policy_Timeout)(nil), "v2ray.core.app.policy.Policy.Timeout")
+	proto.RegisterType((*Policy_Stats)(nil), "v2ray.core.app.policy.Policy.Stats")
 	proto.RegisterType((*Config)(nil), "v2ray.core.app.policy.Config")
 }
 
 func init() { proto.RegisterFile("v2ray.com/core/app/policy/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 349 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xc1, 0x4a, 0xeb, 0x40,
-	0x14, 0x86, 0x49, 0x7a, 0x9b, 0x72, 0x4f, 0x6f, 0xaf, 0x32, 0x58, 0x88, 0x05, 0xa5, 0x14, 0x94,
-	0xae, 0x26, 0x90, 0x6e, 0x44, 0xb1, 0x62, 0x45, 0x41, 0x10, 0x2c, 0x51, 0x14, 0xdc, 0x94, 0x71,
-	0x32, 0xda, 0xd0, 0xe9, 0x9c, 0x21, 0xa6, 0x95, 0xbc, 0x86, 0x6f, 0xe0, 0xd6, 0x87, 0xf2, 0x59,
-	0x24, 0x99, 0x84, 0x6c, 0x5a, 0xe9, 0x6e, 0x72, 0xf8, 0xfe, 0x8f, 0x43, 0xfe, 0x03, 0x87, 0x4b,
-	0x3f, 0x66, 0x29, 0xe5, 0x38, 0xf7, 0x38, 0xc6, 0xc2, 0x63, 0x5a, 0x7b, 0x1a, 0x65, 0xc4, 0x53,
-	0x8f, 0xa3, 0x7a, 0x89, 0x5e, 0xa9, 0x8e, 0x31, 0x41, 0xd2, 0x2e, 0xb9, 0x58, 0x50, 0xa6, 0x35,
-	0x35, 0x4c, 0x6f, 0x1f, 0x9c, 0x3b, 0xc1, 0x51, 0x85, 0x64, 0x07, 0xea, 0x4b, 0x26, 0x17, 0xc2,
-	0xb5, 0xba, 0x56, 0xbf, 0x15, 0x98, 0x8f, 0xde, 0xb7, 0x0d, 0xce, 0x38, 0x47, 0xc9, 0x19, 0x34,
-	0x92, 0x68, 0x2e, 0x70, 0x91, 0xe4, 0x48, 0xd3, 0x3f, 0xa0, 0x2b, 0x9d, 0xd4, 0xf0, 0xf4, 0xde,
-	0xc0, 0x41, 0x99, 0xea, 0x7c, 0xd8, 0xd0, 0x28, 0x86, 0xe4, 0x04, 0xfe, 0x4e, 0x99, 0x0a, 0xdf,
-	0xa6, 0x6c, 0x26, 0x0a, 0xdd, 0xde, 0x1a, 0x9d, 0xd9, 0x2f, 0xa8, 0x78, 0x72, 0x05, 0x5b, 0x1c,
-	0x95, 0x12, 0x3c, 0x89, 0x50, 0x4d, 0xa2, 0x50, 0x0a, 0xd7, 0xde, 0x44, 0xf1, 0xbf, 0x4a, 0x5d,
-	0x87, 0x52, 0x90, 0x21, 0x34, 0x17, 0x5a, 0x46, 0x6a, 0x36, 0x41, 0x25, 0x53, 0xb7, 0xb6, 0x89,
-	0x03, 0x4c, 0xe2, 0x56, 0xc9, 0x94, 0x8c, 0xa0, 0x15, 0xe2, 0xbb, 0xaa, 0x0c, 0x7f, 0x36, 0x31,
-	0xfc, 0x2b, 0x33, 0x99, 0xa3, 0xf7, 0x69, 0x81, 0x73, 0x91, 0x17, 0x45, 0x86, 0x50, 0x97, 0x62,
-	0x29, 0xa4, 0x6b, 0x75, 0x6b, 0xfd, 0xa6, 0xdf, 0x5f, 0xa3, 0x31, 0x34, 0xbd, 0xc9, 0xd0, 0x4b,
-	0x95, 0xc4, 0x69, 0x60, 0x62, 0x9d, 0x47, 0x80, 0x6a, 0x48, 0xb6, 0xa1, 0x36, 0x13, 0x69, 0xd1,
-	0x66, 0xf6, 0x24, 0x83, 0xb2, 0xe1, 0xdf, 0x7f, 0x96, 0xa9, 0xaf, 0x38, 0x80, 0x63, 0xfb, 0xc8,
-	0x1a, 0x9d, 0xc2, 0x2e, 0xc7, 0xf9, 0x6a, 0x7c, 0x6c, 0x3d, 0x39, 0xe6, 0xf5, 0x65, 0xb7, 0x1f,
-	0xfc, 0x80, 0x65, 0x0b, 0xc6, 0x82, 0x9e, 0x6b, 0x5d, 0x98, 0x9e, 0x9d, 0xfc, 0x02, 0x07, 0x3f,
-	0x01, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x25, 0x25, 0xc2, 0xab, 0x02, 0x00, 0x00,
+	// 410 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x5d, 0xab, 0xd3, 0x30,
+	0x18, 0xc7, 0x69, 0x6b, 0x7b, 0x8e, 0x4f, 0xcf, 0x54, 0x82, 0x07, 0xea, 0x40, 0x3d, 0x6c, 0x28,
+	0xbb, 0x4a, 0xa1, 0xbb, 0xf1, 0x05, 0x27, 0xce, 0x17, 0x10, 0x14, 0x47, 0xe6, 0x0b, 0x78, 0x33,
+	0x62, 0x1a, 0x5d, 0x59, 0x96, 0x84, 0xbe, 0x4c, 0xfa, 0x35, 0xfc, 0x06, 0xde, 0xfa, 0xc9, 0xfc,
+	0x18, 0xd2, 0xa4, 0xa5, 0x37, 0xdb, 0xdc, 0x5d, 0xfa, 0xf0, 0xfb, 0xff, 0x78, 0x12, 0xfe, 0x85,
+	0x87, 0xbb, 0x24, 0xa7, 0x35, 0x66, 0x6a, 0x1b, 0x33, 0x95, 0xf3, 0x98, 0x6a, 0x1d, 0x6b, 0x25,
+	0x32, 0x56, 0xc7, 0x4c, 0xc9, 0xef, 0xd9, 0x0f, 0xac, 0x73, 0x55, 0x2a, 0x74, 0xd9, 0x71, 0x39,
+	0xc7, 0x54, 0x6b, 0x6c, 0x99, 0xd1, 0x3d, 0x08, 0x96, 0x9c, 0x29, 0x99, 0xa2, 0xdb, 0xe0, 0xef,
+	0xa8, 0xa8, 0x78, 0xe4, 0x5c, 0x39, 0x93, 0x01, 0xb1, 0x1f, 0xa3, 0xbf, 0x1e, 0x04, 0x0b, 0x83,
+	0xa2, 0xe7, 0x70, 0x56, 0x66, 0x5b, 0xae, 0xaa, 0xd2, 0x20, 0x61, 0xf2, 0x00, 0xef, 0x75, 0x62,
+	0xcb, 0xe3, 0x8f, 0x16, 0x26, 0x5d, 0x0a, 0x3d, 0x06, 0xbf, 0x28, 0x69, 0x59, 0x44, 0xae, 0x89,
+	0x8f, 0x8f, 0xc7, 0x97, 0x0d, 0x4a, 0x6c, 0x62, 0xf8, 0xcb, 0x85, 0xb3, 0xd6, 0x87, 0x9e, 0xc2,
+	0xf5, 0x35, 0x95, 0x69, 0xb1, 0xa6, 0x1b, 0xde, 0x6e, 0x72, 0xf7, 0x80, 0xca, 0x5e, 0x8d, 0xf4,
+	0x3c, 0x7a, 0x03, 0x37, 0x99, 0x92, 0x92, 0xb3, 0x32, 0x53, 0x72, 0x95, 0xa5, 0x82, 0xb7, 0xdb,
+	0xfc, 0x47, 0x71, 0xa3, 0x4f, 0xbd, 0x4d, 0x05, 0x47, 0x33, 0x08, 0x2b, 0x2d, 0x32, 0xb9, 0x59,
+	0x29, 0x29, 0xea, 0xc8, 0x3b, 0xc5, 0x01, 0x36, 0xf1, 0x41, 0x8a, 0x1a, 0xcd, 0x61, 0x90, 0xaa,
+	0x9f, 0xb2, 0x37, 0x5c, 0x3b, 0xc5, 0x70, 0xd1, 0x65, 0x1a, 0xc7, 0xf0, 0x3d, 0xf8, 0xe6, 0x91,
+	0xd0, 0x7d, 0x08, 0xab, 0x82, 0xe7, 0x2b, 0xeb, 0x37, 0x6f, 0x72, 0x4e, 0xa0, 0x19, 0x7d, 0x32,
+	0x13, 0x34, 0x86, 0x81, 0x01, 0xba, 0xb8, 0xb9, 0xf3, 0x39, 0xb9, 0x68, 0x86, 0xaf, 0xda, 0xd9,
+	0xe8, 0xb7, 0x03, 0xc1, 0x4b, 0x53, 0x19, 0x34, 0x03, 0x5f, 0xf0, 0x1d, 0x17, 0x91, 0x73, 0xe5,
+	0x4d, 0xc2, 0x64, 0x72, 0x60, 0x2b, 0x4b, 0xe3, 0x77, 0x0d, 0xfa, 0x5a, 0x96, 0x79, 0x4d, 0x6c,
+	0x6c, 0xf8, 0x05, 0xa0, 0x1f, 0xa2, 0x5b, 0xe0, 0x6d, 0x78, 0xdd, 0xf6, 0xaa, 0x39, 0xa2, 0x69,
+	0xd7, 0xb5, 0xe3, 0x6f, 0x6f, 0x9b, 0xd0, 0x56, 0xf1, 0x89, 0xfb, 0xc8, 0x99, 0x3f, 0x83, 0x3b,
+	0x4c, 0x6d, 0xf7, 0xe3, 0x0b, 0xe7, 0x6b, 0x60, 0x4f, 0x7f, 0xdc, 0xcb, 0xcf, 0x09, 0xa1, 0xcd,
+	0x82, 0x39, 0xc7, 0x2f, 0xb4, 0x6e, 0x4d, 0xdf, 0x02, 0xf3, 0x2f, 0x4c, 0xff, 0x05, 0x00, 0x00,
+	0xff, 0xff, 0x98, 0xa8, 0x2f, 0xbe, 0x35, 0x03, 0x00, 0x00,
 }

+ 6 - 0
app/policy/config.proto

@@ -19,7 +19,13 @@ message Policy {
     Second downlink_only = 4;
   }
 
+  message Stats {
+    bool user_uplink = 1;
+    bool user_downlink = 2;
+  }
+
   Timeout timeout = 1;
+  Stats stats = 2;
 }
 
 message Config {

+ 12 - 14
app/policy/manager.go

@@ -9,29 +9,27 @@ import (
 
 // Instance is an instance of Policy manager.
 type Instance struct {
-	levels map[uint32]core.Policy
+	levels map[uint32]*Policy
 }
 
 // New creates new Policy manager instance.
 func New(ctx context.Context, config *Config) (*Instance, error) {
 	m := &Instance{
-		levels: make(map[uint32]core.Policy),
+		levels: make(map[uint32]*Policy),
 	}
 	if len(config.Level) > 0 {
 		for lv, p := range config.Level {
-			dp := core.DefaultPolicy()
-			dp.OverrideWith(p.ToCorePolicy())
-			m.levels[lv] = dp
+			pp := defaultPolicy()
+			pp.overrideWith(p)
+			m.levels[lv] = pp
 		}
 	}
 
 	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()
+	if v != nil {
+		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
@@ -40,17 +38,17 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
 // ForLevel implements core.PolicyManager.
 func (m *Instance) ForLevel(level uint32) core.Policy {
 	if p, ok := m.levels[level]; ok {
-		return p
+		return p.ToCorePolicy()
 	}
 	return core.DefaultPolicy()
 }
 
-// Start implements app.Application.Start().
+// Start implements common.Runnable.Start().
 func (m *Instance) Start() error {
 	return nil
 }
 
-// Close implements app.Application.Close().
+// Close implements common.Closable.Close().
 func (m *Instance) Close() error {
 	return nil
 }

+ 37 - 0
app/policy/manager_test.go

@@ -0,0 +1,37 @@
+package policy_test
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"v2ray.com/core"
+	. "v2ray.com/core/app/policy"
+	. "v2ray.com/ext/assert"
+)
+
+func TestPolicy(t *testing.T) {
+	assert := With(t)
+
+	manager, err := New(context.Background(), &Config{
+		Level: map[uint32]*Policy{
+			0: {
+				Timeout: &Policy_Timeout{
+					Handshake: &Second{
+						Value: 2,
+					},
+				},
+			},
+		},
+	})
+	assert(err, IsNil)
+
+	pDefault := core.DefaultPolicy()
+
+	p0 := manager.ForLevel(0)
+	assert(p0.Timeouts.Handshake, Equals, 2*time.Second)
+	assert(p0.Timeouts.ConnectionIdle, Equals, pDefault.Timeouts.ConnectionIdle)
+
+	p1 := manager.ForLevel(1)
+	assert(p1.Timeouts.Handshake, Equals, pDefault.Timeouts.Handshake)
+}

+ 4 - 7
app/proxyman/command/command.go

@@ -11,7 +11,7 @@ import (
 
 // InboundOperation is the interface for operations that applies to inbound handlers.
 type InboundOperation interface {
-	// ApplyInbound appliess this operation to the given inbound handler.
+	// ApplyInbound applies this operation to the given inbound handler.
 	ApplyInbound(context.Context, core.InboundHandler) error
 }
 
@@ -37,7 +37,7 @@ func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler core.Inbou
 	}
 	um, ok := p.(proxy.UserManager)
 	if !ok {
-		return newError("proxy is not an UserManager")
+		return newError("proxy is not a UserManager")
 	}
 	return um.AddUser(ctx, op.User)
 }
@@ -50,7 +50,7 @@ func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler core.In
 	}
 	um, ok := p.(proxy.UserManager)
 	if !ok {
-		return newError("proxy is not an UserManager")
+		return newError("proxy is not a UserManager")
 	}
 	return um.RemoveUser(ctx, op.Email)
 }
@@ -139,10 +139,7 @@ func (s *service) Register(server *grpc.Server) {
 
 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.")
-		}
+		s := core.MustFromContext(ctx)
 		return &service{v: s}, nil
 	}))
 }

+ 2 - 1
app/proxyman/command/command.pb.go

@@ -8,7 +8,8 @@ import v2ray_core_common_serial "v2ray.com/core/common/serial"
 import v2ray_core "v2ray.com/core"
 
 import (
-	context "golang.org/x/net/context"
+	"context"
+
 	grpc "google.golang.org/grpc"
 )
 

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

@@ -29,10 +29,7 @@ type DynamicInboundHandler struct {
 }
 
 func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context.")
-	}
+	v := core.MustFromContext(ctx)
 	h := &DynamicInboundHandler{
 		tag:            tag,
 		proxyConfig:    proxyConfig,

+ 9 - 6
app/proxyman/inbound/inbound.go

@@ -24,10 +24,7 @@ func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error)
 	m := &Manager{
 		taggedHandlers: make(map[string]core.InboundHandler),
 	}
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context")
-	}
+	v := core.MustFromContext(ctx)
 	if err := v.RegisterFeature((*core.InboundHandlerManager)(nil), m); err != nil {
 		return nil, newError("unable to register InboundHandlerManager").Base(err)
 	}
@@ -53,7 +50,7 @@ func (m *Manager) AddHandler(ctx context.Context, handler core.InboundHandler) e
 	return nil
 }
 
-// GetHandler returns core.InboundHandlerManager.
+// GetHandler implements core.InboundHandlerManager.
 func (m *Manager) GetHandler(ctx context.Context, tag string) (core.InboundHandler, error) {
 	m.access.RLock()
 	defer m.access.RUnlock()
@@ -65,6 +62,7 @@ func (m *Manager) GetHandler(ctx context.Context, tag string) (core.InboundHandl
 	return handler, nil
 }
 
+// RemoveHandler implements core.InboundHandlerManager.
 func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
 	if len(tag) == 0 {
 		return core.ErrNoClue
@@ -74,7 +72,9 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
 	defer m.access.Unlock()
 
 	if handler, found := m.taggedHandlers[tag]; found {
-		handler.Close()
+		if err := handler.Close(); err != nil {
+			newError("failed to close handler ", tag).Base(err).AtWarning().WithContext(ctx).WriteToLog()
+		}
 		delete(m.taggedHandlers, tag)
 		return nil
 	}
@@ -82,6 +82,7 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
 	return core.ErrNoClue
 }
 
+// Start implements common.Runnable.
 func (m *Manager) Start() error {
 	m.access.Lock()
 	defer m.access.Unlock()
@@ -102,6 +103,7 @@ func (m *Manager) Start() error {
 	return nil
 }
 
+// Close implements common.Closable.
 func (m *Manager) Close() error {
 	m.access.Lock()
 	defer m.access.Unlock()
@@ -118,6 +120,7 @@ func (m *Manager) Close() error {
 	return nil
 }
 
+// NewHandler creates a new core.InboundHandler based on the given config.
 func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (core.InboundHandler, error) {
 	rawReceiverSettings, err := config.ReceiverSettings.GetInstance()
 	if err != nil {

+ 28 - 12
app/proxyman/inbound/worker.go

@@ -7,6 +7,8 @@ import (
 	"sync/atomic"
 	"time"
 
+	"v2ray.com/core/common/session"
+
 	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
@@ -41,10 +43,13 @@ type tcpWorker struct {
 
 func (w *tcpWorker) callback(conn internet.Connection) {
 	ctx, cancel := context.WithCancel(context.Background())
+	sid := session.NewID()
+	ctx = session.ContextWithID(ctx, sid)
+
 	if w.recvOrigDest {
 		dest, err := tcp.GetOriginalDestination(conn)
 		if err != nil {
-			newError("failed to get original destination").Base(err).WriteToLog()
+			newError("failed to get original destination").WithContext(ctx).Base(err).WriteToLog()
 		}
 		if dest.IsValid() {
 			ctx = proxy.ContextWithOriginalTarget(ctx, dest)
@@ -59,10 +64,12 @@ func (w *tcpWorker) callback(conn internet.Connection) {
 		ctx = proxyman.ContextWithProtocolSniffers(ctx, w.sniffers)
 	}
 	if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
-		newError("connection ends").Base(err).WriteToLog()
+		newError("connection ends").Base(err).WithContext(ctx).WriteToLog()
 	}
 	cancel()
-	conn.Close()
+	if err := conn.Close(); err != nil {
+		newError("failed to close connection").Base(err).WithContext(ctx).WriteToLog()
+	}
 }
 
 func (w *tcpWorker) Proxy() proxy.Inbound {
@@ -128,7 +135,7 @@ func (c *udpConn) Write(buf []byte) (int, error) {
 }
 
 func (c *udpConn) Close() error {
-	common.Close(c.done)
+	common.Must(c.done.Close())
 	return nil
 }
 
@@ -203,8 +210,10 @@ func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
 
 func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) {
 	id := connID{
-		src:  source,
-		dest: originalDest,
+		src: source,
+	}
+	if originalDest.IsValid() {
+		id.dest = originalDest
 	}
 	conn, existing := w.getConnection(id)
 	select {
@@ -218,6 +227,9 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
 	if !existing {
 		go func() {
 			ctx := context.Background()
+			sid := session.NewID()
+			ctx = session.ContextWithID(ctx, sid)
+
 			if originalDest.IsValid() {
 				ctx = proxy.ContextWithOriginalTarget(ctx, originalDest)
 			}
@@ -244,10 +256,7 @@ func (w *udpWorker) removeConn(id connID) {
 func (w *udpWorker) Start() error {
 	w.activeConn = make(map[connID]*udpConn, 16)
 	w.done = signal.NewDone()
-	h, err := udp.ListenUDP(w.address, w.port, udp.ListenOption{
-		Callback:            w.callback,
-		ReceiveOriginalDest: w.recvOrigDest,
-	})
+	h, err := udp.ListenUDP(w.address, w.port, w.callback, udp.HubReceiveOriginalDestination(w.recvOrigDest), udp.HubCapacity(256))
 	if err != nil {
 		return err
 	}
@@ -257,11 +266,18 @@ func (w *udpWorker) Start() error {
 }
 
 func (w *udpWorker) Close() error {
+	w.Lock()
+	defer w.Unlock()
+
 	if w.hub != nil {
 		w.hub.Close()
-		w.done.Close()
-		common.Close(w.proxy)
 	}
+
+	if w.done != nil {
+		common.Must(w.done.Close())
+	}
+
+	common.Close(w.proxy)
 	return nil
 }
 

+ 46 - 71
app/proxyman/mux/frame.go

@@ -18,7 +18,8 @@ const (
 )
 
 const (
-	OptionData bitmask.Byte = 0x01
+	OptionData  bitmask.Byte = 0x01
+	OptionError bitmask.Byte = 0x02
 )
 
 type TargetNetwork byte
@@ -28,6 +29,13 @@ const (
 	TargetNetworkUDP TargetNetwork = 0x02
 )
 
+var addrParser = protocol.NewAddressParser(
+	protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),
+	protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),
+	protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),
+	protocol.PortThenAddress(),
+)
+
 /*
 Frame format
 2 bytes - length
@@ -48,88 +56,55 @@ type FrameMetadata struct {
 	SessionStatus SessionStatus
 }
 
-func (f FrameMetadata) AsSupplier() buf.Supplier {
-	return func(b []byte) (int, error) {
-		lengthBytes := b
-		b = serial.Uint16ToBytes(uint16(0), b[:0]) // place holder for length
-
-		b = serial.Uint16ToBytes(f.SessionID, b)
-		b = append(b, byte(f.SessionStatus), byte(f.Option))
-		length := 4
-
-		if f.SessionStatus == SessionStatusNew {
-			switch f.Target.Network {
-			case net.Network_TCP:
-				b = append(b, byte(TargetNetworkTCP))
-			case net.Network_UDP:
-				b = append(b, byte(TargetNetworkUDP))
-			}
-			length++
-
-			b = serial.Uint16ToBytes(f.Target.Port.Value(), b)
-			length += 2
-
-			addr := f.Target.Address
-			switch addr.Family() {
-			case net.AddressFamilyIPv4:
-				b = append(b, byte(protocol.AddressTypeIPv4))
-				b = append(b, addr.IP()...)
-				length += 5
-			case net.AddressFamilyIPv6:
-				b = append(b, byte(protocol.AddressTypeIPv6))
-				b = append(b, addr.IP()...)
-				length += 17
-			case net.AddressFamilyDomain:
-				domain := addr.Domain()
-				if protocol.IsDomainTooLong(domain) {
-					return 0, newError("domain name too long: ", domain)
-				}
-				nDomain := len(domain)
-				b = append(b, byte(protocol.AddressTypeDomain), byte(nDomain))
-				b = append(b, domain...)
-				length += nDomain + 2
-			}
+func (f FrameMetadata) WriteTo(b *buf.Buffer) error {
+	lenBytes := b.Bytes()
+	b.AppendBytes(0x00, 0x00)
+
+	len0 := b.Len()
+	if err := b.AppendSupplier(serial.WriteUint16(f.SessionID)); err != nil {
+		return err
+	}
+
+	b.AppendBytes(byte(f.SessionStatus), byte(f.Option))
+
+	if f.SessionStatus == SessionStatusNew {
+		switch f.Target.Network {
+		case net.Network_TCP:
+			b.AppendBytes(byte(TargetNetworkTCP))
+		case net.Network_UDP:
+			b.AppendBytes(byte(TargetNetworkUDP))
 		}
 
-		serial.Uint16ToBytes(uint16(length), lengthBytes[:0])
-		return length + 2, nil
+		if err := addrParser.WriteAddressPort(b, f.Target.Address, f.Target.Port); err != nil {
+			return err
+		}
 	}
+
+	len1 := b.Len()
+	serial.Uint16ToBytes(uint16(len1-len0), lenBytes)
+	return nil
 }
 
-func ReadFrameFrom(b []byte) (*FrameMetadata, error) {
-	if len(b) < 4 {
-		return nil, newError("insufficient buffer: ", len(b))
+func ReadFrameFrom(b *buf.Buffer) (*FrameMetadata, error) {
+	if b.Len() < 4 {
+		return nil, newError("insufficient buffer: ", b.Len())
 	}
 
 	f := &FrameMetadata{
-		SessionID:     serial.BytesToUint16(b[:2]),
-		SessionStatus: SessionStatus(b[2]),
-		Option:        bitmask.Byte(b[3]),
+		SessionID:     serial.BytesToUint16(b.BytesTo(2)),
+		SessionStatus: SessionStatus(b.Byte(2)),
+		Option:        bitmask.Byte(b.Byte(3)),
 	}
 
-	b = b[4:]
-
 	if f.SessionStatus == SessionStatusNew {
-		network := TargetNetwork(b[0])
-		port := net.PortFromBytes(b[1:3])
-		addrType := protocol.AddressType(b[3])
-		b = b[4:]
-
-		var addr net.Address
-		switch addrType {
-		case protocol.AddressTypeIPv4:
-			addr = net.IPAddress(b[0:4])
-			b = b[4:]
-		case protocol.AddressTypeIPv6:
-			addr = net.IPAddress(b[0:16])
-			b = b[16:]
-		case protocol.AddressTypeDomain:
-			nDomain := int(b[0])
-			addr = net.DomainAddress(string(b[1 : 1+nDomain]))
-			b = b[nDomain+1:]
-		default:
-			return nil, newError("unknown address type: ", addrType)
+		network := TargetNetwork(b.Byte(4))
+		b.SliceFrom(5)
+
+		addr, port, err := addrParser.ReadAddressPort(nil, b)
+		if err != nil {
+			return nil, newError("failed to parse address and port").Base(err)
 		}
+
 		switch network {
 		case TargetNetworkTCP:
 			f.Target = net.TCPDestination(addr, port)

+ 45 - 17
app/proxyman/mux/mux.go

@@ -10,8 +10,10 @@ import (
 
 	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
+	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/errors"
+	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/signal"
@@ -87,7 +89,7 @@ var muxCoolPort = net.Port(9527)
 func NewClient(p proxy.Outbound, dialer proxy.Dialer, m *ClientManager) (*Client, error) {
 	ctx := proxy.ContextWithTarget(context.Background(), net.TCPDestination(muxCoolAddress, muxCoolPort))
 	ctx, cancel := context.WithCancel(ctx)
-	pipe := ray.NewRay(ctx)
+	pipe := ray.New(ctx)
 
 	c := &Client{
 		sessionManager: NewSessionManager(),
@@ -131,7 +133,7 @@ func (m *Client) monitor() {
 		case <-timer.C:
 			size := m.sessionManager.Size()
 			if size == 0 && m.sessionManager.CloseIfNoSession() {
-				m.done.Close()
+				common.Must(m.done.Close())
 				return
 			}
 		}
@@ -146,18 +148,15 @@ func fetchInput(ctx context.Context, s *Session, output buf.Writer) {
 	}
 	s.transferType = transferType
 	writer := NewWriter(s.ID, dest, output, transferType)
-	defer writer.Close()
 	defer s.Close()
 
-	newError("dispatching request to ", dest).WriteToLog()
-	data, _ := s.input.ReadTimeout(time.Millisecond * 500)
-	if err := writer.WriteMultiBuffer(data); err != nil {
-		newError("failed to write first payload").Base(err).WriteToLog()
-		return
-	}
+	newError("dispatching request to ", dest).WithContext(ctx).WriteToLog()
 	if err := buf.Copy(s.input, writer); err != nil {
-		newError("failed to fetch all input").Base(err).WriteToLog()
+		newError("failed to fetch all input").Base(err).WithContext(ctx).WriteToLog()
+		writer.hasError = true
 	}
+
+	writer.Close()
 }
 
 func (m *Client) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) bool {
@@ -204,13 +203,21 @@ func (m *Client) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReade
 	}
 
 	if s, found := m.sessionManager.Get(meta.SessionID); found {
-		return buf.Copy(s.NewReader(reader), s.output, buf.IgnoreWriterError())
+		if err := buf.Copy(s.NewReader(reader), s.output); err != nil {
+			drain(reader)
+			s.input.CloseError()
+			return s.Close()
+		}
 	}
 	return drain(reader)
 }
 
 func (m *Client) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error {
 	if s, found := m.sessionManager.Get(meta.SessionID); found {
+		if meta.Option.Has(OptionError) {
+			s.input.CloseError()
+			s.output.CloseError()
+		}
 		s.Close()
 	}
 	if meta.Option.Has(OptionData) {
@@ -261,7 +268,7 @@ type Server struct {
 // NewServer creates a new mux.Server.
 func NewServer(ctx context.Context) *Server {
 	s := &Server{
-		dispatcher: core.FromContext(ctx).Dispatcher(),
+		dispatcher: core.MustFromContext(ctx).Dispatcher(),
 	}
 	return s
 }
@@ -271,7 +278,7 @@ func (s *Server) Dispatch(ctx context.Context, dest net.Destination) (ray.Inboun
 		return s.dispatcher.Dispatch(ctx, dest)
 	}
 
-	ray := ray.NewRay(ctx)
+	ray := ray.New(ctx)
 	worker := &ServerWorker{
 		dispatcher:     s.dispatcher,
 		outboundRay:    ray,
@@ -298,8 +305,10 @@ type ServerWorker struct {
 func handle(ctx context.Context, s *Session, output buf.Writer) {
 	writer := NewResponseWriter(s.ID, output, s.transferType)
 	if err := buf.Copy(s.input, writer); err != nil {
-		newError("session ", s.ID, " ends.").Base(err).WriteToLog()
+		newError("session ", s.ID, " ends.").Base(err).WithContext(ctx).WriteToLog()
+		writer.hasError = true
 	}
+
 	writer.Close()
 	s.Close()
 }
@@ -312,7 +321,18 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
 }
 
 func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
-	newError("received request for ", meta.Target).WriteToLog()
+	newError("received request for ", meta.Target).WithContext(ctx).WriteToLog()
+	{
+		msg := &log.AccessMessage{
+			To:     meta.Target,
+			Status: log.AccessAccepted,
+			Reason: "",
+		}
+		if src, f := proxy.SourceFromContext(ctx); f {
+			msg.From = src
+		}
+		log.Record(msg)
+	}
 	inboundRay, err := w.dispatcher.Dispatch(ctx, meta.Target)
 	if err != nil {
 		if meta.Option.Has(OptionData) {
@@ -343,13 +363,21 @@ func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.Buffere
 		return nil
 	}
 	if s, found := w.sessionManager.Get(meta.SessionID); found {
-		return buf.Copy(s.NewReader(reader), s.output, buf.IgnoreWriterError())
+		if err := buf.Copy(s.NewReader(reader), s.output); err != nil {
+			drain(reader)
+			s.input.CloseError()
+			return s.Close()
+		}
 	}
 	return drain(reader)
 }
 
 func (w *ServerWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error {
 	if s, found := w.sessionManager.Get(meta.SessionID); found {
+		if meta.Option.Has(OptionError) {
+			s.input.CloseError()
+			s.output.CloseError()
+		}
 		s.Close()
 	}
 	if meta.Option.Has(OptionData) {
@@ -397,7 +425,7 @@ func (w *ServerWorker) run(ctx context.Context) {
 			err := w.handleFrame(ctx, reader)
 			if err != nil {
 				if errors.Cause(err) != io.EOF {
-					newError("unexpected EOF").Base(err).WriteToLog()
+					newError("unexpected EOF").Base(err).WithContext(ctx).WriteToLog()
 					input.CloseError()
 				}
 				return

+ 7 - 12
app/proxyman/mux/reader.go

@@ -17,13 +17,13 @@ func ReadMetadata(reader io.Reader) (*FrameMetadata, error) {
 		return nil, newError("invalid metalen ", metaLen).AtError()
 	}
 
-	b := buf.New()
+	b := buf.NewSize(int32(metaLen))
 	defer b.Release()
 
-	if err := b.Reset(buf.ReadFullFrom(reader, int(metaLen))); err != nil {
+	if err := b.Reset(buf.ReadFullFrom(reader, int32(metaLen))); err != nil {
 		return nil, err
 	}
-	return ReadFrameFrom(b.Bytes())
+	return ReadFrameFrom(b)
 }
 
 // PacketReader is an io.Reader that reads whole chunk of Mux frames every time.
@@ -51,13 +51,8 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 		return nil, err
 	}
 
-	var b *buf.Buffer
-	if size <= buf.Size {
-		b = buf.New()
-	} else {
-		b = buf.NewLocal(int(size))
-	}
-	if err := b.AppendSupplier(buf.ReadFullFrom(r.reader, int(size))); err != nil {
+	b := buf.NewSize(int32(size))
+	if err := b.Reset(buf.ReadFullFrom(r.reader, int32(size))); err != nil {
 		b.Release()
 		return nil, err
 	}
@@ -68,7 +63,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 // StreamReader reads Mux frame as a stream.
 type StreamReader struct {
 	reader   *buf.BufferedReader
-	leftOver int
+	leftOver int32
 }
 
 // NewStreamReader creates a new StreamReader.
@@ -91,7 +86,7 @@ func (r *StreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 		if err != nil {
 			return nil, err
 		}
-		r.leftOver = int(size)
+		r.leftOver = int32(size)
 	}
 
 	mb, err := r.reader.ReadAtMost(r.leftOver)

+ 9 - 4
app/proxyman/mux/writer.go

@@ -13,6 +13,7 @@ type Writer struct {
 	writer       buf.Writer
 	id           uint16
 	followup     bool
+	hasError     bool
 	transferType protocol.TransferType
 }
 
@@ -40,6 +41,7 @@ func (w *Writer) getNextFrameMeta() FrameMetadata {
 		SessionID: w.id,
 		Target:    w.dest,
 	}
+
 	if w.followup {
 		meta.SessionStatus = SessionStatusKeep
 	} else {
@@ -53,7 +55,7 @@ func (w *Writer) getNextFrameMeta() FrameMetadata {
 func (w *Writer) writeMetaOnly() error {
 	meta := w.getNextFrameMeta()
 	b := buf.New()
-	if err := b.Reset(meta.AsSupplier()); err != nil {
+	if err := meta.WriteTo(b); err != nil {
 		return err
 	}
 	return w.writer.WriteMultiBuffer(buf.NewMultiBufferValue(b))
@@ -64,14 +66,14 @@ func (w *Writer) writeData(mb buf.MultiBuffer) error {
 	meta.Option.Set(OptionData)
 
 	frame := buf.New()
-	if err := frame.Reset(meta.AsSupplier()); err != nil {
+	if err := meta.WriteTo(frame); err != nil {
 		return err
 	}
 	if err := frame.AppendSupplier(serial.WriteUint16(uint16(mb.Len()))); err != nil {
 		return err
 	}
 
-	mb2 := buf.NewMultiBufferCap(len(mb) + 1)
+	mb2 := buf.NewMultiBufferCap(int32(len(mb)) + 1)
 	mb2.Append(frame)
 	mb2.AppendMulti(mb)
 	return w.writer.WriteMultiBuffer(mb2)
@@ -105,9 +107,12 @@ func (w *Writer) Close() error {
 		SessionID:     w.id,
 		SessionStatus: SessionStatusEnd,
 	}
+	if w.hasError {
+		meta.Option.Set(OptionError)
+	}
 
 	frame := buf.New()
-	common.Must(frame.Reset(meta.AsSupplier()))
+	common.Must(meta.WriteTo(frame))
 
 	w.writer.WriteMultiBuffer(buf.NewMultiBufferValue(frame))
 	return nil

+ 8 - 13
app/proxyman/outbound/handler.go

@@ -2,13 +2,11 @@ package outbound
 
 import (
 	"context"
-	"io"
 
 	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman/mux"
 	"v2ray.com/core/common"
-	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
@@ -24,10 +22,7 @@ type Handler struct {
 }
 
 func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (core.OutboundHandler, error) {
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context")
-	}
+	v := core.MustFromContext(ctx)
 	h := &Handler{
 		config:          config,
 		outboundManager: v.OutboundHandlerManager(),
@@ -85,14 +80,14 @@ func (h *Handler) Dispatch(ctx context.Context, outboundRay ray.OutboundRay) {
 	if h.mux != nil {
 		err := h.mux.Dispatch(ctx, outboundRay)
 		if err != nil {
-			newError("failed to process outbound traffic").Base(err).WriteToLog()
+			newError("failed to process outbound traffic").Base(err).WithContext(ctx).WriteToLog()
 			outboundRay.OutboundOutput().CloseError()
 		}
 	} else {
 		err := h.proxy.Process(ctx, outboundRay, h)
 		// Ensure outbound ray is properly closed.
-		if err != nil && errors.Cause(err) != io.EOF {
-			newError("failed to process outbound traffic").Base(err).WriteToLog()
+		if err != nil {
+			newError("failed to process outbound traffic").Base(err).WithContext(ctx).WriteToLog()
 			outboundRay.OutboundOutput().CloseError()
 		} else {
 			outboundRay.OutboundOutput().Close()
@@ -108,14 +103,14 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
 			tag := h.senderSettings.ProxySettings.Tag
 			handler := h.outboundManager.GetHandler(tag)
 			if handler != nil {
-				newError("proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog()
+				newError("proxying to ", tag, " for dest ", dest).AtDebug().WithContext(ctx).WriteToLog()
 				ctx = proxy.ContextWithTarget(ctx, dest)
-				stream := ray.NewRay(ctx)
+				stream := ray.New(ctx)
 				go handler.Dispatch(ctx, stream)
 				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().WithContext(ctx).WriteToLog()
 		}
 
 		if h.senderSettings.Via != nil {
@@ -140,7 +135,7 @@ func (h *Handler) Start() error {
 	return nil
 }
 
-// Close implements common.Runnable.
+// Close implements common.Closable.
 func (h *Handler) Close() error {
 	common.Close(h.mux)
 	return nil

+ 1 - 4
app/proxyman/outbound/outbound.go

@@ -25,10 +25,7 @@ func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error)
 	m := &Manager{
 		taggedHandler: make(map[string]core.OutboundHandler),
 	}
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context")
-	}
+	v := core.MustFromContext(ctx)
 	if err := v.RegisterFeature((*core.OutboundHandlerManager)(nil), m); err != nil {
 		return nil, newError("unable to register OutboundHandlerManager").Base(err)
 	}

+ 2 - 2
app/proxyman/proxyman.go

@@ -1,4 +1,4 @@
-// Package proxyman defines applications for manageing inbound and outbound proxies.
+// Package proxyman defines applications for managing inbound and outbound proxies.
 package proxyman
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg proxyman -path App,Proxyman
@@ -17,7 +17,7 @@ func ContextWithProtocolSniffers(ctx context.Context, list []KnownProtocols) con
 	return context.WithValue(ctx, protocolsKey, list)
 }
 
-func ProtocoSniffersFromContext(ctx context.Context) []KnownProtocols {
+func ProtocolSniffersFromContext(ctx context.Context) []KnownProtocols {
 	if list, ok := ctx.Value(protocolsKey).([]KnownProtocols); ok {
 		return list
 	}

+ 6 - 5
app/router/router.go

@@ -11,18 +11,16 @@ import (
 	"v2ray.com/core/proxy"
 )
 
+// Router is an implementation of core.Router.
 type Router struct {
 	domainStrategy Config_DomainStrategy
 	rules          []Rule
 	dns            core.DNSClient
 }
 
+// NewRouter creates a new Router based on the given config.
 func NewRouter(ctx context.Context, config *Config) (*Router, error) {
-	v := core.FromContext(ctx)
-	if v == nil {
-		return nil, newError("V is not in context")
-	}
-
+	v := core.MustFromContext(ctx)
 	r := &Router{
 		domainStrategy: config.DomainStrategy,
 		rules:          make([]Rule, len(config.Rule)),
@@ -72,6 +70,7 @@ func (r *ipResolver) Resolve() []net.Address {
 	return r.ip
 }
 
+// PickRoute implements core.Router.
 func (r *Router) PickRoute(ctx context.Context) (string, error) {
 	resolver := &ipResolver{
 		dns: r.dns,
@@ -110,10 +109,12 @@ func (r *Router) PickRoute(ctx context.Context) (string, error) {
 	return "", core.ErrNoClue
 }
 
+// Start implements common.Runnable.
 func (*Router) Start() error {
 	return nil
 }
 
+// Close implements common.Closable.
 func (*Router) Close() error {
 	return nil
 }

+ 51 - 0
app/stats/command/command.go

@@ -0,0 +1,51 @@
+package command
+
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg command -path App,Stats,Command
+
+import (
+	"context"
+
+	grpc "google.golang.org/grpc"
+	"v2ray.com/core"
+	"v2ray.com/core/common"
+)
+
+type statsServer struct {
+	stats core.StatManager
+}
+
+func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
+	c := s.stats.GetCounter(request.Name)
+	if c == nil {
+		return nil, newError(request.Name, " not found.")
+	}
+	var value int64
+	if request.Reset_ {
+		value = c.Set(0)
+	} else {
+		value = c.Value()
+	}
+	return &GetStatsResponse{
+		Stat: &Stat{
+			Name:  request.Name,
+			Value: value,
+		},
+	}, nil
+}
+
+type service struct {
+	v *core.Instance
+}
+
+func (s *service) Register(server *grpc.Server) {
+	RegisterStatsServiceServer(server, &statsServer{
+		stats: s.v.Stats(),
+	})
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
+		s := core.MustFromContext(ctx)
+		return &service{v: s}, nil
+	}))
+}

+ 198 - 0
app/stats/command/command.pb.go

@@ -0,0 +1,198 @@
+package command
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	"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 GetStatsRequest struct {
+	// Name of the stat counter.
+	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// Whether or not to reset the counter to fetching its value.
+	Reset_ bool `protobuf:"varint,2,opt,name=reset" json:"reset,omitempty"`
+}
+
+func (m *GetStatsRequest) Reset()                    { *m = GetStatsRequest{} }
+func (m *GetStatsRequest) String() string            { return proto.CompactTextString(m) }
+func (*GetStatsRequest) ProtoMessage()               {}
+func (*GetStatsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *GetStatsRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *GetStatsRequest) GetReset_() bool {
+	if m != nil {
+		return m.Reset_
+	}
+	return false
+}
+
+type Stat struct {
+	Name  string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	Value int64  `protobuf:"varint,2,opt,name=value" json:"value,omitempty"`
+}
+
+func (m *Stat) Reset()                    { *m = Stat{} }
+func (m *Stat) String() string            { return proto.CompactTextString(m) }
+func (*Stat) ProtoMessage()               {}
+func (*Stat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *Stat) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Stat) GetValue() int64 {
+	if m != nil {
+		return m.Value
+	}
+	return 0
+}
+
+type GetStatsResponse struct {
+	Stat *Stat `protobuf:"bytes,1,opt,name=stat" json:"stat,omitempty"`
+}
+
+func (m *GetStatsResponse) Reset()                    { *m = GetStatsResponse{} }
+func (m *GetStatsResponse) String() string            { return proto.CompactTextString(m) }
+func (*GetStatsResponse) ProtoMessage()               {}
+func (*GetStatsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func (m *GetStatsResponse) GetStat() *Stat {
+	if m != nil {
+		return m.Stat
+	}
+	return nil
+}
+
+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{3} }
+
+func init() {
+	proto.RegisterType((*GetStatsRequest)(nil), "v2ray.core.app.stats.command.GetStatsRequest")
+	proto.RegisterType((*Stat)(nil), "v2ray.core.app.stats.command.Stat")
+	proto.RegisterType((*GetStatsResponse)(nil), "v2ray.core.app.stats.command.GetStatsResponse")
+	proto.RegisterType((*Config)(nil), "v2ray.core.app.stats.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 StatsService service
+
+type StatsServiceClient interface {
+	GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)
+}
+
+type statsServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewStatsServiceClient(cc *grpc.ClientConn) StatsServiceClient {
+	return &statsServiceClient{cc}
+}
+
+func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {
+	out := new(GetStatsResponse)
+	err := grpc.Invoke(ctx, "/v2ray.core.app.stats.command.StatsService/GetStats", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for StatsService service
+
+type StatsServiceServer interface {
+	GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error)
+}
+
+func RegisterStatsServiceServer(s *grpc.Server, srv StatsServiceServer) {
+	s.RegisterService(&_StatsService_serviceDesc, srv)
+}
+
+func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetStatsRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(StatsServiceServer).GetStats(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/v2ray.core.app.stats.command.StatsService/GetStats",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _StatsService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "v2ray.core.app.stats.command.StatsService",
+	HandlerType: (*StatsServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "GetStats",
+			Handler:    _StatsService_GetStats_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "v2ray.com/core/app/stats/command/command.proto",
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/app/stats/command/command.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 267 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0x3f, 0x4b, 0x03, 0x31,
+	0x14, 0xc0, 0xbd, 0x5a, 0xeb, 0xf9, 0x14, 0x94, 0xe0, 0x50, 0xa4, 0xc3, 0x91, 0xa9, 0x8b, 0xef,
+	0xe4, 0x04, 0x17, 0x27, 0xbd, 0x41, 0x10, 0x07, 0x49, 0xc1, 0xc1, 0x2d, 0xc6, 0xa7, 0x14, 0xcd,
+	0x25, 0x26, 0xe9, 0x41, 0xf1, 0x1b, 0xf9, 0x29, 0x25, 0xb9, 0x1e, 0x82, 0xe0, 0xe1, 0x94, 0xf7,
+	0x92, 0xdf, 0xef, 0xfd, 0x21, 0x80, 0x6d, 0xe5, 0xe4, 0x1a, 0x95, 0xd1, 0xa5, 0x32, 0x8e, 0x4a,
+	0x69, 0x6d, 0xe9, 0x83, 0x0c, 0xbe, 0x54, 0x46, 0x6b, 0xd9, 0x3c, 0xf7, 0x27, 0x5a, 0x67, 0x82,
+	0x61, 0xb3, 0x9e, 0x77, 0x84, 0xd2, 0x5a, 0x4c, 0x2c, 0x6e, 0x18, 0x7e, 0x09, 0x87, 0x37, 0x14,
+	0x16, 0xf1, 0x4e, 0xd0, 0xc7, 0x8a, 0x7c, 0x60, 0x0c, 0xc6, 0x8d, 0xd4, 0x34, 0xcd, 0x8a, 0x6c,
+	0xbe, 0x27, 0x52, 0xcc, 0x8e, 0x61, 0xc7, 0x91, 0xa7, 0x30, 0x1d, 0x15, 0xd9, 0x3c, 0x17, 0x5d,
+	0xc2, 0xcf, 0x60, 0x1c, 0xcd, 0xbf, 0x8c, 0x56, 0xbe, 0xaf, 0x28, 0x19, 0xdb, 0xa2, 0x4b, 0xf8,
+	0x2d, 0x1c, 0xfd, 0xb4, 0xf3, 0xd6, 0x34, 0x9e, 0xd8, 0x05, 0x8c, 0xe3, 0x4c, 0xc9, 0xde, 0xaf,
+	0x38, 0x0e, 0xcd, 0x8b, 0x51, 0x15, 0x89, 0xe7, 0x39, 0x4c, 0x6a, 0xd3, 0xbc, 0x2c, 0x5f, 0xab,
+	0x4f, 0x38, 0x48, 0x25, 0x17, 0xe4, 0xda, 0xa5, 0x22, 0xf6, 0x06, 0x79, 0xdf, 0x85, 0x9d, 0x0e,
+	0xd7, 0xfb, 0xb5, 0xfc, 0x09, 0xfe, 0x17, 0xef, 0x86, 0xe7, 0x5b, 0xd7, 0x77, 0x50, 0x28, 0xa3,
+	0x07, 0xb5, 0xfb, 0xec, 0x71, 0x77, 0x13, 0x7e, 0x8d, 0x66, 0x0f, 0x95, 0x90, 0x6b, 0xac, 0x23,
+	0x79, 0x65, 0x6d, 0xda, 0xc8, 0x63, 0xdd, 0x3d, 0x3f, 0x4d, 0xd2, 0xa7, 0x9d, 0x7f, 0x07, 0x00,
+	0x00, 0xff, 0xff, 0x10, 0x3a, 0x8a, 0xf3, 0xe6, 0x01, 0x00, 0x00,
+}

+ 29 - 0
app/stats/command/command.proto

@@ -0,0 +1,29 @@
+syntax = "proto3";
+
+package v2ray.core.app.stats.command;
+option csharp_namespace = "V2Ray.Core.App.Stats.Command";
+option go_package = "command";
+option java_package = "com.v2ray.core.app.stats.command";
+option java_multiple_files = true;
+
+message GetStatsRequest {
+  // Name of the stat counter.
+  string name = 1;
+  // Whether or not to reset the counter to fetching its value.
+  bool reset = 2;
+}
+
+message Stat {
+  string name = 1;
+  int64 value = 2;
+}
+
+message GetStatsResponse {
+  Stat stat = 1;
+}
+
+service StatsService {
+  rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
+}
+
+message Config {}

+ 7 - 0
app/stats/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", "Stats", "Command")
+}

+ 13 - 0
app/stats/config.go

@@ -0,0 +1,13 @@
+package stats
+
+import (
+	"context"
+
+	"v2ray.com/core/common"
+)
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return NewManager(ctx, config.(*Config))
+	}))
+}

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

@@ -0,0 +1,42 @@
+package stats
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type Config struct {
+}
+
+func (m *Config) Reset()                    { *m = Config{} }
+func (m *Config) String() string            { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()               {}
+func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func init() {
+	proto.RegisterType((*Config)(nil), "v2ray.core.app.stats.Config")
+}
+
+func init() { proto.RegisterFile("v2ray.com/core/app/stats/config.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 123 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2d, 0x33, 0x2a, 0x4a,
+	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x2f,
+	0x2e, 0x49, 0x2c, 0x29, 0xd6, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f,
+	0xc9, 0x17, 0x12, 0x81, 0x29, 0x2b, 0x4a, 0xd5, 0x4b, 0x2c, 0x28, 0xd0, 0x03, 0x2b, 0x51, 0xe2,
+	0xe0, 0x62, 0x73, 0x06, 0xab, 0x72, 0xb2, 0xe2, 0x92, 0x48, 0xce, 0xcf, 0xd5, 0xc3, 0xa6, 0x2a,
+	0x80, 0x31, 0x8a, 0x15, 0xcc, 0x58, 0xc5, 0x24, 0x12, 0x66, 0x14, 0x94, 0x58, 0xa9, 0xe7, 0x0c,
+	0x92, 0x77, 0x2c, 0x28, 0xd0, 0x0b, 0x06, 0x09, 0x27, 0xb1, 0x81, 0xad, 0x30, 0x06, 0x04, 0x00,
+	0x00, 0xff, 0xff, 0x88, 0x24, 0xc6, 0x41, 0x8b, 0x00, 0x00, 0x00,
+}

+ 11 - 0
app/stats/config.proto

@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package v2ray.core.app.stats;
+option csharp_namespace = "V2Ray.Core.App.Stats";
+option go_package = "stats";
+option java_package = "com.v2ray.core.app.stats";
+option java_multiple_files = true;
+
+message Config {
+  
+}

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

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

+ 83 - 0
app/stats/stats.go

@@ -0,0 +1,83 @@
+package stats
+
+//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg stats -path App,Stats
+
+import (
+	"context"
+	"sync"
+	"sync/atomic"
+
+	"v2ray.com/core"
+)
+
+// Counter is an implementation of core.StatCounter.
+type Counter struct {
+	value int64
+}
+
+// Value implements core.StatCounter.
+func (c *Counter) Value() int64 {
+	return atomic.LoadInt64(&c.value)
+}
+
+// Set implements core.StatCounter.
+func (c *Counter) Set(newValue int64) int64 {
+	return atomic.SwapInt64(&c.value, newValue)
+}
+
+// Add implements core.StatCounter.
+func (c *Counter) Add(delta int64) int64 {
+	return atomic.AddInt64(&c.value, delta)
+}
+
+// Manager is an implementation of core.StatManager.
+type Manager struct {
+	access   sync.RWMutex
+	counters map[string]*Counter
+}
+
+func NewManager(ctx context.Context, config *Config) (*Manager, error) {
+	m := &Manager{
+		counters: make(map[string]*Counter),
+	}
+
+	v := core.FromContext(ctx)
+	if v != nil {
+		if err := v.RegisterFeature((*core.StatManager)(nil), m); err != nil {
+			return nil, newError("failed to register StatManager").Base(err)
+		}
+	}
+
+	return m, nil
+}
+
+func (m *Manager) RegisterCounter(name string) (core.StatCounter, error) {
+	m.access.Lock()
+	defer m.access.Unlock()
+
+	if _, found := m.counters[name]; found {
+		return nil, newError("Counter ", name, " already registered.")
+	}
+	newError("create new counter ", name).AtDebug().WriteToLog()
+	c := new(Counter)
+	m.counters[name] = c
+	return c, nil
+}
+
+func (m *Manager) GetCounter(name string) core.StatCounter {
+	m.access.RLock()
+	defer m.access.RUnlock()
+
+	if c, found := m.counters[name]; found {
+		return c
+	}
+	return nil
+}
+
+func (m *Manager) Start() error {
+	return nil
+}
+
+func (m *Manager) Close() error {
+	return nil
+}

+ 32 - 0
app/stats/stats_test.go

@@ -0,0 +1,32 @@
+package stats_test
+
+import (
+	"context"
+	"testing"
+
+	"v2ray.com/core"
+	. "v2ray.com/core/app/stats"
+	"v2ray.com/core/common"
+	. "v2ray.com/ext/assert"
+)
+
+func TestInternface(t *testing.T) {
+	assert := With(t)
+
+	assert((*Manager)(nil), Implements, (*core.StatManager)(nil))
+}
+
+func TestStatsCounter(t *testing.T) {
+	assert := With(t)
+
+	raw, err := common.CreateObject(context.Background(), &Config{})
+	assert(err, IsNil)
+
+	m := raw.(core.StatManager)
+	c, err := m.RegisterCounter("test.counter")
+	assert(err, IsNil)
+
+	assert(c.Add(1), Equals, int64(1))
+	assert(c.Set(0), Equals, int64(1))
+	assert(c.Value(), Equals, int64(0))
+}

+ 0 - 59
clock.go

@@ -1,59 +0,0 @@
-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
-}

+ 0 - 44
commander.go

@@ -1,44 +0,0 @@
-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
-}

+ 2 - 1
common/buf/buf.go

@@ -1,3 +1,4 @@
-package buf
+// Package buf provides a light-weight memory allocation mechanism.
+package buf // import "v2ray.com/core/common/buf"
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg buf -path Buf

+ 27 - 31
common/buf/buffer.go

@@ -1,4 +1,3 @@
-// Package buf provides a light-weight memory allocation mechanism.
 package buf
 
 import (
@@ -12,11 +11,10 @@ type Supplier func([]byte) (int, error)
 // the buffer into an internal buffer pool, in order to recreate a buffer more
 // quickly.
 type Buffer struct {
-	v    []byte
-	pool Pool
+	v []byte
 
-	start int
-	end   int
+	start int32
+	end   int32
 }
 
 // Release recycles the buffer into an internal buffer pool.
@@ -24,11 +22,8 @@ func (b *Buffer) Release() {
 	if b == nil || b.v == nil {
 		return
 	}
-	if b.pool != nil {
-		b.pool.Free(b)
-	}
+	freeBytes(b.v)
 	b.v = nil
-	b.pool = nil
 	b.start = 0
 	b.end = 0
 }
@@ -48,24 +43,24 @@ func (b *Buffer) AppendBytes(bytes ...byte) int {
 // Append appends a byte array to the end of the buffer.
 func (b *Buffer) Append(data []byte) int {
 	nBytes := copy(b.v[b.end:], data)
-	b.end += nBytes
+	b.end += int32(nBytes)
 	return nBytes
 }
 
 // AppendSupplier appends the content of a BytesWriter to the buffer.
 func (b *Buffer) AppendSupplier(writer Supplier) error {
 	nBytes, err := writer(b.v[b.end:])
-	b.end += nBytes
+	b.end += int32(nBytes)
 	return err
 }
 
 // Byte returns the bytes at index.
-func (b *Buffer) Byte(index int) byte {
+func (b *Buffer) Byte(index int32) byte {
 	return b.v[b.start+index]
 }
 
 // SetByte sets the byte value at index.
-func (b *Buffer) SetByte(index int, value byte) {
+func (b *Buffer) SetByte(index int32, value byte) {
 	b.v[b.start+index] = value
 }
 
@@ -78,12 +73,12 @@ func (b *Buffer) Bytes() []byte {
 func (b *Buffer) Reset(writer Supplier) error {
 	nBytes, err := writer(b.v)
 	b.start = 0
-	b.end = nBytes
+	b.end = int32(nBytes)
 	return err
 }
 
-// BytesRange returns a slice of this buffer with given from and to bounary.
-func (b *Buffer) BytesRange(from, to int) []byte {
+// BytesRange returns a slice of this buffer with given from and to boundary.
+func (b *Buffer) BytesRange(from, to int32) []byte {
 	if from < 0 {
 		from += b.Len()
 	}
@@ -94,7 +89,7 @@ func (b *Buffer) BytesRange(from, to int) []byte {
 }
 
 // BytesFrom returns a slice of this Buffer starting from the given position.
-func (b *Buffer) BytesFrom(from int) []byte {
+func (b *Buffer) BytesFrom(from int32) []byte {
 	if from < 0 {
 		from += b.Len()
 	}
@@ -102,7 +97,7 @@ func (b *Buffer) BytesFrom(from int) []byte {
 }
 
 // BytesTo returns a slice of this Buffer from start to the given position.
-func (b *Buffer) BytesTo(to int) []byte {
+func (b *Buffer) BytesTo(to int32) []byte {
 	if to < 0 {
 		to += b.Len()
 	}
@@ -110,7 +105,7 @@ func (b *Buffer) BytesTo(to int) []byte {
 }
 
 // Slice cuts the buffer at the given position.
-func (b *Buffer) Slice(from, to int) {
+func (b *Buffer) Slice(from, to int32) {
 	if from < 0 {
 		from += b.Len()
 	}
@@ -125,7 +120,7 @@ func (b *Buffer) Slice(from, to int) {
 }
 
 // SliceFrom cuts the buffer at the given position.
-func (b *Buffer) SliceFrom(from int) {
+func (b *Buffer) SliceFrom(from int32) {
 	if from < 0 {
 		from += b.Len()
 	}
@@ -133,7 +128,7 @@ func (b *Buffer) SliceFrom(from int) {
 }
 
 // Len returns the length of the buffer content.
-func (b *Buffer) Len() int {
+func (b *Buffer) Len() int32 {
 	if b == nil {
 		return 0
 	}
@@ -147,13 +142,13 @@ func (b *Buffer) IsEmpty() bool {
 
 // IsFull returns true if the buffer has no more room to grow.
 func (b *Buffer) IsFull() bool {
-	return b.end == len(b.v)
+	return b.end == int32(len(b.v))
 }
 
 // Write implements Write method in io.Writer.
 func (b *Buffer) Write(data []byte) (int, error) {
 	nBytes := copy(b.v[b.end:], data)
-	b.end += nBytes
+	b.end += int32(nBytes)
 	return nBytes, nil
 }
 
@@ -163,10 +158,10 @@ func (b *Buffer) Read(data []byte) (int, error) {
 		return 0, io.EOF
 	}
 	nBytes := copy(data, b.v[b.start:b.end])
-	if nBytes == b.Len() {
+	if int32(nBytes) == b.Len() {
 		b.Clear()
 	} else {
-		b.start += nBytes
+		b.start += int32(nBytes)
 	}
 	return nBytes, nil
 }
@@ -176,15 +171,16 @@ func (b *Buffer) String() string {
 	return string(b.Bytes())
 }
 
-// New creates a Buffer with 0 length and 8K capacity.
+// New creates a Buffer with 0 length and 2K capacity.
 func New() *Buffer {
-	return mediumPool.Allocate()
+	return &Buffer{
+		v: pool[0].Get().([]byte),
+	}
 }
 
-// NewLocal creates and returns a buffer with 0 length and given capacity on current thread.
-func NewLocal(size int) *Buffer {
+// NewSize creates and returns a buffer with 0 length and at least the given capacity. Capacity must be positive.
+func NewSize(capacity int32) *Buffer {
 	return &Buffer{
-		v:    make([]byte, size),
-		pool: nil,
+		v: newBytes(capacity),
 	}
 }

+ 46 - 35
common/buf/buffer_pool.go

@@ -4,49 +4,60 @@ import (
 	"sync"
 )
 
-// Pool provides functionality to generate and recycle buffers on demand.
-type Pool interface {
-	// Allocate either returns a unused buffer from the pool, or generates a new one from system.
-	Allocate() *Buffer
-	// Free recycles the given buffer.
-	Free(*Buffer)
-}
+const (
+	// Size of a regular buffer.
+	Size = 2 * 1024
+)
 
-// SyncPool is a buffer pool based on sync.Pool
-type SyncPool struct {
-	allocator *sync.Pool
+func createAllocFunc(size int32) func() interface{} {
+	return func() interface{} {
+		return make([]byte, size)
+	}
 }
 
-// NewSyncPool creates a SyncPool with given buffer size.
-func NewSyncPool(bufferSize uint32) *SyncPool {
-	pool := &SyncPool{
-		allocator: &sync.Pool{
-			New: func() interface{} { return make([]byte, bufferSize) },
-		},
+// The following parameters controls the size of buffer pools.
+// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one.
+// Package buf is guaranteed to not use buffers larger than the largest pool.
+// Other packets may use larger buffers.
+const (
+	numPools  = 5
+	sizeMulti = 4
+)
+
+var (
+	pool      [numPools]sync.Pool
+	poolSize  [numPools]int32
+	largeSize int32
+)
+
+func init() {
+	size := int32(Size)
+	for i := 0; i < numPools; i++ {
+		pool[i] = sync.Pool{
+			New: createAllocFunc(size),
+		}
+		poolSize[i] = size
+		largeSize = size
+		size *= sizeMulti
 	}
-	return pool
 }
 
-// Allocate implements Pool.Allocate().
-func (p *SyncPool) Allocate() *Buffer {
-	return &Buffer{
-		v:    p.allocator.Get().([]byte),
-		pool: p,
+func newBytes(size int32) []byte {
+	for idx, ps := range poolSize {
+		if size <= ps {
+			return pool[idx].Get().([]byte)
+		}
 	}
+	return make([]byte, size)
 }
 
-// Free implements Pool.Free().
-func (p *SyncPool) Free(buffer *Buffer) {
-	if buffer.v != nil {
-		p.allocator.Put(buffer.v)
+func freeBytes(b []byte) {
+	size := int32(cap(b))
+	b = b[0:cap(b)]
+	for i := numPools - 1; i >= 0; i-- {
+		if size >= poolSize[i] {
+			pool[i].Put(b)
+			return
+		}
 	}
 }
-
-const (
-	// Size of a regular buffer.
-	Size = 2 * 1024
-)
-
-var (
-	mediumPool Pool = NewSyncPool(Size)
-)

+ 3 - 30
common/buf/buffer_test.go

@@ -1,7 +1,6 @@
 package buf_test
 
 import (
-	"crypto/rand"
 	"testing"
 
 	. "v2ray.com/core/common/buf"
@@ -17,10 +16,10 @@ func TestBufferClear(t *testing.T) {
 
 	payload := "Bytes"
 	buffer.Append([]byte(payload))
-	assert(buffer.Len(), Equals, len(payload))
+	assert(buffer.Len(), Equals, int32(len(payload)))
 
 	buffer.Clear()
-	assert(buffer.Len(), Equals, 0)
+	assert(buffer.Len(), Equals, int32(0))
 }
 
 func TestBufferIsEmpty(t *testing.T) {
@@ -42,32 +41,6 @@ func TestBufferString(t *testing.T) {
 	assert(buffer.String(), Equals, "Test String")
 }
 
-func TestBufferWrite(t *testing.T) {
-	assert := With(t)
-
-	buffer := NewLocal(8)
-	nBytes, err := buffer.Write([]byte("abcd"))
-	assert(err, IsNil)
-	assert(nBytes, Equals, 4)
-	nBytes, err = buffer.Write([]byte("abcde"))
-	assert(err, IsNil)
-	assert(nBytes, Equals, 4)
-	assert(buffer.String(), Equals, "abcdabcd")
-}
-
-func TestSyncPool(t *testing.T) {
-	assert := With(t)
-
-	p := NewSyncPool(32)
-	b := p.Allocate()
-	assert(b.Len(), Equals, 0)
-
-	assert(b.AppendSupplier(ReadFrom(rand.Reader)), IsNil)
-	assert(b.Len(), Equals, 32)
-
-	b.Release()
-}
-
 func BenchmarkNewBuffer(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		buffer := New()
@@ -77,7 +50,7 @@ func BenchmarkNewBuffer(b *testing.B) {
 
 func BenchmarkNewLocalBuffer(b *testing.B) {
 	for i := 0; i < b.N; i++ {
-		buffer := NewLocal(Size)
+		buffer := NewSize(Size)
 		buffer.Release()
 	}
 }

+ 4 - 1
common/buf/copy.go

@@ -36,6 +36,7 @@ func (h *copyHandler) writeTo(writer Writer, mb MultiBuffer) error {
 	return err
 }
 
+// SizeCounter is for counting bytes copied by Copy().
 type SizeCounter struct {
 	Size int64
 }
@@ -91,7 +92,9 @@ func copyInternal(reader Reader, writer Writer, handler *copyHandler) error {
 				buffer.Release()
 				return werr
 			}
-		} else if err != nil {
+		}
+
+		if err != nil {
 			return err
 		}
 	}

+ 2 - 1
common/buf/io.go

@@ -33,7 +33,7 @@ func ReadFrom(reader io.Reader) Supplier {
 }
 
 // ReadFullFrom creates a Supplier to read full buffer from a given io.Reader.
-func ReadFullFrom(reader io.Reader, size int) Supplier {
+func ReadFullFrom(reader io.Reader, size int32) Supplier {
 	return func(b []byte) (int, error) {
 		return io.ReadFull(reader, b[:size])
 	}
@@ -67,6 +67,7 @@ func NewWriter(writer io.Writer) Writer {
 	}
 }
 
+// NewSequentialWriter returns a Writer that write Buffers in a MultiBuffer sequentially.
 func NewSequentialWriter(writer io.Writer) Writer {
 	return &seqWriter{
 		writer: writer,

+ 30 - 8
common/buf/multi_buffer.go

@@ -30,6 +30,26 @@ func ReadAllToMultiBuffer(reader io.Reader) (MultiBuffer, error) {
 	}
 }
 
+// ReadSizeToMultiBuffer reads specific number of bytes from reader into a MultiBuffer.
+func ReadSizeToMultiBuffer(reader io.Reader, size int32) (MultiBuffer, error) {
+	mb := NewMultiBufferCap(32)
+
+	for size > 0 {
+		bSize := size
+		if bSize > Size {
+			bSize = Size
+		}
+		b := NewSize(bSize)
+		if err := b.Reset(ReadFullFrom(reader, bSize)); err != nil {
+			mb.Release()
+			return nil, err
+		}
+		size -= bSize
+		mb.Append(b)
+	}
+	return mb, nil
+}
+
 // ReadAllToBytes reads all content from the reader into a byte array, until EOF.
 func ReadAllToBytes(reader io.Reader) ([]byte, error) {
 	mb, err := ReadAllToMultiBuffer(reader)
@@ -46,7 +66,7 @@ func ReadAllToBytes(reader io.Reader) ([]byte, error) {
 type MultiBuffer []*Buffer
 
 // NewMultiBufferCap creates a new MultiBuffer instance.
-func NewMultiBufferCap(capacity int) MultiBuffer {
+func NewMultiBufferCap(capacity int32) MultiBuffer {
 	return MultiBuffer(make([]*Buffer, 0, capacity))
 }
 
@@ -57,7 +77,9 @@ func NewMultiBufferValue(b ...*Buffer) MultiBuffer {
 
 // Append appends buffer to the end of this MultiBuffer
 func (mb *MultiBuffer) Append(buf *Buffer) {
-	*mb = append(*mb, buf)
+	if buf != nil {
+		*mb = append(*mb, buf)
+	}
 }
 
 // AppendMulti appends a MultiBuffer to the end of this one.
@@ -71,7 +93,7 @@ func (mb MultiBuffer) Copy(b []byte) int {
 	for _, bb := range mb {
 		nBytes := copy(b[total:], bb.Bytes())
 		total += nBytes
-		if nBytes < bb.Len() {
+		if int32(nBytes) < bb.Len() {
 			break
 		}
 	}
@@ -115,8 +137,8 @@ func (mb *MultiBuffer) Write(b []byte) {
 }
 
 // Len returns the total number of bytes in the MultiBuffer.
-func (mb MultiBuffer) Len() int {
-	size := 0
+func (mb MultiBuffer) Len() int32 {
+	size := int32(0)
 	for _, b := range mb {
 		size += b.Len()
 	}
@@ -152,9 +174,9 @@ func (mb MultiBuffer) ToNetBuffers() net.Buffers {
 }
 
 // SliceBySize splits the beginning of this MultiBuffer into another one, for at most size bytes.
-func (mb *MultiBuffer) SliceBySize(size int) MultiBuffer {
+func (mb *MultiBuffer) SliceBySize(size int32) MultiBuffer {
 	slice := NewMultiBufferCap(10)
-	sliceSize := 0
+	sliceSize := int32(0)
 	endIndex := len(*mb)
 	for i, b := range *mb {
 		if b.Len()+sliceSize > size {
@@ -167,7 +189,7 @@ func (mb *MultiBuffer) SliceBySize(size int) MultiBuffer {
 	}
 	*mb = (*mb)[endIndex:]
 	if endIndex == 0 && len(*mb) > 0 {
-		b := New()
+		b := NewSize(size)
 		common.Must(b.Reset(ReadFullFrom((*mb)[0], size)))
 		return NewMultiBufferValue(b)
 	}

+ 16 - 1
common/buf/multi_buffer_test.go

@@ -1,8 +1,10 @@
 package buf_test
 
 import (
+	"crypto/rand"
 	"testing"
 
+	"v2ray.com/core/common"
 	. "v2ray.com/core/common/buf"
 	. "v2ray.com/ext/assert"
 )
@@ -31,5 +33,18 @@ func TestMultiBufferAppend(t *testing.T) {
 	b := New()
 	b.AppendBytes('a', 'b')
 	mb.Append(b)
-	assert(mb.Len(), Equals, 2)
+	assert(mb.Len(), Equals, int32(2))
+}
+
+func TestMultiBufferSliceBySizeLarge(t *testing.T) {
+	assert := With(t)
+
+	lb := NewSize(8 * 1024)
+	common.Must(lb.Reset(ReadFrom(rand.Reader)))
+
+	var mb MultiBuffer
+	mb.Append(lb)
+
+	mb2 := mb.SliceBySize(4 * 1024)
+	assert(mb2.Len(), Equals, int32(4*1024))
 }

+ 50 - 23
common/buf/reader.go

@@ -12,26 +12,35 @@ type BytesToBufferReader struct {
 	buffer []byte
 }
 
+// NewBytesToBufferReader returns a new BytesToBufferReader.
 func NewBytesToBufferReader(reader io.Reader) Reader {
 	return &BytesToBufferReader{
 		Reader: reader,
 	}
 }
 
-const mediumSize = 8 * 1024
-const largeSize = 64 * 1024
-
 func (r *BytesToBufferReader) readSmall() (MultiBuffer, error) {
 	b := New()
-	err := b.Reset(ReadFrom(r.Reader))
-	if b.IsFull() {
-		r.buffer = make([]byte, mediumSize)
-	}
-	if !b.IsEmpty() {
-		return NewMultiBufferValue(b), nil
+	for i := 0; i < 64; i++ {
+		err := b.Reset(ReadFrom(r.Reader))
+		if b.IsFull() {
+			r.buffer = newBytes(Size + 1)
+		}
+		if !b.IsEmpty() {
+			return NewMultiBufferValue(b), nil
+		}
+		if err != nil {
+			b.Release()
+			return nil, err
+		}
 	}
-	b.Release()
-	return nil, err
+
+	return nil, newError("Reader returns too many empty payloads.")
+}
+
+func (r *BytesToBufferReader) freeBuffer() {
+	freeBytes(r.buffer)
+	r.buffer = nil
 }
 
 // ReadMultiBuffer implements Reader.
@@ -42,29 +51,35 @@ func (r *BytesToBufferReader) ReadMultiBuffer() (MultiBuffer, error) {
 
 	nBytes, err := r.Reader.Read(r.buffer)
 	if nBytes > 0 {
-		mb := NewMultiBufferCap(nBytes/Size + 1)
+		mb := NewMultiBufferCap(int32(nBytes/Size) + 1)
 		mb.Write(r.buffer[:nBytes])
-		if nBytes == len(r.buffer) && len(r.buffer) == mediumSize {
-			r.buffer = make([]byte, largeSize)
+		if nBytes == len(r.buffer) && nBytes < int(largeSize) {
+			freeBytes(r.buffer)
+			r.buffer = newBytes(int32(nBytes) + 1)
+		} else if nBytes < Size {
+			r.freeBuffer()
 		}
 		return mb, nil
 	}
-	return nil, err
-}
 
-var (
-	_ Reader        = (*BufferedReader)(nil)
-	_ io.Reader     = (*BufferedReader)(nil)
-	_ io.ByteReader = (*BufferedReader)(nil)
-	_ io.WriterTo   = (*BufferedReader)(nil)
-)
+	r.freeBuffer()
+
+	if err != nil {
+		return nil, err
+	}
+
+	// Read() returns empty payload and nil err. We don't expect this to happen, but just in case.
+	return r.readSmall()
+}
 
+// BufferedReader is a Reader that keeps its internal buffer.
 type BufferedReader struct {
 	stream   Reader
 	leftOver MultiBuffer
 	buffered bool
 }
 
+// NewBufferedReader returns a new BufferedReader.
 func NewBufferedReader(reader Reader) *BufferedReader {
 	return &BufferedReader{
 		stream:   reader,
@@ -72,20 +87,29 @@ func NewBufferedReader(reader Reader) *BufferedReader {
 	}
 }
 
+// SetBuffered sets whether to keep the interal buffer.
 func (r *BufferedReader) SetBuffered(f bool) {
 	r.buffered = f
 }
 
+// IsBuffered returns true if internal buffer is used.
 func (r *BufferedReader) IsBuffered() bool {
 	return r.buffered
 }
 
+// BufferedBytes returns the number of bytes that is cached in this reader.
+func (r *BufferedReader) BufferedBytes() int32 {
+	return r.leftOver.Len()
+}
+
+// ReadByte implements io.ByteReader.
 func (r *BufferedReader) ReadByte() (byte, error) {
 	var b [1]byte
 	_, err := r.Read(b[:])
 	return b[0], err
 }
 
+// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.
 func (r *BufferedReader) Read(b []byte) (int, error) {
 	if r.leftOver != nil {
 		nBytes, _ := r.leftOver.Read(b)
@@ -113,6 +137,7 @@ func (r *BufferedReader) Read(b []byte) (int, error) {
 	return 0, err
 }
 
+// ReadMultiBuffer implements Reader.
 func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {
 	if r.leftOver != nil {
 		mb := r.leftOver
@@ -124,7 +149,7 @@ func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {
 }
 
 // ReadAtMost returns a MultiBuffer with at most size.
-func (r *BufferedReader) ReadAtMost(size int) (MultiBuffer, error) {
+func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) {
 	if r.leftOver == nil {
 		mb, err := r.stream.ReadMultiBuffer()
 		if mb.IsEmpty() && err != nil {
@@ -148,6 +173,7 @@ func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {
 		if err := mbWriter.WriteMultiBuffer(r.leftOver); err != nil {
 			return 0, err
 		}
+		r.leftOver = nil
 	}
 
 	for {
@@ -164,6 +190,7 @@ func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {
 	}
 }
 
+// WriteTo implements io.WriterTo.
 func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) {
 	nBytes, err := r.writeToInternal(writer)
 	if errors.Cause(err) == io.EOF {

+ 11 - 3
common/buf/reader_test.go

@@ -17,15 +17,23 @@ func TestAdaptiveReader(t *testing.T) {
 	reader := NewReader(bytes.NewReader(make([]byte, 1024*1024)))
 	b, err := reader.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(b.Len(), Equals, 2*1024)
+	assert(b.Len(), Equals, int32(2*1024))
 
 	b, err = reader.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(b.Len(), Equals, 8*1024)
+	assert(b.Len(), Equals, int32(8*1024))
 
 	b, err = reader.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(b.Len(), Equals, 64*1024)
+	assert(b.Len(), Equals, int32(32*1024))
+
+	b, err = reader.ReadMultiBuffer()
+	assert(err, IsNil)
+	assert(b.Len(), Equals, int32(128*1024))
+
+	b, err = reader.ReadMultiBuffer()
+	assert(err, IsNil)
+	assert(b.Len(), Equals, int32(512*1024))
 }
 
 func TestBytesReaderWriteTo(t *testing.T) {

+ 4 - 1
common/buf/writer.go

@@ -11,6 +11,7 @@ type BufferToBytesWriter struct {
 	io.Writer
 }
 
+// NewBufferToBytesWriter returns a new BufferToBytesWriter.
 func NewBufferToBytesWriter(writer io.Writer) *BufferToBytesWriter {
 	return &BufferToBytesWriter{
 		Writer: writer,
@@ -22,7 +23,7 @@ func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
 	defer mb.Release()
 
 	bs := mb.ToNetBuffers()
-	_, err := bs.WriteTo(w)
+	_, err := bs.WriteTo(w.Writer)
 	return err
 }
 
@@ -49,6 +50,7 @@ func NewBufferedWriter(writer Writer) *BufferedWriter {
 	}
 }
 
+// WriteByte implements io.ByteWriter.
 func (w *BufferedWriter) WriteByte(c byte) error {
 	_, err := w.Write([]byte{c})
 	return err
@@ -121,6 +123,7 @@ func (w *BufferedWriter) Flush() error {
 	return nil
 }
 
+// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer.
 func (w *BufferedWriter) SetBuffered(f bool) error {
 	w.buffered = f
 	if !f {

+ 1 - 1
common/buf/writer_test.go

@@ -47,7 +47,7 @@ func TestBytesWriterReadFrom(t *testing.T) {
 
 	mb, err := cache.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(mb.Len(), Equals, size)
+	assert(mb.Len(), Equals, int32(size))
 }
 
 func TestDiscardBytes(t *testing.T) {

+ 4 - 5
common/common.go

@@ -11,9 +11,8 @@ func Must(err error) {
 	}
 }
 
-// Must2 panics if the second parameter is not nil.
-func Must2(v interface{}, err error) {
-	if err != nil {
-		panic(err)
-	}
+// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
+func Must2(v interface{}, err error) interface{} {
+	Must(err)
+	return v
 }

+ 65 - 21
common/crypto/auth.go

@@ -93,6 +93,7 @@ type AuthenticationReader struct {
 	reader       *buf.BufferedReader
 	sizeParser   ChunkSizeDecoder
 	transferType protocol.TransferType
+	size         int32
 }
 
 func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType) *AuthenticationReader {
@@ -101,35 +102,48 @@ func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, re
 		reader:       buf.NewBufferedReader(buf.NewReader(reader)),
 		sizeParser:   sizeParser,
 		transferType: transferType,
+		size:         -1,
 	}
 }
 
-func (r *AuthenticationReader) readSize() (int, error) {
+func (r *AuthenticationReader) readSize() (int32, error) {
+	if r.size != -1 {
+		s := r.size
+		r.size = -1
+		return s, nil
+	}
 	sizeBytes := make([]byte, r.sizeParser.SizeBytes())
 	_, err := io.ReadFull(r.reader, sizeBytes)
 	if err != nil {
 		return 0, err
 	}
 	size, err := r.sizeParser.Decode(sizeBytes)
-	return int(size), err
+	return int32(size), err
 }
 
-func (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
+var errSoft = newError("waiting for more data")
+
+func (r *AuthenticationReader) readInternal(soft bool) (*buf.Buffer, error) {
+	if soft && r.reader.BufferedBytes() < r.sizeParser.SizeBytes() {
+		return nil, errSoft
+	}
+
 	size, err := r.readSize()
 	if err != nil {
 		return nil, err
 	}
 
-	if size == r.auth.Overhead() {
+	if size == -2 || size == int32(r.auth.Overhead()) {
+		r.size = -2
 		return nil, io.EOF
 	}
 
-	var b *buf.Buffer
-	if size <= buf.Size {
-		b = buf.New()
-	} else {
-		b = buf.NewLocal(size)
+	if soft && size > r.reader.BufferedBytes() {
+		r.size = size
+		return nil, errSoft
 	}
+
+	b := buf.NewSize(size)
 	if err := b.Reset(buf.ReadFullFrom(r.reader, size)); err != nil {
 		b.Release()
 		return nil, err
@@ -140,8 +154,33 @@ func (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 		b.Release()
 		return nil, err
 	}
-	b.Slice(0, len(rb))
-	return buf.NewMultiBufferValue(b), nil
+	b.Slice(0, int32(len(rb)))
+
+	return b, nil
+}
+
+func (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
+	b, err := r.readInternal(false)
+	if err != nil {
+		return nil, err
+	}
+
+	mb := buf.NewMultiBufferCap(32)
+	mb.Append(b)
+
+	for {
+		b, err := r.readInternal(true)
+		if err == errSoft || err == io.EOF {
+			break
+		}
+		if err != nil {
+			mb.Release()
+			return nil, err
+		}
+		mb.Append(b)
+	}
+
+	return mb, nil
 }
 
 type AuthenticationWriter struct {
@@ -161,12 +200,12 @@ func NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, wr
 }
 
 func (w *AuthenticationWriter) seal(b *buf.Buffer) (*buf.Buffer, error) {
-	encryptedSize := b.Len() + w.auth.Overhead()
+	encryptedSize := int(b.Len()) + w.auth.Overhead()
 
 	eb := buf.New()
 	common.Must(eb.Reset(func(bb []byte) (int, error) {
 		w.sizeParser.Encode(uint16(encryptedSize), bb[:0])
-		return w.sizeParser.SizeBytes(), nil
+		return int(w.sizeParser.SizeBytes()), nil
 	}))
 	if err := eb.AppendSupplier(func(bb []byte) (int, error) {
 		_, err := w.auth.Seal(bb[:0], b.Bytes())
@@ -182,8 +221,8 @@ func (w *AuthenticationWriter) seal(b *buf.Buffer) (*buf.Buffer, error) {
 func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error {
 	defer mb.Release()
 
-	payloadSize := buf.Size - w.auth.Overhead() - w.sizeParser.SizeBytes()
-	mb2Write := buf.NewMultiBufferCap(len(mb) + 10)
+	payloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes()
+	mb2Write := buf.NewMultiBufferCap(int32(len(mb) + 10))
 
 	for {
 		b := buf.New()
@@ -209,12 +248,20 @@ func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error {
 func (w *AuthenticationWriter) writePacket(mb buf.MultiBuffer) error {
 	defer mb.Release()
 
-	mb2Write := buf.NewMultiBufferCap(len(mb) * 2)
+	if mb.IsEmpty() {
+		b := buf.New()
+		defer b.Release()
 
-	for {
+		eb, _ := w.seal(b)
+		return w.writer.WriteMultiBuffer(buf.NewMultiBufferValue(eb))
+	}
+
+	mb2Write := buf.NewMultiBufferCap(int32(len(mb)) + 1)
+
+	for !mb.IsEmpty() {
 		b := mb.SplitFirst()
 		if b == nil {
-			b = buf.New()
+			continue
 		}
 		eb, err := w.seal(b)
 		b.Release()
@@ -223,9 +270,6 @@ func (w *AuthenticationWriter) writePacket(mb buf.MultiBuffer) error {
 			return err
 		}
 		mb2Write.Append(eb)
-		if mb.IsEmpty() {
-			break
-		}
 	}
 
 	return w.writer.WriteMultiBuffer(mb2Write)

+ 13 - 12
common/crypto/auth_test.go

@@ -24,13 +24,15 @@ func TestAuthenticationReaderWriter(t *testing.T) {
 	aead, err := cipher.NewGCM(block)
 	assert(err, IsNil)
 
-	rawPayload := make([]byte, 8192*10)
+	const payloadSize = 1024 * 80
+	rawPayload := make([]byte, payloadSize)
 	rand.Read(rawPayload)
 
-	payload := buf.NewLocal(8192 * 10)
+	payload := buf.NewSize(payloadSize)
 	payload.Append(rawPayload)
+	assert(payload.Len(), Equals, int32(payloadSize))
 
-	cache := buf.NewLocal(160 * 1024)
+	cache := buf.NewSize(160 * 1024)
 	iv := make([]byte, 12)
 	rand.Read(iv)
 
@@ -43,9 +45,8 @@ func TestAuthenticationReaderWriter(t *testing.T) {
 	}, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream)
 
 	assert(writer.WriteMultiBuffer(buf.NewMultiBufferValue(payload)), IsNil)
-	assert(cache.Len(), Equals, 82658)
+	assert(cache.Len(), Equals, int32(82658))
 	assert(writer.WriteMultiBuffer(buf.MultiBuffer{}), IsNil)
-	assert(err, IsNil)
 
 	reader := NewAuthenticationReader(&AEADAuthenticator{
 		AEAD: aead,
@@ -57,14 +58,16 @@ func TestAuthenticationReaderWriter(t *testing.T) {
 
 	var mb buf.MultiBuffer
 
-	for mb.Len() < len(rawPayload) {
+	for mb.Len() < payloadSize {
 		mb2, err := reader.ReadMultiBuffer()
 		assert(err, IsNil)
 
 		mb.AppendMulti(mb2)
 	}
 
-	mbContent := make([]byte, 8192*10)
+	assert(mb.Len(), Equals, int32(payloadSize))
+
+	mbContent := make([]byte, payloadSize)
 	mb.Read(mbContent)
 	assert(mbContent, Equals, rawPayload)
 
@@ -83,7 +86,7 @@ func TestAuthenticationReaderWriterPacket(t *testing.T) {
 	aead, err := cipher.NewGCM(block)
 	assert(err, IsNil)
 
-	cache := buf.NewLocal(1024)
+	cache := buf.NewSize(1024)
 	iv := make([]byte, 12)
 	rand.Read(iv)
 
@@ -105,7 +108,7 @@ func TestAuthenticationReaderWriterPacket(t *testing.T) {
 	payload.Append(pb2)
 
 	assert(writer.WriteMultiBuffer(payload), IsNil)
-	assert(cache.Len(), GreaterThan, 0)
+	assert(cache.Len(), GreaterThan, int32(0))
 	assert(writer.WriteMultiBuffer(buf.MultiBuffer{}), IsNil)
 	assert(err, IsNil)
 
@@ -122,12 +125,10 @@ func TestAuthenticationReaderWriterPacket(t *testing.T) {
 
 	b1 := mb.SplitFirst()
 	assert(b1.String(), Equals, "abcd")
-	assert(mb.IsEmpty(), IsTrue)
 
-	mb, err = reader.ReadMultiBuffer()
-	assert(err, IsNil)
 	b2 := mb.SplitFirst()
 	assert(b2.String(), Equals, "efgh")
+
 	assert(mb.IsEmpty(), IsTrue)
 
 	_, err = reader.ReadMultiBuffer()

+ 10 - 10
common/crypto/chunk.go

@@ -8,21 +8,21 @@ import (
 	"v2ray.com/core/common/serial"
 )
 
-// ChunkSizeDecoder is an utility class to decode size value from bytes.
+// ChunkSizeDecoder is a utility class to decode size value from bytes.
 type ChunkSizeDecoder interface {
-	SizeBytes() int
+	SizeBytes() int32
 	Decode([]byte) (uint16, error)
 }
 
-// ChunkSizeEncoder is an utility class to encode size value into bytes.
+// ChunkSizeEncoder is a utility class to encode size value into bytes.
 type ChunkSizeEncoder interface {
-	SizeBytes() int
+	SizeBytes() int32
 	Encode(uint16, []byte) []byte
 }
 
 type PlainChunkSizeParser struct{}
 
-func (PlainChunkSizeParser) SizeBytes() int {
+func (PlainChunkSizeParser) SizeBytes() int32 {
 	return 2
 }
 
@@ -38,8 +38,8 @@ type AEADChunkSizeParser struct {
 	Auth *AEADAuthenticator
 }
 
-func (p *AEADChunkSizeParser) SizeBytes() int {
-	return 2 + p.Auth.Overhead()
+func (p *AEADChunkSizeParser) SizeBytes() int32 {
+	return 2 + int32(p.Auth.Overhead())
 }
 
 func (p *AEADChunkSizeParser) Encode(size uint16, b []byte) []byte {
@@ -62,7 +62,7 @@ type ChunkStreamReader struct {
 	reader      *buf.BufferedReader
 
 	buffer       []byte
-	leftOverSize int
+	leftOverSize int32
 }
 
 func NewChunkStreamReader(sizeDecoder ChunkSizeDecoder, reader io.Reader) *ChunkStreamReader {
@@ -90,7 +90,7 @@ func (r *ChunkStreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 		if nextSize == 0 {
 			return nil, io.EOF
 		}
-		size = int(nextSize)
+		size = int32(nextSize)
 	}
 	r.leftOverSize = size
 
@@ -125,7 +125,7 @@ func (w *ChunkStreamWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 		b := buf.New()
 		common.Must(b.Reset(func(buffer []byte) (int, error) {
 			w.sizeEncoder.Encode(uint16(slice.Len()), buffer[:0])
-			return w.sizeEncoder.SizeBytes(), nil
+			return int(w.sizeEncoder.SizeBytes()), nil
 		}))
 		mb2Write.Append(b)
 		mb2Write.AppendMulti(slice)

+ 4 - 4
common/crypto/chunk_test.go

@@ -12,7 +12,7 @@ import (
 func TestChunkStreamIO(t *testing.T) {
 	assert := With(t)
 
-	cache := buf.NewLocal(8192)
+	cache := buf.NewSize(8192)
 
 	writer := NewChunkStreamWriter(PlainChunkSizeParser{}, cache)
 	reader := NewChunkStreamReader(PlainChunkSizeParser{}, cache)
@@ -27,16 +27,16 @@ func TestChunkStreamIO(t *testing.T) {
 
 	assert(writer.WriteMultiBuffer(buf.MultiBuffer{}), IsNil)
 
-	assert(cache.Len(), Equals, 13)
+	assert(cache.Len(), Equals, int32(13))
 
 	mb, err := reader.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(mb.Len(), Equals, 4)
+	assert(mb.Len(), Equals, int32(4))
 	assert(mb[0].Bytes(), Equals, []byte("abcd"))
 
 	mb, err = reader.ReadMultiBuffer()
 	assert(err, IsNil)
-	assert(mb.Len(), Equals, 3)
+	assert(mb.Len(), Equals, int32(3))
 	assert(mb[0].Bytes(), Equals, []byte("efg"))
 
 	_, err = reader.ReadMultiBuffer()

+ 1 - 1
common/crypto/crypto.go

@@ -1,4 +1,4 @@
 // Package crypto provides common crypto libraries for V2Ray.
-package crypto
+package crypto // import "v2ray.com/core/common/crypto"
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg crypto -path Crypto

+ 1 - 1
common/dice/dice.go

@@ -1,6 +1,6 @@
 // Package dice contains common functions to generate random number.
 // It also initialize math/rand with the time in seconds at launch time.
-package dice
+package dice // import "v2ray.com/core/common/dice"
 
 import (
 	"math/rand"

+ 32 - 0
common/dice/dice_test.go

@@ -0,0 +1,32 @@
+package dice_test
+
+import (
+	"math/rand"
+	"testing"
+
+	. "v2ray.com/core/common/dice"
+)
+
+func BenchmarkRoll1(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		Roll(1)
+	}
+}
+
+func BenchmarkRoll20(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		Roll(20)
+	}
+}
+
+func BenchmarkIntn1(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		rand.Intn(1)
+	}
+}
+
+func BenchmarkIntn20(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		rand.Intn(20)
+	}
+}

+ 51 - 2
common/errors/errors.go

@@ -1,11 +1,13 @@
 // Package errors is a drop-in replacement for Golang lib 'errors'.
-package errors
+package errors // import "v2ray.com/core/common/errors"
 
 import (
+	"context"
 	"strings"
 
 	"v2ray.com/core/common/log"
 	"v2ray.com/core/common/serial"
+	"v2ray.com/core/common/session"
 )
 
 type hasInnerError interface {
@@ -17,12 +19,17 @@ type hasSeverity interface {
 	Severity() log.Severity
 }
 
+type hasContext interface {
+	Context() context.Context
+}
+
 // Error is an error object with underlying error.
 type Error struct {
 	message  []interface{}
 	inner    error
 	severity log.Severity
 	path     []string
+	ctx      context.Context
 }
 
 // Error implements error.Error().
@@ -50,6 +57,27 @@ func (v *Error) Base(err error) *Error {
 	return v
 }
 
+func (v *Error) WithContext(ctx context.Context) *Error {
+	v.ctx = ctx
+	return v
+}
+
+func (v *Error) Context() context.Context {
+	if v.ctx != nil {
+		return v.ctx
+	}
+
+	if v.inner == nil {
+		return nil
+	}
+
+	if c, ok := v.inner.(hasContext); ok {
+		return c.Context()
+	}
+
+	return nil
+}
+
 func (v *Error) atSeverity(s log.Severity) *Error {
 	v.severity = s
 	return v
@@ -103,9 +131,21 @@ func (v *Error) String() string {
 
 // WriteToLog writes current error into log.
 func (v *Error) WriteToLog() {
+	ctx := v.Context()
+	var sid session.ID
+	if ctx != nil {
+		sid = session.IDFromContext(ctx)
+	}
+	var c interface{} = v
+	if sid > 0 {
+		c = sessionLog{
+			id:      sid,
+			content: v,
+		}
+	}
 	log.Record(&log.GeneralMessage{
 		Severity: GetSeverity(v),
-		Content:  v,
+		Content:  c,
 	})
 }
 
@@ -139,3 +179,12 @@ func GetSeverity(err error) log.Severity {
 	}
 	return log.Severity_Info
 }
+
+type sessionLog struct {
+	id      session.ID
+	content interface{}
+}
+
+func (s sessionLog) String() string {
+	return serial.Concat("[", s.id, "] ", s.content)
+}

+ 19 - 0
common/interfaces.go

@@ -21,3 +21,22 @@ type Runnable interface {
 
 	Closable
 }
+
+// HasType is the interface for objects that knows its type.
+type HasType interface {
+	// Type returns the type of the object.
+	Type() interface{}
+}
+
+type ChainedClosable []Closable
+
+func NewChainedClosable(c ...Closable) ChainedClosable {
+	return ChainedClosable(c)
+}
+
+func (cc ChainedClosable) Close() error {
+	for _, c := range cc {
+		c.Close()
+	}
+	return nil
+}

+ 2 - 2
common/log/log.go

@@ -1,4 +1,4 @@
-package log
+package log // import "v2ray.com/core/common/log"
 
 import (
 	"sync"
@@ -24,7 +24,7 @@ type GeneralMessage struct {
 
 // String implements Message.
 func (m *GeneralMessage) String() string {
-	return serial.Concat("[", m.Severity, "]: ", m.Content)
+	return serial.Concat("[", m.Severity, "] ", m.Content)
 }
 
 // Record writes a message into log stream.

+ 1 - 1
common/log/log_test.go

@@ -28,5 +28,5 @@ func TestLogRecord(t *testing.T) {
 		Content:  net.ParseAddress(ip),
 	})
 
-	assert(logger.value, Equals, "[Error]: "+ip)
+	assert(logger.value, Equals, "[Error] "+ip)
 }

+ 8 - 0
common/log/logger.go

@@ -23,6 +23,7 @@ type generalLogger struct {
 	creator WriterCreator
 	buffer  chan Message
 	access  *signal.Semaphore
+	done    *signal.Done
 }
 
 // NewLogger returns a generic log handler that can handle all type of messages.
@@ -31,6 +32,7 @@ func NewLogger(logWriterCreator WriterCreator) Handler {
 		creator: logWriterCreator,
 		buffer:  make(chan Message, 16),
 		access:  signal.NewSemaphore(1),
+		done:    signal.NewDone(),
 	}
 }
 
@@ -49,6 +51,8 @@ func (l *generalLogger) run() {
 
 	for {
 		select {
+		case <-l.done.C():
+			return
 		case msg := <-l.buffer:
 			logger.Write(msg.String() + platform.LineSeparator())
 			dataWritten = true
@@ -74,6 +78,10 @@ func (l *generalLogger) Handle(msg Message) {
 	}
 }
 
+func (l *generalLogger) Close() error {
+	return l.done.Close()
+}
+
 type consoleLogWriter struct {
 	logger *log.Logger
 }

+ 40 - 0
common/log/logger_test.go

@@ -0,0 +1,40 @@
+package log_test
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/buf"
+	. "v2ray.com/core/common/log"
+	. "v2ray.com/ext/assert"
+)
+
+func TestFileLogger(t *testing.T) {
+	assert := With(t)
+
+	f, err := ioutil.TempFile("", "vtest")
+	assert(err, IsNil)
+	path := f.Name()
+	common.Must(f.Close())
+
+	creator, err := CreateFileLogWriter(path)
+	assert(err, IsNil)
+
+	handler := NewLogger(creator)
+	handler.Handle(&GeneralMessage{Content: "Test Log"})
+	time.Sleep(2 * time.Second)
+
+	common.Must(common.Close(handler))
+
+	f, err = os.Open(path)
+	assert(err, IsNil)
+
+	b, err := buf.ReadAllToBytes(f)
+	assert(err, IsNil)
+	assert(string(b), HasSubstring, "Test Log")
+
+	common.Must(f.Close())
+}

+ 1 - 1
common/net/address.go

@@ -22,7 +22,7 @@ var (
 )
 
 // AddressFamily is the type of address.
-type AddressFamily int
+type AddressFamily byte
 
 const (
 	// AddressFamilyIPv4 represents address as IPv4

+ 3 - 3
common/net/destination.go

@@ -6,9 +6,9 @@ import (
 
 // Destination represents a network destination including address and protocol (tcp / udp).
 type Destination struct {
-	Network Network
-	Port    Port
 	Address Address
+	Port    Port
+	Network Network
 }
 
 // DestinationFromAddr generates a Destination from a net address.
@@ -56,7 +56,7 @@ func (d Destination) IsValid() bool {
 	return d.Network != Network_Unknown
 }
 
-// AsDestination converts current Enpoint into Destination.
+// AsDestination converts current Endpoint into Destination.
 func (p *Endpoint) AsDestination() Destination {
 	return Destination{
 		Network: p.Network,

+ 1 - 1
common/net/net.go

@@ -1,4 +1,4 @@
 // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
-package net
+package net // import "v2ray.com/core/common/net"
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg net -path Net

+ 9 - 0
common/net/network.go

@@ -46,6 +46,15 @@ func (n Network) URLPrefix() string {
 	}
 }
 
+func HasNetwork(list []Network, network Network) bool {
+	for _, value := range list {
+		if string(value) == string(network) {
+			return true
+		}
+	}
+	return false
+}
+
 // HasNetwork returns true if the given network is in v NetworkList.
 func (l NetworkList) HasNetwork(network Network) bool {
 	for _, value := range l.Network {

+ 3 - 3
common/platform/platform.go

@@ -1,4 +1,4 @@
-package platform
+package platform // import "v2ray.com/core/common/platform"
 
 import (
 	"os"
@@ -53,7 +53,7 @@ func getExecutableDir() string {
 	return filepath.Dir(exec)
 }
 
-func getExecuableSubDir(dir string) func() string {
+func getExecutableSubDir(dir string) func() string {
 	return func() string {
 		return filepath.Join(getExecutableDir(), dir)
 	}
@@ -67,7 +67,7 @@ func GetAssetLocation(file string) string {
 
 func GetPluginDirectory() string {
 	const name = "v2ray.location.plugin"
-	pluginDir := EnvFlag{Name: name, AltName: NormalizeEnvName(name)}.GetValue(getExecuableSubDir("plugins"))
+	pluginDir := EnvFlag{Name: name, AltName: NormalizeEnvName(name)}.GetValue(getExecutableSubDir("plugins"))
 	return pluginDir
 }
 

+ 1 - 1
common/predicate/predicate.go

@@ -1,4 +1,4 @@
-package predicate
+package predicate // import "v2ray.com/core/common/predicate"
 
 type Predicate func() bool
 

+ 1 - 1
common/protocol/account.go

@@ -1,6 +1,6 @@
 package protocol
 
-// Account is an user identity used for authentication.
+// Account is a user identity used for authentication.
 type Account interface {
 	Equals(Account) bool
 }

+ 204 - 0
common/protocol/address.go

@@ -0,0 +1,204 @@
+package protocol
+
+import (
+	"io"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/net"
+)
+
+type AddressOption func(*AddressParser)
+
+func PortThenAddress() AddressOption {
+	return func(p *AddressParser) {
+		p.portFirst = true
+	}
+}
+
+func AddressFamilyByte(b byte, f net.AddressFamily) AddressOption {
+	return func(p *AddressParser) {
+		p.addrTypeMap[b] = f
+		p.addrByteMap[f] = b
+	}
+}
+
+type AddressTypeParser func(byte) byte
+
+func WithAddressTypeParser(atp AddressTypeParser) AddressOption {
+	return func(p *AddressParser) {
+		p.typeParser = atp
+	}
+}
+
+type AddressParser struct {
+	addrTypeMap map[byte]net.AddressFamily
+	addrByteMap map[net.AddressFamily]byte
+	portFirst   bool
+	typeParser  AddressTypeParser
+}
+
+func NewAddressParser(options ...AddressOption) *AddressParser {
+	p := &AddressParser{
+		addrTypeMap: make(map[byte]net.AddressFamily, 8),
+		addrByteMap: make(map[net.AddressFamily]byte, 8),
+	}
+	for _, opt := range options {
+		opt(p)
+	}
+	return p
+}
+
+func (p *AddressParser) readPort(b *buf.Buffer, reader io.Reader) (net.Port, error) {
+	if err := b.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+		return 0, err
+	}
+	return net.PortFromBytes(b.BytesFrom(-2)), nil
+}
+
+func maybeIPPrefix(b byte) bool {
+	return b == '[' || (b >= '0' && b <= '9')
+}
+
+func isValidDomain(d string) bool {
+	for _, c := range d {
+		if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '.' || c == '_') {
+			return false
+		}
+	}
+	return true
+}
+
+func (p *AddressParser) readAddress(b *buf.Buffer, reader io.Reader) (net.Address, error) {
+	if err := b.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+		return nil, err
+	}
+
+	addrType := b.Byte(b.Len() - 1)
+	if p.typeParser != nil {
+		addrType = p.typeParser(addrType)
+	}
+
+	addrFamily, valid := p.addrTypeMap[addrType]
+	if !valid {
+		return nil, newError("unknown address type: ", addrType)
+	}
+
+	switch addrFamily {
+	case net.AddressFamilyIPv4:
+		if err := b.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil {
+			return nil, err
+		}
+		return net.IPAddress(b.BytesFrom(-4)), nil
+	case net.AddressFamilyIPv6:
+		if err := b.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil {
+			return nil, err
+		}
+		return net.IPAddress(b.BytesFrom(-16)), nil
+	case net.AddressFamilyDomain:
+		if err := b.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+			return nil, err
+		}
+		domainLength := int32(b.Byte(b.Len() - 1))
+		if err := b.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil {
+			return nil, err
+		}
+		domain := string(b.BytesFrom(-domainLength))
+		if maybeIPPrefix(domain[0]) {
+			addr := net.ParseAddress(domain)
+			if addr.Family().IsIPv4() || addrFamily.IsIPv6() {
+				return addr, nil
+			}
+		}
+		if !isValidDomain(domain) {
+			return nil, newError("invalid domain name: ", domain)
+		}
+		return net.DomainAddress(domain), nil
+	default:
+		panic("impossible case")
+	}
+}
+
+func (p *AddressParser) ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) {
+	if buffer == nil {
+		buffer = buf.New()
+		defer buffer.Release()
+	}
+
+	if p.portFirst {
+		port, err := p.readPort(buffer, input)
+		if err != nil {
+			return nil, 0, err
+		}
+		addr, err := p.readAddress(buffer, input)
+		if err != nil {
+			return nil, 0, err
+		}
+		return addr, port, nil
+	}
+
+	addr, err := p.readAddress(buffer, input)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	port, err := p.readPort(buffer, input)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	return addr, port, nil
+}
+
+func (p *AddressParser) writePort(writer io.Writer, port net.Port) error {
+	_, err := writer.Write(port.Bytes(nil))
+	return err
+}
+
+func (p *AddressParser) writeAddress(writer io.Writer, address net.Address) error {
+	tb, valid := p.addrByteMap[address.Family()]
+	if !valid {
+		return newError("unknown address family", address.Family())
+	}
+
+	switch address.Family() {
+	case net.AddressFamilyIPv4, net.AddressFamilyIPv6:
+		if _, err := writer.Write([]byte{tb}); err != nil {
+			return err
+		}
+		if _, err := writer.Write(address.IP()); err != nil {
+			return err
+		}
+	case net.AddressFamilyDomain:
+		domain := address.Domain()
+		if isDomainTooLong(domain) {
+			return newError("Super long domain is not supported: ", domain)
+		}
+		if _, err := writer.Write([]byte{tb, byte(len(domain))}); err != nil {
+			return err
+		}
+		if _, err := writer.Write([]byte(domain)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (p *AddressParser) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error {
+	if p.portFirst {
+		if err := p.writePort(writer, port); err != nil {
+			return err
+		}
+		if err := p.writeAddress(writer, addr); err != nil {
+			return err
+		}
+		return nil
+	}
+
+	if err := p.writeAddress(writer, addr); err != nil {
+		return err
+	}
+	if err := p.writePort(writer, port); err != nil {
+		return err
+	}
+	return nil
+}

+ 118 - 0
common/protocol/address_test.go

@@ -0,0 +1,118 @@
+package protocol_test
+
+import (
+	"bytes"
+	"testing"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/net"
+	. "v2ray.com/core/common/protocol"
+	. "v2ray.com/ext/assert"
+)
+
+func TestAddressReading(t *testing.T) {
+	assert := With(t)
+
+	data := []struct {
+		Options []AddressOption
+		Input   []byte
+		Address net.Address
+		Port    net.Port
+		Error   bool
+	}{
+		{
+			Options: []AddressOption{},
+			Input:   []byte{},
+			Error:   true,
+		},
+		{
+			Options: []AddressOption{},
+			Input:   []byte{0, 0, 0, 0, 0},
+			Error:   true,
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},
+			Input:   []byte{1, 0, 0, 0, 0, 0, 53},
+			Address: net.IPAddress([]byte{0, 0, 0, 0}),
+			Port:    net.Port(53),
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},
+			Input:   []byte{1, 0, 0, 0, 0},
+			Error:   true,
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x04, net.AddressFamilyIPv6)},
+			Input:   []byte{4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 80},
+			Address: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}),
+			Port:    net.Port(80),
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},
+			Input:   []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0, 80},
+			Address: net.DomainAddress("v2ray.com"),
+			Port:    net.Port(80),
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},
+			Input:   []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0},
+			Error:   true,
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},
+			Input:   []byte{3, 7, 56, 46, 56, 46, 56, 46, 56, 0, 80},
+			Address: net.ParseAddress("8.8.8.8"),
+			Port:    net.Port(80),
+		},
+		{
+			Options: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},
+			Input:   []byte{3, 7, 10, 46, 56, 46, 56, 46, 56, 0, 80},
+			Error:   true,
+		},
+	}
+
+	for _, tc := range data {
+		b := buf.New()
+		parser := NewAddressParser(tc.Options...)
+		addr, port, err := parser.ReadAddressPort(b, bytes.NewReader(tc.Input))
+		b.Release()
+		if tc.Error {
+			assert(err, IsNotNil)
+		} else {
+			assert(addr, Equals, tc.Address)
+			assert(port, Equals, tc.Port)
+			assert(err, IsNil)
+		}
+	}
+}
+
+func TestAddressWriting(t *testing.T) {
+	assert := With(t)
+
+	data := []struct {
+		Options []AddressOption
+		Address net.Address
+		Port    net.Port
+		Bytes   []byte
+		Error   bool
+	}{
+		{
+			Options: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},
+			Address: net.LocalHostIP,
+			Port:    net.Port(80),
+			Bytes:   []byte{1, 127, 0, 0, 1, 0, 80},
+		},
+	}
+
+	for _, tc := range data {
+		parser := NewAddressParser(tc.Options...)
+
+		b := buf.New()
+		err := parser.WriteAddressPort(b, tc.Address, tc.Port)
+		if tc.Error {
+			assert(err, IsNotNil)
+		} else {
+			assert(b.Bytes(), Equals, tc.Bytes)
+		}
+	}
+}

+ 2 - 2
common/protocol/context.go

@@ -10,12 +10,12 @@ const (
 	userKey key = iota
 )
 
-// ContextWithUser returns a context combined with an User.
+// ContextWithUser returns a context combined with a User.
 func ContextWithUser(ctx context.Context, user *User) context.Context {
 	return context.WithValue(ctx, userKey, user)
 }
 
-// UserFromContext extracts an User from the given context, if any.
+// UserFromContext extracts a User from the given context, if any.
 func UserFromContext(ctx context.Context) *User {
 	v := ctx.Value(userKey)
 	if v == nil {

+ 6 - 19
common/protocol/headers.go

@@ -38,24 +38,11 @@ const (
 	RequestOptionChunkMasking bitmask.Byte = 0x04
 )
 
-type Security byte
-
-func (s Security) Is(t SecurityType) bool {
-	return s == Security(t)
-}
-
-func NormSecurity(s Security) Security {
-	if s.Is(SecurityType_UNKNOWN) {
-		return Security(SecurityType_LEGACY)
-	}
-	return s
-}
-
 type RequestHeader struct {
 	Version  byte
 	Command  RequestCommand
 	Option   bitmask.Byte
-	Security Security
+	Security SecurityType
 	Port     net.Port
 	Address  net.Address
 	User     *User
@@ -88,16 +75,16 @@ type CommandSwitchAccount struct {
 	ValidMin byte
 }
 
-func (sc *SecurityConfig) AsSecurity() Security {
+func (sc *SecurityConfig) GetSecurityType() SecurityType {
 	if sc == nil || sc.Type == SecurityType_AUTO {
 		if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" {
-			return Security(SecurityType_AES128_GCM)
+			return SecurityType_AES128_GCM
 		}
-		return Security(SecurityType_CHACHA20_POLY1305)
+		return SecurityType_CHACHA20_POLY1305
 	}
-	return NormSecurity(Security(sc.Type))
+	return sc.Type
 }
 
-func IsDomainTooLong(domain string) bool {
+func isDomainTooLong(domain string) bool {
 	return len(domain) > 256
 }

+ 1 - 1
common/protocol/id.go

@@ -19,7 +19,7 @@ func DefaultIDHash(key []byte) hash.Hash {
 	return hmac.New(md5.New, key)
 }
 
-// The ID of en entity, in the form of an UUID.
+// The ID of en entity, in the form of a UUID.
 type ID struct {
 	uuid   uuid.UUID
 	cmdKey [IDBytesLen]byte

+ 1 - 1
common/protocol/payload.go

@@ -1,6 +1,6 @@
 package protocol
 
-type TransferType int
+type TransferType byte
 
 const (
 	TransferTypeStream TransferType = 0

+ 1 - 1
common/protocol/protocol.go

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

+ 4 - 4
common/protocol/server_spec.go

@@ -110,10 +110,10 @@ func (s *ServerSpec) PickUser() *User {
 	}
 }
 
-func (v *ServerSpec) IsValid() bool {
-	return v.valid.IsValid()
+func (s *ServerSpec) IsValid() bool {
+	return s.valid.IsValid()
 }
 
-func (v *ServerSpec) Invalidate() {
-	v.valid.Invalidate()
+func (s *ServerSpec) Invalidate() {
+	s.valid.Invalidate()
 }

+ 1 - 1
common/retry/retry.go

@@ -1,4 +1,4 @@
-package retry
+package retry // import "v2ray.com/core/common/retry"
 
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg retry -path Retry
 

+ 2 - 2
common/serial/bytes.go

@@ -7,12 +7,12 @@ func ByteToHexString(value byte) string {
 	return hex.EncodeToString([]byte{value})
 }
 
-// BytesToUint16 deserializes a byte array to an uint16 in big endian order. The byte array must have at least 2 elements.
+// BytesToUint16 deserializes a byte array to a uint16 in big endian order. The byte array must have at least 2 elements.
 func BytesToUint16(value []byte) uint16 {
 	return uint16(value[0])<<8 | uint16(value[1])
 }
 
-// BytesToUint32 deserializes a byte array to an uint32 in big endian order. The byte array must have at least 4 elements.
+// BytesToUint32 deserializes a byte array to a uint32 in big endian order. The byte array must have at least 4 elements.
 func BytesToUint32(value []byte) uint32 {
 	return uint32(value[0])<<24 |
 		uint32(value[1])<<16 |

+ 1 - 1
common/serial/numbers.go

@@ -3,7 +3,7 @@ package serial
 import "strconv"
 import "io"
 
-// Uint16ToBytes serializes an uint16 into bytes in big endian order.
+// Uint16ToBytes serializes a uint16 into bytes in big endian order.
 func Uint16ToBytes(value uint16, b []byte) []byte {
 	return append(b, byte(value>>8), byte(value))
 }

+ 28 - 0
common/serial/string_test.go

@@ -0,0 +1,28 @@
+package serial_test
+
+import (
+	"errors"
+	"testing"
+
+	. "v2ray.com/core/common/serial"
+	. "v2ray.com/ext/assert"
+)
+
+func TestToString(t *testing.T) {
+	assert := With(t)
+
+	s := "a"
+	data := []struct {
+		Value  interface{}
+		String string
+	}{
+		{Value: s, String: s},
+		{Value: &s, String: s},
+		{Value: errors.New("t"), String: "t"},
+		{Value: []byte{'b', 'c'}, String: "[62,63]"},
+	}
+
+	for _, c := range data {
+		assert(ToString(c.Value), Equals, c.String)
+	}
+}

+ 40 - 0
common/session/session.go

@@ -0,0 +1,40 @@
+// Package session provides functions for sessions of incoming requests.
+package session // import "v2ray.com/core/common/session"
+
+import (
+	"context"
+	"math/rand"
+)
+
+// ID of a session.
+type ID uint32
+
+// NewID generates a new ID. The generated ID is high likely to be unique, but not cryptographically secure.
+// The generated ID will never be 0.
+func NewID() ID {
+	for {
+		id := ID(rand.Uint32())
+		if id != 0 {
+			return id
+		}
+	}
+}
+
+type sessionKey int
+
+const (
+	idSessionKey sessionKey = iota
+)
+
+// ContextWithID returns a new context with the given ID.
+func ContextWithID(ctx context.Context, id ID) context.Context {
+	return context.WithValue(ctx, idSessionKey, id)
+}
+
+// IDFromContext returns ID in this context, or 0 if not contained.
+func IDFromContext(ctx context.Context) ID {
+	if id, ok := ctx.Value(idSessionKey).(ID); ok {
+		return id
+	}
+	return 0
+}

+ 2 - 2
common/signal/done.go

@@ -4,7 +4,7 @@ import (
 	"sync"
 )
 
-// Done is an utility for notifications of something being done.
+// Done is a utility for notifications of something being done.
 type Done struct {
 	access sync.Mutex
 	c      chan struct{}
@@ -38,7 +38,7 @@ func (d *Done) Wait() {
 	<-d.c
 }
 
-// Close marks this Done 'done'. This method may be called mutliple times. All calls after first call will have no effect on its status.
+// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.
 func (d *Done) Close() error {
 	d.access.Lock()
 	defer d.access.Unlock()

+ 1 - 1
common/signal/exec.go

@@ -12,7 +12,7 @@ func executeAndFulfill(f func() error, done chan<- error) {
 	close(done)
 }
 
-// ExecuteAsync executes a function asychrously and return its result.
+// ExecuteAsync executes a function asynchronously and return its result.
 func ExecuteAsync(f func() error) <-chan error {
 	done := make(chan error, 1)
 	go executeAndFulfill(f, done)

+ 1 - 1
common/signal/notifier.go

@@ -1,6 +1,6 @@
 package signal
 
-// Notifier is an utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asychronously.
+// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.
 type Notifier struct {
 	c chan struct{}
 }

+ 1 - 1
common/signal/task.go

@@ -50,7 +50,7 @@ func (t *PeriodicTask) Start() error {
 	return nil
 }
 
-// Close implements common.Runnable.
+// Close implements common.Closable.
 func (t *PeriodicTask) Close() error {
 	t.access.Lock()
 	defer t.access.Unlock()

+ 7 - 7
common/uuid/uuid.go

@@ -1,4 +1,4 @@
-package uuid
+package uuid // import "v2ray.com/core/common/uuid"
 
 import (
 	"bytes"
@@ -49,26 +49,26 @@ func (u *UUID) Equals(another *UUID) bool {
 // Next generates a deterministic random UUID based on this UUID.
 func (u *UUID) Next() UUID {
 	md5hash := md5.New()
-	md5hash.Write(u.Bytes())
-	md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))
+	common.Must2(md5hash.Write(u.Bytes()))
+	common.Must2(md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")))
 	var newid UUID
 	for {
 		md5hash.Sum(newid[:0])
 		if !newid.Equals(u) {
 			return newid
 		}
-		md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))
+		common.Must2(md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")))
 	}
 }
 
-// New creates an UUID with random value.
+// New creates a UUID with random value.
 func New() UUID {
 	var uuid UUID
 	common.Must2(rand.Read(uuid.Bytes()))
 	return uuid
 }
 
-// ParseBytes converts an UUID in byte form to object.
+// ParseBytes converts a UUID in byte form to object.
 func ParseBytes(b []byte) (UUID, error) {
 	var uuid UUID
 	if len(b) != 16 {
@@ -78,7 +78,7 @@ func ParseBytes(b []byte) (UUID, error) {
 	return uuid, nil
 }
 
-// ParseString converts an UUID in string form to object.
+// ParseString converts a UUID in string form to object.
 func ParseString(str string) (UUID, error) {
 	var uuid UUID
 

+ 88 - 0
config.go

@@ -0,0 +1,88 @@
+package core
+
+import (
+	"io"
+	"strings"
+
+	"github.com/golang/protobuf/proto"
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/buf"
+)
+
+// ConfigFormat is a configurable format of V2Ray config file.
+type ConfigFormat struct {
+	Name      string
+	Extension []string
+	Loader    ConfigLoader
+}
+
+// ConfigLoader is a utility to load V2Ray config from external source.
+type ConfigLoader func(input io.Reader) (*Config, error)
+
+var (
+	configLoaderByName = make(map[string]*ConfigFormat)
+	configLoaderByExt  = make(map[string]*ConfigFormat)
+)
+
+// RegisterConfigLoader add a new ConfigLoader.
+func RegisterConfigLoader(format *ConfigFormat) error {
+	name := strings.ToLower(format.Name)
+	if _, found := configLoaderByName[name]; found {
+		return newError(format.Name, " already registered.")
+	}
+	configLoaderByName[name] = format
+
+	for _, ext := range format.Extension {
+		lext := strings.ToLower(ext)
+		if f, found := configLoaderByExt[lext]; found {
+			return newError(ext, " already registered to ", f.Name)
+		}
+		configLoaderByExt[lext] = format
+	}
+
+	return nil
+}
+
+func getExtension(filename string) string {
+	idx := strings.LastIndexByte(filename, '.')
+	if idx == -1 {
+		return ""
+	}
+	return filename[idx+1:]
+}
+
+// LoadConfig loads config with given format from given source.
+func LoadConfig(formatName string, filename string, input io.Reader) (*Config, error) {
+	ext := getExtension(filename)
+	if len(ext) > 0 {
+		if f, found := configLoaderByExt[ext]; found {
+			return f.Loader(input)
+		}
+	}
+
+	if f, found := configLoaderByName[formatName]; found {
+		return f.Loader(input)
+	}
+
+	return nil, newError("Unable to load config in ", formatName).AtWarning()
+}
+
+func loadProtobufConfig(input io.Reader) (*Config, error) {
+	config := new(Config)
+	data, err := buf.ReadAllToBytes(input)
+	if err != nil {
+		return nil, err
+	}
+	if err := proto.Unmarshal(data, config); err != nil {
+		return nil, err
+	}
+	return config, nil
+}
+
+func init() {
+	common.Must(RegisterConfigLoader(&ConfigFormat{
+		Name:      "Protobuf",
+		Extension: []string{"pb"},
+		Loader:    loadProtobufConfig,
+	}))
+}

+ 34 - 57
config.pb.go

@@ -17,35 +17,13 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
 
-// Configuration serialization format.
-type ConfigFormat int32
-
-const (
-	ConfigFormat_Protobuf ConfigFormat = 0
-	ConfigFormat_JSON     ConfigFormat = 1
-)
-
-var ConfigFormat_name = map[int32]string{
-	0: "Protobuf",
-	1: "JSON",
-}
-var ConfigFormat_value = map[string]int32{
-	"Protobuf": 0,
-	"JSON":     1,
-}
-
-func (x ConfigFormat) String() string {
-	return proto.EnumName(ConfigFormat_name, int32(x))
-}
-func (ConfigFormat) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
-
-// Master config of V2Ray. V2Ray takes this config as input and functions accordingly.
+// Config is the master config of V2Ray. V2Ray takes this config as input and functions accordingly.
 type Config struct {
 	// Inbound handler configurations. Must have at least one item.
 	Inbound []*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 []*OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound" json:"outbound,omitempty"`
-	// App configuration. Must be one in the app directory.
+	// App is for configurations of all features in V2Ray. A feature must implement the Feature interface, and its config type must be registered through common.RegisterConfig.
 	App []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=app" json:"app,omitempty"`
 	// Transport settings.
 	Transport *v2ray_core_transport.Config `protobuf:"bytes,5,opt,name=transport" json:"transport,omitempty"`
@@ -94,10 +72,11 @@ func (m *Config) GetExtension() []*v2ray_core_common_serial.TypedMessage {
 	return nil
 }
 
+// InboundHandlerConfig is the configuration for inbound handler.
 type InboundHandlerConfig struct {
-	// Tag of the inbound handler.
+	// Tag of the inbound handler. The tag must be unique among all inbound handlers
 	Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"`
-	// Settings for how this inbound proxy is handled. Must be ReceiverConfig above.
+	// Settings for how this inbound proxy is handled.
 	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"`
@@ -129,10 +108,11 @@ func (m *InboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.Type
 	return nil
 }
 
+// OutboundHandlerConfig is the configuration for outbound handler.
 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.
+	// Settings for how to dial connection for this outbound handler.
 	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"`
@@ -186,39 +166,36 @@ func init() {
 	proto.RegisterType((*Config)(nil), "v2ray.core.Config")
 	proto.RegisterType((*InboundHandlerConfig)(nil), "v2ray.core.InboundHandlerConfig")
 	proto.RegisterType((*OutboundHandlerConfig)(nil), "v2ray.core.OutboundHandlerConfig")
-	proto.RegisterEnum("v2ray.core.ConfigFormat", ConfigFormat_name, ConfigFormat_value)
 }
 
 func init() { proto.RegisterFile("v2ray.com/core/config.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 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,
+	// 405 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x41, 0x6f, 0x94, 0x40,
+	0x1c, 0xc5, 0x33, 0x0b, 0x6e, 0xcb, 0xbf, 0x71, 0x5d, 0x27, 0x6a, 0x26, 0xd5, 0x03, 0x6e, 0x52,
+	0xc3, 0x69, 0x30, 0x78, 0x31, 0x4d, 0xbc, 0x58, 0x0f, 0x6a, 0xd2, 0xd4, 0x50, 0xe3, 0xc1, 0x4b,
+	0x33, 0x85, 0xbf, 0x84, 0xa4, 0xcc, 0x90, 0x99, 0x69, 0xb3, 0x7c, 0x25, 0xbf, 0x87, 0x37, 0xbf,
+	0x91, 0x17, 0x03, 0x03, 0x0b, 0xd5, 0x3d, 0xb8, 0x26, 0x3d, 0xc1, 0xf0, 0xe6, 0xf7, 0xde, 0xff,
+	0x01, 0x03, 0x4f, 0x6f, 0x12, 0x2d, 0x1a, 0x9e, 0xa9, 0x2a, 0xce, 0x94, 0xc6, 0x38, 0x53, 0xf2,
+	0x5b, 0x59, 0xf0, 0x5a, 0x2b, 0xab, 0x28, 0x0c, 0xa2, 0xc6, 0xc3, 0x97, 0x7f, 0x6d, 0xac, 0x2a,
+	0x25, 0x63, 0x83, 0xba, 0x14, 0x57, 0xb1, 0x6d, 0x6a, 0xcc, 0x2f, 0x2a, 0x34, 0x46, 0x14, 0xe8,
+	0xe8, 0xc3, 0xa3, 0x3f, 0x08, 0xab, 0x85, 0x34, 0xb5, 0xd2, 0xf6, 0x56, 0xc8, 0xea, 0xc7, 0x0c,
+	0xe6, 0x27, 0xdd, 0x03, 0x7a, 0x0c, 0x7b, 0xa5, 0xbc, 0x54, 0xd7, 0x32, 0x67, 0x24, 0xf4, 0xa2,
+	0x83, 0x24, 0xe4, 0xe3, 0x04, 0xfc, 0x83, 0x93, 0xde, 0x0b, 0x99, 0x5f, 0xa1, 0x76, 0x48, 0x3a,
+	0x00, 0xf4, 0x0d, 0xec, 0xab, 0x6b, 0xeb, 0xe0, 0x59, 0x07, 0x3f, 0x9f, 0xc2, 0x67, 0xbd, 0x76,
+	0x9b, 0xde, 0x20, 0xf4, 0x35, 0x78, 0xa2, 0xae, 0x99, 0xdf, 0x91, 0x2f, 0xa6, 0xa4, 0x2b, 0xca,
+	0x5d, 0x51, 0xfe, 0xb9, 0x2d, 0x7a, 0xea, 0x7a, 0xa6, 0x2d, 0x42, 0x8f, 0x21, 0xd8, 0x34, 0x63,
+	0xf7, 0x42, 0x12, 0x1d, 0x24, 0xcf, 0xa6, 0xfc, 0x46, 0xe4, 0x7d, 0xe8, 0xb8, 0x9d, 0xbe, 0x83,
+	0x00, 0xd7, 0x16, 0xa5, 0x29, 0x95, 0x64, 0xf3, 0x9d, 0xb2, 0x47, 0xf0, 0xa3, 0xbf, 0xef, 0x2d,
+	0xfd, 0xd5, 0x4f, 0x02, 0x8f, 0xb6, 0xbd, 0x22, 0xba, 0x04, 0xcf, 0x8a, 0x82, 0x91, 0x90, 0x44,
+	0x41, 0xda, 0xde, 0xd2, 0x73, 0x78, 0xa8, 0x31, 0xc3, 0xf2, 0x06, 0xf5, 0x85, 0x41, 0x6b, 0x4b,
+	0x59, 0x18, 0x36, 0xeb, 0x46, 0xff, 0xd7, 0xf8, 0xe5, 0x60, 0x70, 0xde, 0xf3, 0xf4, 0x14, 0x16,
+	0xb5, 0x56, 0xeb, 0x66, 0x74, 0xf4, 0x76, 0x72, 0xbc, 0xdf, 0xd1, 0x83, 0xdd, 0xea, 0x17, 0x81,
+	0xc7, 0x5b, 0x3f, 0xda, 0x96, 0x3e, 0x67, 0xf0, 0xc0, 0xa0, 0xcc, 0xff, 0xbf, 0xcd, 0xc2, 0xe1,
+	0x77, 0xd4, 0x85, 0x3e, 0x81, 0x39, 0xae, 0xeb, 0x52, 0x23, 0xf3, 0x43, 0x12, 0x79, 0x69, 0xbf,
+	0xa2, 0x0c, 0xf6, 0x5a, 0x13, 0x94, 0xee, 0xc7, 0x09, 0xd2, 0x61, 0xf9, 0xf6, 0x08, 0x16, 0x99,
+	0xaa, 0x26, 0x69, 0x9f, 0xc8, 0x57, 0xbf, 0xbd, 0x7e, 0x9f, 0xc1, 0x97, 0x24, 0x15, 0x0d, 0x3f,
+	0x51, 0x1a, 0x2f, 0xe7, 0xdd, 0x11, 0x7a, 0xf5, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x15, 0xee,
+	0x31, 0xc6, 0x03, 0x00, 0x00,
 }

+ 7 - 11
config.proto

@@ -9,13 +9,7 @@ option java_multiple_files = true;
 import "v2ray.com/core/common/serial/typed_message.proto";
 import "v2ray.com/core/transport/config.proto";
 
-// Configuration serialization format.
-enum ConfigFormat {
-  Protobuf = 0;
-  JSON = 1;
-}
-
-// Master config of V2Ray. V2Ray takes this config as input and functions accordingly.
+// Config is the master config of V2Ray. V2Ray takes this config as input and functions accordingly.
 message Config {
   // Inbound handler configurations. Must have at least one item.
   repeated InboundHandlerConfig inbound = 1;
@@ -25,7 +19,7 @@ message Config {
 
   reserved 3;
 
-  // App configuration. Must be one in the app directory.
+  // App is for configurations of all features in V2Ray. A feature must implement the Feature interface, and its config type must be registered through common.RegisterConfig.
   repeated v2ray.core.common.serial.TypedMessage app = 4;
 
   // Transport settings.
@@ -36,19 +30,21 @@ message Config {
   repeated v2ray.core.common.serial.TypedMessage extension = 6;
 }
 
+// InboundHandlerConfig is the configuration for inbound handler.
 message InboundHandlerConfig {
-  // Tag of the inbound handler.
+  // Tag of the inbound handler. The tag must be unique among all inbound handlers
   string tag = 1;
-  // Settings for how this inbound proxy is handled. Must be ReceiverConfig above.
+  // Settings for how this inbound proxy is handled.
   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;
 }
 
+// OutboundHandlerConfig is the configuration for outbound handler.
 message OutboundHandlerConfig {
   // Tag of this outbound handler.
   string tag = 1;
-  // Settings for how to dial connection for this outbound handler. Must be SenderConfig above.
+  // Settings for how to dial connection for this outbound handler.
   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;

+ 10 - 1
context.go

@@ -8,10 +8,19 @@ type key int
 
 const v2rayKey key = 1
 
-// FromContext returns a Instance from the given context, or nil if the context doesn't contain one.
+// FromContext returns an 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
 }
+
+// MustFromContext returns an Instance from the given context, or panics if not present.
+func MustFromContext(ctx context.Context) *Instance {
+	v := FromContext(ctx)
+	if v == nil {
+		panic("V is not in context.")
+	}
+	return v
+}

+ 9 - 9
core.go

@@ -12,16 +12,14 @@ package core
 //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg core -path Core
 
 import (
-	"fmt"
-
-	"v2ray.com/core/common/platform"
+	"v2ray.com/core/common/serial"
 )
 
 var (
-	version  = "3.9"
+	version  = "3.16"
 	build    = "Custom"
 	codename = "die Commanderin"
-	intro    = "An unified platform for anti-censorship."
+	intro    = "A unified platform for anti-censorship."
 )
 
 // Version returns V2Ray's version as a string, in the form of "x.y.z" where x, y and z are numbers.
@@ -30,10 +28,12 @@ func Version() string {
 	return version
 }
 
-// PrintVersion prints current version into console.
-func PrintVersion() {
-	fmt.Printf("V2Ray %s (%s) %s%s", Version(), codename, build, platform.LineSeparator())
-	fmt.Printf("%s%s", intro, platform.LineSeparator())
+// VersionStatement returns a list of strings representing the full version info.
+func VersionStatement() []string {
+	return []string{
+		serial.Concat("V2Ray ", Version(), " (", codename, ") ", build),
+		intro,
+	}
 }
 
 /*

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