diff --git a/.travis.yml b/.travis.yml index e5f3318f8..b30581336 100644 --- a/.travis.yml +++ b/.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 diff --git a/app/commander/commander.go b/app/commander/commander.go index 67a0b89b7..fef554953 100644 --- a/app/commander/commander.go +++ b/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() diff --git a/app/commander/outbound.go b/app/commander/outbound.go index 92be34a9a..25e476db1 100644 --- a/app/commander/outbound.go +++ b/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() } diff --git a/app/commander/service.go b/app/commander/service.go index fb9340db4..4e227c342 100644 --- a/app/commander/service.go +++ b/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) } diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index 6be84fcf3..3a9fb82d2 100644 --- a/app/dispatcher/default.go +++ b/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) diff --git a/app/dispatcher/sniffer.go b/app/dispatcher/sniffer.go index a02d81054..f81ba9a02 100644 --- a/app/dispatcher/sniffer.go +++ b/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: diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index a689e785c..1d7667124 100644 --- a/app/dns/nameserver.go +++ b/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 } diff --git a/app/dns/server.go b/app/dns/server.go index 2972ff5cf..5fbec9b91 100644 --- a/app/dns/server.go +++ b/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() } diff --git a/app/log/command/command.go b/app/log/command/command.go new file mode 100644 index 000000000..4c6d7236a --- /dev/null +++ b/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 + })) +} diff --git a/app/log/command/command_test.go b/app/log/command/command_test.go new file mode 100644 index 000000000..03debdec8 --- /dev/null +++ b/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) +} diff --git a/app/log/command/config.pb.go b/app/log/command/config.pb.go new file mode 100644 index 000000000..d0ee71eb5 --- /dev/null +++ b/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, +} diff --git a/app/log/command/config.proto b/app/log/command/config.proto new file mode 100644 index 000000000..39e2f33f3 --- /dev/null +++ b/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) {} +} \ No newline at end of file diff --git a/app/log/command/errors.generated.go b/app/log/command/errors.generated.go new file mode 100644 index 000000000..ac6c3bb2b --- /dev/null +++ b/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") +} diff --git a/app/log/log.go b/app/log/log.go index c2dc5e579..5471fb35f 100644 --- a/app/log/log.go +++ b/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,17 +24,15 @@ type Instance struct { func New(ctx context.Context, config *Config) (*Instance, error) { g := &Instance{ config: config, - active: true, - } - - 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() + active: false, } log.RegisterHandler(g) + v := core.FromContext(ctx) + if v != nil { + common.Must(v.RegisterFeature((*log.Handler)(nil), 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 + } - return g.active + newError("Logger started").AtDebug().WriteToLog() + + 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 } diff --git a/app/policy/config.go b/app/policy/config.go index 4d2f9452d..db5aaa9fa 100644 --- a/app/policy/config.go +++ b/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) { - 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 - } +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 { + 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 } diff --git a/app/policy/config.pb.go b/app/policy/config.pb.go index 98063a4b9..10e77c0e8 100644 --- a/app/policy/config.pb.go +++ b/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, } diff --git a/app/policy/config.proto b/app/policy/config.proto index b00aefc5c..e82bff3e3 100644 --- a/app/policy/config.proto +++ b/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 { diff --git a/app/policy/manager.go b/app/policy/manager.go index 14f7b7b9b..68ed4e9d7 100644 --- a/app/policy/manager.go +++ b/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 } diff --git a/app/policy/manager_test.go b/app/policy/manager_test.go new file mode 100644 index 000000000..dde4e2acc --- /dev/null +++ b/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) +} diff --git a/app/proxyman/command/command.go b/app/proxyman/command/command.go old mode 100644 new mode 100755 index c0357579d..e80f3d2c2 --- a/app/proxyman/command/command.go +++ b/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 })) } diff --git a/app/proxyman/command/command.pb.go b/app/proxyman/command/command.pb.go index fe0c1071d..0a3d92e92 100644 --- a/app/proxyman/command/command.pb.go +++ b/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" ) diff --git a/app/proxyman/inbound/dynamic.go b/app/proxyman/inbound/dynamic.go index e6f77972b..ca2cc9440 100644 --- a/app/proxyman/inbound/dynamic.go +++ b/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, diff --git a/app/proxyman/inbound/inbound.go b/app/proxyman/inbound/inbound.go index 9b019d136..b6d38d4ca 100644 --- a/app/proxyman/inbound/inbound.go +++ b/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 { diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go index 804092fec..e431c732d 100644 --- a/app/proxyman/inbound/worker.go +++ b/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 } diff --git a/app/proxyman/mux/frame.go b/app/proxyman/mux/frame.go index 462dd0da4..958aa740b 100644 --- a/app/proxyman/mux/frame.go +++ b/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 +func (f FrameMetadata) WriteTo(b *buf.Buffer) error { + lenBytes := b.Bytes() + b.AppendBytes(0x00, 0x00) - b = serial.Uint16ToBytes(f.SessionID, b) - b = append(b, byte(f.SessionStatus), byte(f.Option)) - length := 4 + len0 := b.Len() + if err := b.AppendSupplier(serial.WriteUint16(f.SessionID)); err != nil { + return err + } - 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.AppendBytes(byte(f.SessionStatus), byte(f.Option)) - 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 - } + 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:] + network := TargetNetwork(b.Byte(4)) + b.SliceFrom(5) - 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) + 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) diff --git a/app/proxyman/mux/mux.go b/app/proxyman/mux/mux.go index b921dbd69..1a35929cb 100644 --- a/app/proxyman/mux/mux.go +++ b/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 diff --git a/app/proxyman/mux/reader.go b/app/proxyman/mux/reader.go index 39e6d8ba7..4c5432972 100644 --- a/app/proxyman/mux/reader.go +++ b/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) diff --git a/app/proxyman/mux/writer.go b/app/proxyman/mux/writer.go index f8cbc21f2..ed0f938ff 100644 --- a/app/proxyman/mux/writer.go +++ b/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 diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 49d5da913..7fe3ed49a 100644 --- a/app/proxyman/outbound/handler.go +++ b/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 diff --git a/app/proxyman/outbound/outbound.go b/app/proxyman/outbound/outbound.go index 52848f324..91bc2c441 100644 --- a/app/proxyman/outbound/outbound.go +++ b/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) } diff --git a/app/proxyman/proxyman.go b/app/proxyman/proxyman.go index 1ae393aa3..361eb4c71 100644 --- a/app/proxyman/proxyman.go +++ b/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 } diff --git a/app/router/router.go b/app/router/router.go index 5e0109d5b..fb30b419c 100644 --- a/app/router/router.go +++ b/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 } diff --git a/app/stats/command/command.go b/app/stats/command/command.go new file mode 100644 index 000000000..5ff3e40eb --- /dev/null +++ b/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 + })) +} diff --git a/app/stats/command/command.pb.go b/app/stats/command/command.pb.go new file mode 100644 index 000000000..0d148746e --- /dev/null +++ b/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, +} diff --git a/app/stats/command/command.proto b/app/stats/command/command.proto new file mode 100644 index 000000000..fbd57a42c --- /dev/null +++ b/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 {} diff --git a/app/stats/command/errors.generated.go b/app/stats/command/errors.generated.go new file mode 100644 index 000000000..a5030ca0d --- /dev/null +++ b/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") +} diff --git a/app/stats/config.go b/app/stats/config.go new file mode 100644 index 000000000..4e3490034 --- /dev/null +++ b/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)) + })) +} diff --git a/app/stats/config.pb.go b/app/stats/config.pb.go new file mode 100644 index 000000000..a4b3d244a --- /dev/null +++ b/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, +} diff --git a/app/stats/config.proto b/app/stats/config.proto new file mode 100644 index 000000000..9407fd391 --- /dev/null +++ b/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 { + +} diff --git a/app/stats/errors.generated.go b/app/stats/errors.generated.go new file mode 100644 index 000000000..1bfbe328a --- /dev/null +++ b/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") } diff --git a/app/stats/stats.go b/app/stats/stats.go new file mode 100644 index 000000000..03ee2982c --- /dev/null +++ b/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 +} diff --git a/app/stats/stats_test.go b/app/stats/stats_test.go new file mode 100644 index 000000000..51a145acf --- /dev/null +++ b/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)) +} diff --git a/clock.go b/clock.go deleted file mode 100644 index 54bcb905b..000000000 --- a/clock.go +++ /dev/null @@ -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 -} diff --git a/commander.go b/commander.go deleted file mode 100644 index eb2d3750d..000000000 --- a/commander.go +++ /dev/null @@ -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 -} diff --git a/common/buf/buf.go b/common/buf/buf.go index 949ec6456..584815f4a 100644 --- a/common/buf/buf.go +++ b/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 diff --git a/common/buf/buffer.go b/common/buf/buffer.go index 2d6705e6b..e7583fccb 100644 --- a/common/buf/buffer.go +++ b/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() -} - -// NewLocal creates and returns a buffer with 0 length and given capacity on current thread. -func NewLocal(size int) *Buffer { return &Buffer{ - v: make([]byte, size), - pool: nil, + v: pool[0].Get().([]byte), + } +} + +// 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: newBytes(capacity), } } diff --git a/common/buf/buffer_pool.go b/common/buf/buffer_pool.go index d66047305..e73881332 100644 --- a/common/buf/buffer_pool.go +++ b/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) -} - -// SyncPool is a buffer pool based on sync.Pool -type SyncPool struct { - allocator *sync.Pool -} - -// 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) }, - }, - } - return pool -} - -// Allocate implements Pool.Allocate(). -func (p *SyncPool) Allocate() *Buffer { - return &Buffer{ - v: p.allocator.Get().([]byte), - pool: p, - } -} - -// Free implements Pool.Free(). -func (p *SyncPool) Free(buffer *Buffer) { - if buffer.v != nil { - p.allocator.Put(buffer.v) - } -} - const ( // Size of a regular buffer. Size = 2 * 1024 ) -var ( - mediumPool Pool = NewSyncPool(Size) +func createAllocFunc(size int32) func() interface{} { + return func() interface{} { + return make([]byte, size) + } +} + +// 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 + } +} + +func newBytes(size int32) []byte { + for idx, ps := range poolSize { + if size <= ps { + return pool[idx].Get().([]byte) + } + } + return make([]byte, size) +} + +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 + } + } +} diff --git a/common/buf/buffer_test.go b/common/buf/buffer_test.go index af05057ec..e00ba28f2 100644 --- a/common/buf/buffer_test.go +++ b/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() } } diff --git a/common/buf/copy.go b/common/buf/copy.go index d4f65235d..7d487c174 100644 --- a/common/buf/copy.go +++ b/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 } } diff --git a/common/buf/io.go b/common/buf/io.go index 17debb2b5..c3b9538ba 100644 --- a/common/buf/io.go +++ b/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, diff --git a/common/buf/multi_buffer.go b/common/buf/multi_buffer.go index 85830fc7b..b4e6d4cf7 100644 --- a/common/buf/multi_buffer.go +++ b/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) } diff --git a/common/buf/multi_buffer_test.go b/common/buf/multi_buffer_test.go index 92df32f02..656d0656e 100644 --- a/common/buf/multi_buffer_test.go +++ b/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)) } diff --git a/common/buf/reader.go b/common/buf/reader.go index 574bd5f61..d652e86a8 100644 --- a/common/buf/reader.go +++ b/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) + 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 + } } - if !b.IsEmpty() { - return NewMultiBufferValue(b), nil - } - 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 + + 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() } -var ( - _ Reader = (*BufferedReader)(nil) - _ io.Reader = (*BufferedReader)(nil) - _ io.ByteReader = (*BufferedReader)(nil) - _ io.WriterTo = (*BufferedReader)(nil) -) - +// 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 { diff --git a/common/buf/reader_test.go b/common/buf/reader_test.go index 9c90cfe05..6086a7301 100644 --- a/common/buf/reader_test.go +++ b/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) { diff --git a/common/buf/writer.go b/common/buf/writer.go index 85771cff5..05798cf8b 100644 --- a/common/buf/writer.go +++ b/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 { diff --git a/common/buf/writer_test.go b/common/buf/writer_test.go index 1dbe2f224..b90f84c9c 100644 --- a/common/buf/writer_test.go +++ b/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) { diff --git a/common/common.go b/common/common.go index b26ba7704..5ab6a7704 100644 --- a/common/common.go +++ b/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 } diff --git a/common/crypto/auth.go b/common/crypto/auth.go index 2d591bfbd..2cf0eb15e 100644 --- a/common/crypto/auth.go +++ b/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) diff --git a/common/crypto/auth_test.go b/common/crypto/auth_test.go index 3bc35fa3f..702ddbb33 100644 --- a/common/crypto/auth_test.go +++ b/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() diff --git a/common/crypto/chunk.go b/common/crypto/chunk.go old mode 100644 new mode 100755 index e54119984..e6ea63d11 --- a/common/crypto/chunk.go +++ b/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) diff --git a/common/crypto/chunk_test.go b/common/crypto/chunk_test.go index 4131ebfb2..f1ef5d285 100644 --- a/common/crypto/chunk_test.go +++ b/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() diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go index 8483b9540..45797a8ad 100644 --- a/common/crypto/crypto.go +++ b/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 diff --git a/common/dice/dice.go b/common/dice/dice.go index 014f4c0de..ac03fe9e5 100644 --- a/common/dice/dice.go +++ b/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" diff --git a/common/dice/dice_test.go b/common/dice/dice_test.go new file mode 100644 index 000000000..09b1dd4d3 --- /dev/null +++ b/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) + } +} diff --git a/common/errors/errors.go b/common/errors/errors.go index 3d9996ab1..3e5e09425 100644 --- a/common/errors/errors.go +++ b/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) +} diff --git a/common/interfaces.go b/common/interfaces.go index d5cc177e6..962141784 100644 --- a/common/interfaces.go +++ b/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 +} diff --git a/common/log/log.go b/common/log/log.go index c7cdfadf8..423901b73 100644 --- a/common/log/log.go +++ b/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. diff --git a/common/log/log_test.go b/common/log/log_test.go index f4ee096c8..3555e5cc5 100644 --- a/common/log/log_test.go +++ b/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) } diff --git a/common/log/logger.go b/common/log/logger.go index a07100bc9..b3008720b 100644 --- a/common/log/logger.go +++ b/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 } diff --git a/common/log/logger_test.go b/common/log/logger_test.go new file mode 100644 index 000000000..03018e2c0 --- /dev/null +++ b/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()) +} diff --git a/common/net/address.go b/common/net/address.go index 6ab0587f4..04d40f0c7 100644 --- a/common/net/address.go +++ b/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 diff --git a/common/net/destination.go b/common/net/destination.go index f7f5b720b..8cbfbccd1 100644 --- a/common/net/destination.go +++ b/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, diff --git a/common/net/net.go b/common/net/net.go index 9cddfe530..b44beedbf 100644 --- a/common/net/net.go +++ b/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 diff --git a/common/net/network.go b/common/net/network.go index f232de0ef..33d53bc72 100644 --- a/common/net/network.go +++ b/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 { diff --git a/common/platform/platform.go b/common/platform/platform.go index 16d146045..3b86ec9dc 100644 --- a/common/platform/platform.go +++ b/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 } diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go index 326ceba1f..fbeb703e4 100644 --- a/common/predicate/predicate.go +++ b/common/predicate/predicate.go @@ -1,4 +1,4 @@ -package predicate +package predicate // import "v2ray.com/core/common/predicate" type Predicate func() bool diff --git a/common/protocol/account.go b/common/protocol/account.go old mode 100644 new mode 100755 index 1c6a1eeaa..7793974a7 --- a/common/protocol/account.go +++ b/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 } diff --git a/common/protocol/address.go b/common/protocol/address.go new file mode 100644 index 000000000..83437e0ca --- /dev/null +++ b/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 +} diff --git a/common/protocol/address_test.go b/common/protocol/address_test.go new file mode 100644 index 000000000..d66d119e5 --- /dev/null +++ b/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) + } + } +} diff --git a/common/protocol/context.go b/common/protocol/context.go old mode 100644 new mode 100755 index 6a2d24090..17437719a --- a/common/protocol/context.go +++ b/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 { diff --git a/common/protocol/headers.go b/common/protocol/headers.go index b8fc58853..5a3a16267 100644 --- a/common/protocol/headers.go +++ b/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 } diff --git a/common/protocol/id.go b/common/protocol/id.go old mode 100644 new mode 100755 index e6b0176ee..6deb0ccec --- a/common/protocol/id.go +++ b/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 diff --git a/common/protocol/payload.go b/common/protocol/payload.go index 03eced677..2f3c72904 100644 --- a/common/protocol/payload.go +++ b/common/protocol/payload.go @@ -1,6 +1,6 @@ package protocol -type TransferType int +type TransferType byte const ( TransferTypeStream TransferType = 0 diff --git a/common/protocol/protocol.go b/common/protocol/protocol.go index 6c89418d4..edf4e5ca9 100644 --- a/common/protocol/protocol.go +++ b/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 diff --git a/common/protocol/server_spec.go b/common/protocol/server_spec.go index 107dbb1c7..3f35d8f39 100644 --- a/common/protocol/server_spec.go +++ b/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() } diff --git a/common/retry/retry.go b/common/retry/retry.go index c2f94ce3f..b2c785997 100644 --- a/common/retry/retry.go +++ b/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 diff --git a/common/serial/bytes.go b/common/serial/bytes.go old mode 100644 new mode 100755 index 14c7676cc..69475b19a --- a/common/serial/bytes.go +++ b/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 | diff --git a/common/serial/numbers.go b/common/serial/numbers.go old mode 100644 new mode 100755 index 556fcb345..3b15c38ff --- a/common/serial/numbers.go +++ b/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)) } diff --git a/common/serial/string_test.go b/common/serial/string_test.go new file mode 100644 index 000000000..47d044683 --- /dev/null +++ b/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) + } +} diff --git a/common/session/session.go b/common/session/session.go new file mode 100644 index 000000000..e1be95766 --- /dev/null +++ b/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 +} diff --git a/common/signal/done.go b/common/signal/done.go old mode 100644 new mode 100755 index ab3655137..351f8c630 --- a/common/signal/done.go +++ b/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() diff --git a/common/signal/exec.go b/common/signal/exec.go index 3a3400f9b..0547a607d 100644 --- a/common/signal/exec.go +++ b/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) diff --git a/common/signal/notifier.go b/common/signal/notifier.go old mode 100644 new mode 100755 index 702bab065..19836e54f --- a/common/signal/notifier.go +++ b/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{} } diff --git a/common/signal/task.go b/common/signal/task.go index a9a886fb9..36e0a1f4e 100644 --- a/common/signal/task.go +++ b/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() diff --git a/common/uuid/uuid.go b/common/uuid/uuid.go old mode 100644 new mode 100755 index 0e0ca3869..1f3a8342f --- a/common/uuid/uuid.go +++ b/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 diff --git a/config.go b/config.go new file mode 100755 index 000000000..d03f0673e --- /dev/null +++ b/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, + })) +} diff --git a/config.pb.go b/config.pb.go index 1429f6e22..beee21273 100644 --- a/config.pb.go +++ b/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, } diff --git a/config.proto b/config.proto index 6101c0fa4..26d660e3c 100644 --- a/config.proto +++ b/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; diff --git a/context.go b/context.go index c92f0bacc..2651f3fe2 100644 --- a/context.go +++ b/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 +} diff --git a/core.go b/core.go old mode 100644 new mode 100755 index a3696c660..5ad599ba1 --- a/core.go +++ b/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, + } } /* diff --git a/dns.go b/dns.go index 5e94d1c2a..5546f1156 100644 --- a/dns.go +++ b/dns.go @@ -55,5 +55,6 @@ func (d *syncDNSClient) Set(client DNSClient) { d.Lock() defer d.Unlock() + common.Close(d.DNSClient) d.DNSClient = client } diff --git a/loader.go b/loader.go deleted file mode 100644 index 1dab37dd2..000000000 --- a/loader.go +++ /dev/null @@ -1,46 +0,0 @@ -package core - -import ( - "io" - - "v2ray.com/core/common" - "v2ray.com/core/common/buf" - - "github.com/golang/protobuf/proto" -) - -// ConfigLoader is an utility to load V2Ray config from external source. -type ConfigLoader func(input io.Reader) (*Config, error) - -var configLoaderCache = make(map[ConfigFormat]ConfigLoader) - -// RegisterConfigLoader add a new ConfigLoader. -func RegisterConfigLoader(format ConfigFormat, loader ConfigLoader) error { - configLoaderCache[format] = loader - return nil -} - -// LoadConfig loads config with given format from given source. -func LoadConfig(format ConfigFormat, input io.Reader) (*Config, error) { - loader, found := configLoaderCache[format] - if !found { - return nil, newError(ConfigFormat_name[int32(format)], " is not loadable.").AtWarning() - } - return loader(input) -} - -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_Protobuf, loadProtobufConfig)) -} diff --git a/main/config_json.go b/main/config_json.go deleted file mode 100644 index 5e294dddc..000000000 --- a/main/config_json.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "io" - "os" - "os/exec" - - "v2ray.com/core" - "v2ray.com/core/common/platform" -) - -func jsonToProto(input io.Reader) (*core.Config, error) { - v2ctl := platform.GetToolLocation("v2ctl") - _, err := os.Stat(v2ctl) - if err != nil { - return nil, err - } - cmd := exec.Command(v2ctl, "config") - cmd.Stdin = input - cmd.Stderr = os.Stderr - cmd.SysProcAttr = getSysProcAttr() - - stdoutReader, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - defer stdoutReader.Close() - - if err := cmd.Start(); err != nil { - return nil, err - } - - config, err := core.LoadConfig(core.ConfigFormat_Protobuf, stdoutReader) - - cmd.Wait() - - return config, err -} - -func init() { - core.RegisterConfigLoader(core.ConfigFormat_JSON, func(input io.Reader) (*core.Config, error) { - config, err := jsonToProto(input) - if err != nil { - return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning() - } - return config, nil - }) -} diff --git a/main/distro/all/all.go b/main/distro/all/all.go index c66e22acb..989d7c2e0 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -8,15 +8,18 @@ import ( _ "v2ray.com/core/app/proxyman/inbound" _ "v2ray.com/core/app/proxyman/outbound" - // Default commander and all its services. + // Default commander and all its services. This is an optional feature. _ "v2ray.com/core/app/commander" + _ "v2ray.com/core/app/log/command" _ "v2ray.com/core/app/proxyman/command" + _ "v2ray.com/core/app/stats/command" // Other optional features. _ "v2ray.com/core/app/dns" _ "v2ray.com/core/app/log" _ "v2ray.com/core/app/policy" _ "v2ray.com/core/app/router" + _ "v2ray.com/core/app/stats" // Inbound and outbound proxies. _ "v2ray.com/core/proxy/blackhole" @@ -29,6 +32,7 @@ import ( _ "v2ray.com/core/proxy/vmess/outbound" // Transports + _ "v2ray.com/core/transport/internet/http" _ "v2ray.com/core/transport/internet/kcp" _ "v2ray.com/core/transport/internet/tcp" _ "v2ray.com/core/transport/internet/tls" @@ -41,4 +45,7 @@ import ( _ "v2ray.com/core/transport/internet/headers/srtp" _ "v2ray.com/core/transport/internet/headers/utp" _ "v2ray.com/core/transport/internet/headers/wechat" + + // JSON config format + _ "v2ray.com/core/main/json" ) diff --git a/main/json/config_json.go b/main/json/config_json.go new file mode 100644 index 000000000..a98e1ca7c --- /dev/null +++ b/main/json/config_json.go @@ -0,0 +1,81 @@ +package json + +//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg json -path Main,Json + +import ( + "context" + "io" + "os" + "os/exec" + + "v2ray.com/core" + "v2ray.com/core/common" + "v2ray.com/core/common/platform" + "v2ray.com/core/common/signal" +) + +type logWriter struct{} + +func (*logWriter) Write(b []byte) (int, error) { + n, err := os.Stderr.Write(b) + if err == nil { + os.Stderr.WriteString(platform.LineSeparator()) + } + return n, err +} + +func jsonToProto(input io.Reader) (*core.Config, error) { + v2ctl := platform.GetToolLocation("v2ctl") + if _, err := os.Stat(v2ctl); err != nil { + return nil, err + } + cmd := exec.Command(v2ctl, "config") + cmd.Stdin = input + cmd.Stderr = &logWriter{} + cmd.SysProcAttr = getSysProcAttr() + + stdoutReader, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + defer stdoutReader.Close() + + if err := cmd.Start(); err != nil { + return nil, err + } + + var config *core.Config + + loadTask := signal.ExecuteAsync(func() error { + c, err := core.LoadConfig("protobuf", "", stdoutReader) + if err != nil { + return err + } + config = c + return nil + }) + + waitTask := signal.ExecuteAsync(func() error { + return cmd.Wait() + }) + + if err := signal.ErrorOrFinish2(context.Background(), loadTask, waitTask); err != nil { + return nil, err + } + + return config, nil +} + +func init() { + common.Must(core.RegisterConfigLoader(&core.ConfigFormat{ + Name: "JSON", + Extension: []string{"json"}, + Loader: func(input io.Reader) (*core.Config, error) { + config, err := jsonToProto(input) + if err != nil { + return nil, newError("failed to execute v2ctl to convert config file.").Base(err).AtWarning() + } + return config, nil + }, + })) +} diff --git a/main/config_json_other.go b/main/json/config_json_other.go similarity index 88% rename from main/config_json_other.go rename to main/json/config_json_other.go index b1502710e..2c52fd109 100644 --- a/main/config_json_other.go +++ b/main/json/config_json_other.go @@ -1,6 +1,6 @@ // +build !windows -package main +package json import "syscall" diff --git a/main/config_json_windows.go b/main/json/config_json_windows.go similarity index 91% rename from main/config_json_windows.go rename to main/json/config_json_windows.go index 01c2b53ba..78f63c795 100644 --- a/main/config_json_windows.go +++ b/main/json/config_json_windows.go @@ -1,6 +1,6 @@ // +build windows -package main +package json import "syscall" diff --git a/proxy/errors.generated.go b/main/json/errors.generated.go similarity index 65% rename from proxy/errors.generated.go rename to main/json/errors.generated.go index f144f5d19..faf694b90 100644 --- a/proxy/errors.generated.go +++ b/main/json/errors.generated.go @@ -1,5 +1,5 @@ -package proxy +package json import "v2ray.com/core/common/errors" -func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("Proxy") } +func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("Main", "Json") } diff --git a/main/main.go b/main/main.go index b3f533714..e3b323da9 100644 --- a/main/main.go +++ b/main/main.go @@ -49,14 +49,12 @@ func getConfigFilePath() string { return "" } -func GetConfigFormat() core.ConfigFormat { +func GetConfigFormat() string { switch strings.ToLower(*format) { - case "json": - return core.ConfigFormat_JSON case "pb", "protobuf": - return core.ConfigFormat_Protobuf + return "protobuf" default: - return core.ConfigFormat_JSON + return "json" } } @@ -74,7 +72,7 @@ func startV2Ray() (core.Server, error) { defer file.Close() configInput = file } - config, err := core.LoadConfig(GetConfigFormat(), configInput) + config, err := core.LoadConfig(GetConfigFormat(), configFile, configInput) if err != nil { return nil, newError("failed to read config file: ", configFile).Base(err) } @@ -87,10 +85,17 @@ func startV2Ray() (core.Server, error) { return server, nil } +func printVersion() { + version := core.VersionStatement() + for _, s := range version { + fmt.Println(s) + } +} + func main() { flag.Parse() - core.PrintVersion() + printVersion() if *version { return diff --git a/network.go b/network.go index d526a91f1..e6a0046cb 100644 --- a/network.go +++ b/network.go @@ -26,7 +26,7 @@ type OutboundHandler interface { Dispatch(ctx context.Context, outboundRay ray.OutboundRay) } -// InboundHandlerManager is a feature that managers InboundHandlers. +// InboundHandlerManager is a feature that manages InboundHandlers. type InboundHandlerManager interface { Feature // GetHandlers returns an InboundHandler for the given tag. @@ -84,16 +84,21 @@ func (m *syncInboundHandlerManager) Close() error { } func (m *syncInboundHandlerManager) Set(manager InboundHandlerManager) { + if manager == nil { + return + } + m.Lock() defer m.Unlock() + common.Close(m.InboundHandlerManager) m.InboundHandlerManager = manager } // OutboundHandlerManager is a feature that manages OutboundHandlers. type OutboundHandlerManager interface { Feature - // GetHandler returns an OutboundHandler will given tag. + // GetHandler returns an OutboundHandler for the given tag. GetHandler(tag string) OutboundHandler // GetDefaultHandler returns the default OutboundHandler. It is usually the first OutboundHandler specified in the configuration. GetDefaultHandler() OutboundHandler @@ -161,8 +166,13 @@ func (m *syncOutboundHandlerManager) Close() error { } func (m *syncOutboundHandlerManager) Set(manager OutboundHandlerManager) { + if manager == nil { + return + } + m.Lock() defer m.Unlock() + common.Close(m.OutboundHandlerManager) m.OutboundHandlerManager = manager } diff --git a/policy.go b/policy.go index a07173206..f8dab11cd 100644 --- a/policy.go +++ b/policy.go @@ -13,38 +13,24 @@ type TimeoutPolicy struct { Handshake time.Duration // Timeout for connection being idle, i.e., there is no egress or ingress traffic in this connection. ConnectionIdle time.Duration - // Timeout for an uplink only connection, i.e., the downlink of the connection has ben closed. + // Timeout for an uplink only connection, i.e., the downlink of the connection has been closed. UplinkOnly time.Duration - // Timeout for an downlink only connection, i.e., the uplink of the connection has ben closed. + // Timeout for an downlink only connection, i.e., the uplink of the connection has been closed. DownlinkOnly time.Duration } -// OverrideWith overrides the current TimeoutPolicy with another one. All timeouts with zero value will be overridden with the new value. -func (p TimeoutPolicy) OverrideWith(another TimeoutPolicy) TimeoutPolicy { - if p.Handshake == 0 { - p.Handshake = another.Handshake - } - if p.ConnectionIdle == 0 { - p.ConnectionIdle = another.ConnectionIdle - } - if p.UplinkOnly == 0 { - p.UplinkOnly = another.UplinkOnly - } - if p.DownlinkOnly == 0 { - p.DownlinkOnly = another.DownlinkOnly - } - return p +// StatsPolicy contains settings for stats counters. +type StatsPolicy struct { + // Whether or not to enable stat counter for user uplink traffic. + UserUplink bool + // Whether or not to enable stat counter for user downlink traffic. + UserDownlink bool } // Policy is session based settings for controlling V2Ray requests. It contains various settings (or limits) that may differ for different users in the context. type Policy struct { Timeouts TimeoutPolicy // Timeout settings -} - -// OverrideWith overrides the current Policy with another one. All values with default value will be overridden. -func (p Policy) OverrideWith(another Policy) Policy { - p.Timeouts.OverrideWith(another.Timeouts) - return p + Stats StatsPolicy } // PolicyManager is a feature that provides Policy for the given user by its id or level. @@ -64,6 +50,10 @@ func DefaultPolicy() Policy { UplinkOnly: time.Second * 5, DownlinkOnly: time.Second * 30, }, + Stats: StatsPolicy{ + UserUplink: false, + UserDownlink: false, + }, } } @@ -113,5 +103,6 @@ func (m *syncPolicyManager) Set(manager PolicyManager) { m.Lock() defer m.Unlock() + common.Close(m.PolicyManager) m.PolicyManager = manager } diff --git a/proxy/blackhole/config.go b/proxy/blackhole/config.go index 5bfd3290e..89c8df438 100644 --- a/proxy/blackhole/config.go +++ b/proxy/blackhole/config.go @@ -27,7 +27,7 @@ func (*NoneResponse) WriteTo(buf.Writer) {} // WriteTo implements ResponseConfig.WriteTo(). func (*HTTPResponse) WriteTo(writer buf.Writer) { - b := buf.NewLocal(512) + b := buf.New() common.Must(b.AppendSupplier(serial.WriteString(http403response))) writer.WriteMultiBuffer(buf.NewMultiBufferValue(b)) } diff --git a/proxy/context.go b/proxy/context.go index c3a1378c1..7bc551255 100644 --- a/proxy/context.go +++ b/proxy/context.go @@ -22,7 +22,7 @@ func ContextWithSource(ctx context.Context, src net.Destination) context.Context return context.WithValue(ctx, sourceKey, src) } -// SourceFromContext retreives source from the given context. +// SourceFromContext retrieves source from the given context. func SourceFromContext(ctx context.Context) (net.Destination, bool) { v, ok := ctx.Value(sourceKey).(net.Destination) return v, ok diff --git a/proxy/dokodemo/dokodemo.go b/proxy/dokodemo/dokodemo.go index 80e952390..7626911dc 100644 --- a/proxy/dokodemo/dokodemo.go +++ b/proxy/dokodemo/dokodemo.go @@ -21,18 +21,13 @@ type DokodemoDoor struct { config *Config address net.Address port net.Port - v *core.Instance } func New(ctx context.Context, config *Config) (*DokodemoDoor, error) { if config.NetworkList == nil || config.NetworkList.Size() == 0 { return nil, newError("no network specified") } - v := core.FromContext(ctx) - if v == nil { - return nil, newError("V is not in context.") - } - + v := core.MustFromContext(ctx) d := &DokodemoDoor{ config: config, address: config.GetPredefinedAddress(), @@ -57,7 +52,7 @@ func (d *DokodemoDoor) policy() core.Policy { } func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error { - newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WriteToLog() + newError("processing connection from: ", conn.RemoteAddr()).AtDebug().WithContext(ctx).WriteToLog() dest := net.Destination{ Network: network, Address: d.address, @@ -82,6 +77,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in requestDone := signal.ExecuteAsync(func() error { defer inboundRay.InboundInput().Close() + defer timer.SetTimeout(d.policy().Timeouts.DownlinkOnly) chunkReader := buf.NewReader(conn) @@ -89,12 +85,12 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in return newError("failed to transport request").Base(err) } - timer.SetTimeout(d.policy().Timeouts.DownlinkOnly) - return nil }) responseDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(d.policy().Timeouts.UplinkOnly) + var writer buf.Writer if network == net.Network_TCP { writer = buf.NewWriter(conn) @@ -116,8 +112,6 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn in return newError("failed to transport response").Base(err) } - timer.SetTimeout(d.policy().Timeouts.UplinkOnly) - return nil }) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index ef87340f4..7868c3c96 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -27,11 +27,7 @@ type Handler struct { // New creates a new Freedom handler. func New(ctx context.Context, config *Config) (*Handler, error) { - v := core.FromContext(ctx) - if v == nil { - return nil, newError("V is not found in context.") - } - + v := core.MustFromContext(ctx) f := &Handler{ config: *config, policyManager: v.PolicyManager(), @@ -60,7 +56,7 @@ func (h *Handler) resolveIP(ctx context.Context, domain string) net.Address { ips, err := h.dns.LookupIP(domain) if err != nil { - newError("failed to get IP address for domain ", domain).Base(err).WriteToLog() + newError("failed to get IP address for domain ", domain).Base(err).WithContext(ctx).WriteToLog() } if len(ips) == 0 { return nil @@ -79,7 +75,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial Port: net.Port(server.Port), } } - newError("opening connection to ", destination).WriteToLog() + newError("opening connection to ", destination).WithContext(ctx).WriteToLog() input := outboundRay.OutboundInput() output := outboundRay.OutboundOutput() @@ -92,7 +88,7 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial Address: ip, Port: destination.Port, } - newError("changing destination to ", destination).WriteToLog() + newError("changing destination to ", destination).WithContext(ctx).WriteToLog() } } @@ -114,6 +110,8 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial timer := signal.CancelAfterInactivity(ctx, cancel, h.policy().Timeouts.ConnectionIdle) requestDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(h.policy().Timeouts.DownlinkOnly) + var writer buf.Writer if destination.Network == net.Network_TCP { writer = buf.NewWriter(conn) @@ -123,18 +121,18 @@ func (h *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to process request").Base(err) } - timer.SetTimeout(h.policy().Timeouts.DownlinkOnly) + return nil }) responseDone := signal.ExecuteAsync(func() error { - defer output.Close() + defer timer.SetTimeout(h.policy().Timeouts.UplinkOnly) v2reader := buf.NewReader(conn) if err := buf.Copy(v2reader, output, buf.UpdateActivity(timer)); err != nil { return newError("failed to process response").Base(err) } - timer.SetTimeout(h.policy().Timeouts.UplinkOnly) + return nil }) diff --git a/proxy/http/server.go b/proxy/http/server.go old mode 100644 new mode 100755 index 9179de132..ffc7a1bc5 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -21,7 +21,7 @@ import ( "v2ray.com/core/transport/internet" ) -// Server is a HTTP proxy server. +// Server is an HTTP proxy server. type Server struct { config *ServerConfig v *core.Instance @@ -31,10 +31,7 @@ type Server struct { func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { s := &Server{ config: config, - v: core.FromContext(ctx), - } - if s.v == nil { - return nil, newError("V is not in context.") + v: core.MustFromContext(ctx), } return s, nil @@ -124,7 +121,7 @@ Start: } } - newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]").WriteToLog() + newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]").WithContext(ctx).WriteToLog() conn.SetReadDeadline(time.Time{}) defaultPort := net.Port(80) @@ -176,11 +173,11 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade } if reader.Buffered() > 0 { - payload := buf.New() - common.Must(payload.Reset(func(b []byte) (int, error) { - return reader.Read(b[:reader.Buffered()]) - })) - if err := ray.InboundInput().WriteMultiBuffer(buf.NewMultiBufferValue(payload)); err != nil { + payload, err := buf.ReadSizeToMultiBuffer(reader, int32(reader.Buffered())) + if err != nil { + return err + } + if err := ray.InboundInput().WriteMultiBuffer(payload); err != nil { return err } reader = nil @@ -195,11 +192,13 @@ func (s *Server) handleConnect(ctx context.Context, request *http.Request, reade }) responseDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(s.policy().Timeouts.UplinkOnly) + v2writer := buf.NewWriter(conn) if err := buf.Copy(ray.InboundOutput(), v2writer, buf.UpdateActivity(timer)); err != nil { return err } - timer.SetTimeout(s.policy().Timeouts.UplinkOnly) + return nil }) @@ -279,7 +278,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri result = nil } } else { - newError("failed to read response from ", request.Host).Base(err).AtWarning().WriteToLog() + newError("failed to read response from ", request.Host).Base(err).AtWarning().WithContext(ctx).WriteToLog() response = &http.Response{ Status: "Service Unavailable", StatusCode: 503, diff --git a/proxy/proxy.go b/proxy/proxy.go old mode 100644 new mode 100755 index 7174c2453..a0d0ac422 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,8 +5,6 @@ // 2. Register a config creator through common.RegisterConfig. package proxy -//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg proxy -path Proxy - import ( "context" @@ -43,7 +41,7 @@ type UserManager interface { // AddUser adds a new user. AddUser(context.Context, *protocol.User) error - // RemoveUser removes an user by email. + // RemoveUser removes a user by email. RemoveUser(context.Context, string) error } diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 3ce880c68..8cb20d6d8 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -32,17 +32,13 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { } client := &Client{ serverPicker: protocol.NewRoundRobinServerPicker(serverList), - v: core.FromContext(ctx), + v: core.MustFromContext(ctx), } - if client.v == nil { - return nil, newError("V is not in context.") - } - return client, nil } // Process implements OutboundHandler.Process(). -func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, dialer proxy.Dialer) error { +func (c *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, dialer proxy.Dialer) error { destination, ok := proxy.TargetFromContext(ctx) if !ok { return newError("target not specified") @@ -53,7 +49,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale var conn internet.Connection err := retry.ExponentialBackoff(5, 100).On(func() error { - server = v.serverPicker.PickServer() + server = c.serverPicker.PickServer() dest := server.Destination() dest.Network = network rawConn, err := dialer.Dial(ctx, dest) @@ -67,7 +63,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale if err != nil { return newError("failed to find an available destination").AtWarning().Base(err) } - newError("tunneling request to ", destination, " via ", server.Destination()).WriteToLog() + newError("tunneling request to ", destination, " via ", server.Destination()).WithContext(ctx).WriteToLog() defer conn.Close() @@ -94,7 +90,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale request.Option |= RequestOptionOneTimeAuth } - sessionPolicy := v.v.PolicyManager().ForLevel(user.Level) + sessionPolicy := c.v.PolicyManager().ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) @@ -115,7 +111,6 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale }) responseDone := signal.ExecuteAsync(func() error { - defer outboundRay.OutboundOutput().Close() defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) responseReader, err := ReadTCPResponse(user, conn) @@ -141,6 +136,8 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale }) requestDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + if err := buf.Copy(outboundRay.OutboundInput(), writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all UDP request").Base(err) } @@ -148,14 +145,14 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale }) responseDone := signal.ExecuteAsync(func() error { - defer outboundRay.OutboundOutput().Close() + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) reader := &UDPReader{ Reader: conn, User: user, } - if err := buf.Copy(reader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer), buf.IgnoreReaderError()); err != nil { + if err := buf.Copy(reader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all UDP response").Base(err) } return nil diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index 4d67b02bf..3c30a1c09 100644 --- a/proxy/shadowsocks/config.go +++ b/proxy/shadowsocks/config.go @@ -96,8 +96,8 @@ func (a *Account) AsAccount() (protocol.Account, error) { // Cipher is an interface for all Shadowsocks ciphers. type Cipher interface { - KeySize() int - IVSize() int + KeySize() int32 + IVSize() int32 NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) IsAEAD() bool @@ -107,18 +107,18 @@ type Cipher interface { // AesCfb represents all AES-CFB ciphers. type AesCfb struct { - KeyBytes int + KeyBytes int32 } func (*AesCfb) IsAEAD() bool { return false } -func (v *AesCfb) KeySize() int { +func (v *AesCfb) KeySize() int32 { return v.KeyBytes } -func (v *AesCfb) IVSize() int { +func (v *AesCfb) IVSize() int32 { return 16 } @@ -151,8 +151,8 @@ func (v *AesCfb) DecodePacket(key []byte, b *buf.Buffer) error { } type AEADCipher struct { - KeyBytes int - IVBytes int + KeyBytes int32 + IVBytes int32 AEADAuthCreator func(key []byte) cipher.AEAD } @@ -160,11 +160,11 @@ func (*AEADCipher) IsAEAD() bool { return true } -func (c *AEADCipher) KeySize() int { +func (c *AEADCipher) KeySize() int32 { return c.KeyBytes } -func (c *AEADCipher) IVSize() int { +func (c *AEADCipher) IVSize() int32 { return c.IVBytes } @@ -226,18 +226,18 @@ func (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error { } type ChaCha20 struct { - IVBytes int + IVBytes int32 } func (*ChaCha20) IsAEAD() bool { return false } -func (v *ChaCha20) KeySize() int { +func (v *ChaCha20) KeySize() int32 { return 32 } -func (v *ChaCha20) IVSize() int { +func (v *ChaCha20) IVSize() int32 { return v.IVBytes } @@ -271,8 +271,8 @@ func (v *ChaCha20) DecodePacket(key []byte, b *buf.Buffer) error { type NoneCipher struct{} -func (NoneCipher) KeySize() int { return 0 } -func (NoneCipher) IVSize() int { return 0 } +func (NoneCipher) KeySize() int32 { return 0 } +func (NoneCipher) IVSize() int32 { return 0 } func (NoneCipher) IsAEAD() bool { return true // to avoid OTA } @@ -293,13 +293,13 @@ func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error { return nil } -func passwordToCipherKey(password []byte, keySize int) []byte { +func passwordToCipherKey(password []byte, keySize int32) []byte { key := make([]byte, 0, keySize) md5Sum := md5.Sum(password) key = append(key, md5Sum[:]...) - for len(key) < keySize { + for int32(len(key)) < keySize { md5Hash := md5.New() common.Must2(md5Hash.Write(md5Sum[:])) common.Must2(md5Hash.Write(password)) diff --git a/proxy/shadowsocks/config.pb.go b/proxy/shadowsocks/config.pb.go index bd7cd3b64..c345cf222 100644 --- a/proxy/shadowsocks/config.pb.go +++ b/proxy/shadowsocks/config.pb.go @@ -3,6 +3,7 @@ package shadowsocks import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import v2ray_core_common_net "v2ray.com/core/common/net" import v2ray_core_common_protocol "v2ray.com/core/common/protocol" import v2ray_core_common_protocol1 "v2ray.com/core/common/protocol" @@ -116,8 +117,11 @@ func (m *Account) GetOta() Account_OneTimeAuth { } type ServerConfig struct { + // UdpEnabled specified whether or not to enable UDP for Shadowsocks. + // Deprecated. Use 'network' field. UdpEnabled bool `protobuf:"varint,1,opt,name=udp_enabled,json=udpEnabled" json:"udp_enabled,omitempty"` User *v2ray_core_common_protocol.User `protobuf:"bytes,2,opt,name=user" json:"user,omitempty"` + Network []v2ray_core_common_net.Network `protobuf:"varint,3,rep,packed,name=network,enum=v2ray.core.common.net.Network" json:"network,omitempty"` } func (m *ServerConfig) Reset() { *m = ServerConfig{} } @@ -139,6 +143,13 @@ func (m *ServerConfig) GetUser() *v2ray_core_common_protocol.User { return nil } +func (m *ServerConfig) GetNetwork() []v2ray_core_common_net.Network { + if m != nil { + return m.Network + } + return nil +} + type ClientConfig struct { Server []*v2ray_core_common_protocol1.ServerEndpoint `protobuf:"bytes,1,rep,name=server" json:"server,omitempty"` } @@ -166,35 +177,38 @@ func init() { func init() { proto.RegisterFile("v2ray.com/core/proxy/shadowsocks/config.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 477 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0x51, 0x8f, 0x93, 0x40, - 0x14, 0x85, 0x97, 0xb6, 0xb6, 0xf5, 0x52, 0x95, 0x9d, 0xc4, 0xa4, 0x69, 0x36, 0xb1, 0xe9, 0x53, - 0xdd, 0xc4, 0xa1, 0x65, 0x5d, 0xe3, 0x2b, 0x45, 0xd6, 0xdd, 0xa8, 0xb4, 0xa1, 0x5d, 0x8d, 0xbe, - 0x10, 0x76, 0x18, 0x2d, 0xb1, 0x30, 0x93, 0x19, 0xd8, 0x95, 0x5f, 0xe3, 0xbb, 0xff, 0xcc, 0x7f, - 0x61, 0x18, 0xda, 0x2e, 0xf1, 0xa1, 0xfb, 0xc6, 0xbd, 0x9c, 0x73, 0x38, 0xf7, 0x03, 0x5e, 0xdd, - 0x5a, 0x22, 0x2c, 0x30, 0x61, 0x89, 0x49, 0x98, 0xa0, 0x26, 0x17, 0xec, 0x57, 0x61, 0xca, 0x75, - 0x18, 0xb1, 0x3b, 0xc9, 0xc8, 0x4f, 0x69, 0x12, 0x96, 0x7e, 0x8f, 0x7f, 0x60, 0x2e, 0x58, 0xc6, - 0xd0, 0xc9, 0x4e, 0x2e, 0x28, 0x56, 0x52, 0x5c, 0x93, 0x0e, 0x5e, 0xfe, 0x17, 0x46, 0x58, 0x92, - 0xb0, 0xd4, 0x54, 0x56, 0xc2, 0x36, 0x66, 0x2e, 0xa9, 0xa8, 0x82, 0x06, 0x93, 0x07, 0xa4, 0x92, - 0x8a, 0x5b, 0x2a, 0x02, 0xc9, 0x29, 0xa9, 0x1c, 0xa3, 0xbf, 0x1a, 0x74, 0x6c, 0x42, 0x58, 0x9e, - 0x66, 0x68, 0x00, 0x5d, 0x1e, 0x4a, 0x79, 0xc7, 0x44, 0xd4, 0xd7, 0x86, 0xda, 0xf8, 0xb1, 0xbf, - 0x9f, 0xd1, 0x15, 0xe8, 0x24, 0xe6, 0x6b, 0x2a, 0x82, 0xac, 0xe0, 0xb4, 0xdf, 0x18, 0x6a, 0xe3, - 0xa7, 0xd6, 0x18, 0x1f, 0x2a, 0x8e, 0x1d, 0x65, 0x58, 0x15, 0x9c, 0xfa, 0x40, 0xf6, 0xcf, 0xc8, - 0x81, 0x26, 0xcb, 0xc2, 0x7e, 0x53, 0x45, 0x4c, 0x0f, 0x47, 0x6c, 0xab, 0xe1, 0x79, 0x4a, 0x57, - 0x71, 0x42, 0xed, 0x3c, 0x5b, 0xfb, 0xa5, 0x7b, 0x64, 0x81, 0x5e, 0xdb, 0xa1, 0x2e, 0xb4, 0xec, - 0x3c, 0x63, 0xc6, 0x11, 0xea, 0x41, 0xf7, 0x5d, 0x2c, 0xc3, 0x9b, 0x0d, 0x8d, 0x0c, 0x0d, 0xe9, - 0xd0, 0x71, 0xd3, 0x6a, 0x68, 0x8c, 0x28, 0xf4, 0x96, 0x0a, 0x80, 0xa3, 0xe0, 0xa3, 0x17, 0xa0, - 0xe7, 0x11, 0x0f, 0x68, 0x25, 0x50, 0x27, 0x77, 0x7d, 0xc8, 0x23, 0xbe, 0xb5, 0xa0, 0xd7, 0xd0, - 0x2a, 0xe1, 0xaa, 0x6b, 0x75, 0x6b, 0x58, 0xaf, 0x5a, 0x91, 0xc5, 0x3b, 0xb2, 0xf8, 0x5a, 0x52, - 0xe1, 0x2b, 0xf5, 0xc8, 0x87, 0x9e, 0xb3, 0x89, 0x69, 0x9a, 0x6d, 0x3f, 0x33, 0x83, 0x76, 0xc5, - 0xbd, 0xaf, 0x0d, 0x9b, 0x63, 0xdd, 0x3a, 0x3d, 0x94, 0x53, 0x15, 0x74, 0xd3, 0x88, 0xb3, 0x38, - 0xcd, 0xfc, 0xad, 0xf3, 0xf4, 0xb7, 0x06, 0x70, 0x8f, 0xb3, 0x3c, 0xeb, 0xda, 0xfb, 0xe0, 0xcd, - 0xbf, 0x78, 0xc6, 0x11, 0x7a, 0x06, 0xba, 0xed, 0x2e, 0x83, 0xa9, 0xf5, 0x36, 0x70, 0x2e, 0x66, - 0x86, 0xb6, 0x5b, 0x58, 0xe7, 0x6f, 0xd4, 0xa2, 0x51, 0x32, 0x71, 0x2e, 0x6d, 0xe7, 0xd2, 0xb6, - 0x26, 0x46, 0x13, 0x1d, 0xc3, 0x93, 0xdd, 0x14, 0x5c, 0xb9, 0xab, 0x0b, 0xa3, 0x55, 0x8f, 0x78, - 0xef, 0x7c, 0x32, 0x1e, 0xd5, 0x23, 0xca, 0x45, 0x1b, 0x3d, 0x87, 0xe3, 0xbd, 0x69, 0x31, 0xff, - 0xf8, 0x75, 0x7a, 0x36, 0x39, 0x37, 0x3a, 0x25, 0x77, 0x6f, 0xee, 0xb9, 0x46, 0x77, 0xb6, 0x80, - 0x21, 0x61, 0xc9, 0xc1, 0xbf, 0xb9, 0xd0, 0xbe, 0xe9, 0xb5, 0xf1, 0x4f, 0xe3, 0xe4, 0xb3, 0xe5, - 0x87, 0x05, 0x76, 0x4a, 0xf5, 0x42, 0xa9, 0x97, 0xf7, 0xaf, 0x6f, 0xda, 0x0a, 0xca, 0xd9, 0xbf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0xd5, 0x9f, 0x8c, 0x4d, 0x03, 0x00, 0x00, + // 522 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xc1, 0x6e, 0xd3, 0x4e, + 0x10, 0xc6, 0xbb, 0x71, 0xff, 0x4d, 0xfe, 0xe3, 0x50, 0xdc, 0x95, 0x90, 0xac, 0xa8, 0x42, 0x56, + 0x38, 0x10, 0x2a, 0xb1, 0x4e, 0x5c, 0x8a, 0x7a, 0x75, 0x4c, 0x4a, 0x2b, 0xc0, 0x89, 0x9c, 0x14, + 0x04, 0x17, 0xcb, 0x5d, 0x2f, 0xc4, 0x6a, 0xe2, 0xb5, 0xd6, 0x76, 0x43, 0x9e, 0x86, 0x03, 0x37, + 0xde, 0x8c, 0xb7, 0x40, 0x5e, 0x3b, 0xa9, 0x85, 0xaa, 0x70, 0x88, 0x94, 0x99, 0xfd, 0x7d, 0x9f, + 0x66, 0xbe, 0x31, 0xbc, 0xbc, 0xb3, 0x44, 0xb0, 0x26, 0x94, 0x2f, 0x4d, 0xca, 0x05, 0x33, 0x13, + 0xc1, 0xbf, 0xaf, 0xcd, 0x74, 0x1e, 0x84, 0x7c, 0x95, 0x72, 0x7a, 0x9b, 0x9a, 0x94, 0xc7, 0x5f, + 0xa3, 0x6f, 0x24, 0x11, 0x3c, 0xe3, 0xf8, 0x78, 0x83, 0x0b, 0x46, 0x24, 0x4a, 0x6a, 0x68, 0xe7, + 0xf9, 0x5f, 0x66, 0x94, 0x2f, 0x97, 0x3c, 0x36, 0x63, 0x96, 0x15, 0xbf, 0x15, 0x17, 0xb7, 0xa5, + 0x4d, 0xe7, 0xc5, 0xc3, 0xa0, 0x7c, 0xa4, 0x7c, 0x61, 0xe6, 0x29, 0x13, 0x15, 0xda, 0xff, 0x07, + 0x9a, 0x32, 0x71, 0xc7, 0x84, 0x9f, 0x26, 0x8c, 0x96, 0x8a, 0xee, 0x6f, 0x04, 0x4d, 0x9b, 0x52, + 0x9e, 0xc7, 0x19, 0xee, 0x40, 0x2b, 0x09, 0xd2, 0x74, 0xc5, 0x45, 0xa8, 0x23, 0x03, 0xf5, 0xfe, + 0xf7, 0xb6, 0x35, 0xbe, 0x02, 0x95, 0x46, 0xc9, 0x9c, 0x09, 0x3f, 0x5b, 0x27, 0x4c, 0x6f, 0x18, + 0xa8, 0x77, 0x68, 0xf5, 0xc8, 0xae, 0x0d, 0x89, 0x23, 0x05, 0xb3, 0x75, 0xc2, 0x3c, 0xa0, 0xdb, + 0xff, 0xd8, 0x01, 0x85, 0x67, 0x81, 0xae, 0x48, 0x8b, 0xc1, 0x6e, 0x8b, 0x6a, 0x34, 0x32, 0x8e, + 0xd9, 0x2c, 0x5a, 0x32, 0x3b, 0xcf, 0xe6, 0x5e, 0xa1, 0xee, 0x5a, 0xa0, 0xd6, 0x7a, 0xb8, 0x05, + 0xfb, 0x76, 0x9e, 0x71, 0x6d, 0x0f, 0xb7, 0xa1, 0xf5, 0x26, 0x4a, 0x83, 0x9b, 0x05, 0x0b, 0x35, + 0x84, 0x55, 0x68, 0x8e, 0xe2, 0xb2, 0x68, 0x74, 0x7f, 0x22, 0x68, 0x4f, 0x65, 0x02, 0x8e, 0x3c, + 0x13, 0x7e, 0x06, 0x6a, 0x1e, 0x26, 0x3e, 0x2b, 0x09, 0xb9, 0x73, 0x6b, 0xd8, 0xd0, 0x91, 0x07, + 0x79, 0x98, 0x54, 0x3a, 0xfc, 0x0a, 0xf6, 0x8b, 0x84, 0xe5, 0xca, 0xaa, 0x65, 0xd4, 0xe7, 0x2d, + 0xe3, 0x25, 0x9b, 0x78, 0xc9, 0x75, 0xca, 0x84, 0x27, 0x69, 0x7c, 0x0e, 0xcd, 0xea, 0x8a, 0xba, + 0x62, 0x28, 0xbd, 0x43, 0xeb, 0xe9, 0x03, 0xc2, 0x98, 0x65, 0xc4, 0x2d, 0x29, 0x6f, 0x83, 0x77, + 0x3d, 0x68, 0x3b, 0x8b, 0x88, 0xc5, 0x59, 0x35, 0xe4, 0x10, 0x0e, 0xca, 0xb3, 0xe9, 0xc8, 0x50, + 0x7a, 0xaa, 0x75, 0xb2, 0x6b, 0x82, 0x72, 0xbd, 0x51, 0x1c, 0x26, 0x3c, 0x8a, 0x33, 0xaf, 0x52, + 0x9e, 0xfc, 0x40, 0x00, 0xf7, 0xd7, 0x28, 0x52, 0xb9, 0x76, 0xdf, 0xb9, 0xe3, 0x4f, 0xae, 0xb6, + 0x87, 0x1f, 0x83, 0x6a, 0x8f, 0xa6, 0xfe, 0xc0, 0x3a, 0xf7, 0x9d, 0x8b, 0xa1, 0x86, 0x36, 0x0d, + 0xeb, 0xec, 0xb5, 0x6c, 0x34, 0x8a, 0x48, 0x9d, 0x4b, 0xdb, 0xb9, 0xb4, 0xad, 0xbe, 0xa6, 0xe0, + 0x23, 0x78, 0xb4, 0xa9, 0xfc, 0xab, 0xd1, 0xec, 0x42, 0xdb, 0xaf, 0x5b, 0xbc, 0x75, 0x3e, 0x68, + 0xff, 0xd5, 0x2d, 0x8a, 0xc6, 0x01, 0x7e, 0x02, 0x47, 0x5b, 0xd1, 0x64, 0xfc, 0xfe, 0xf3, 0xe0, + 0xb4, 0x7f, 0xa6, 0x35, 0x8b, 0xb3, 0xb9, 0x63, 0x77, 0xa4, 0xb5, 0x86, 0x13, 0x30, 0x28, 0x5f, + 0xee, 0xfc, 0x18, 0x26, 0xe8, 0x8b, 0x5a, 0x2b, 0x7f, 0x35, 0x8e, 0x3f, 0x5a, 0x5e, 0xb0, 0x26, + 0x4e, 0x41, 0x4f, 0x24, 0x3d, 0xbd, 0x7f, 0xbe, 0x39, 0x90, 0xa1, 0x9c, 0xfe, 0x09, 0x00, 0x00, + 0xff, 0xff, 0xdc, 0x7e, 0x6b, 0x61, 0xb5, 0x03, 0x00, 0x00, } diff --git a/proxy/shadowsocks/config.proto b/proxy/shadowsocks/config.proto index a9a9fb5a1..9e77a7efd 100644 --- a/proxy/shadowsocks/config.proto +++ b/proxy/shadowsocks/config.proto @@ -6,6 +6,7 @@ option go_package = "shadowsocks"; option java_package = "com.v2ray.core.proxy.shadowsocks"; option java_multiple_files = true; +import "v2ray.com/core/common/net/network.proto"; import "v2ray.com/core/common/protocol/user.proto"; import "v2ray.com/core/common/protocol/server_spec.proto"; @@ -33,8 +34,11 @@ enum CipherType { } message ServerConfig { - bool udp_enabled = 1; + // UdpEnabled specified whether or not to enable UDP for Shadowsocks. + // Deprecated. Use 'network' field. + bool udp_enabled = 1 [deprecated = true]; v2ray.core.common.protocol.User user = 2; + repeated v2ray.core.common.net.Network network = 3; } message ClientConfig { diff --git a/proxy/shadowsocks/ota.go b/proxy/shadowsocks/ota.go index 3d9396822..500ccec38 100644 --- a/proxy/shadowsocks/ota.go +++ b/proxy/shadowsocks/ota.go @@ -70,22 +70,14 @@ func NewChunkReader(reader io.Reader, auth *Authenticator) *ChunkReader { } func (v *ChunkReader) ReadMultiBuffer() (buf.MultiBuffer, error) { - buffer := buf.New() - if err := buffer.AppendSupplier(buf.ReadFullFrom(v.reader, 2)); err != nil { - buffer.Release() - return nil, err - } - // There is a potential buffer overflow here. Large buffer is 64K bytes, - // while uin16 + 10 will be more than that - length := serial.BytesToUint16(buffer.BytesTo(2)) + AuthSize - if length > buf.Size { - // Theoretically the size of a chunk is 64K, but most Shadowsocks implementations used <4K buffer. - buffer.Release() - buffer = buf.NewLocal(int(length) + 128) + size, err := serial.ReadUint16(v.reader) + if err != nil { + return nil, newError("failed to read size") } + size += AuthSize - buffer.Clear() - if err := buffer.AppendSupplier(buf.ReadFullFrom(v.reader, int(length))); err != nil { + buffer := buf.NewSize(int32(size)) + if err := buffer.AppendSupplier(buf.ReadFullFrom(v.reader, int32(size))); err != nil { buffer.Release() return nil, err } diff --git a/proxy/shadowsocks/ota_test.go b/proxy/shadowsocks/ota_test.go index 95064444f..106466389 100644 --- a/proxy/shadowsocks/ota_test.go +++ b/proxy/shadowsocks/ota_test.go @@ -24,11 +24,11 @@ func TestNormalChunkReading(t *testing.T) { func TestNormalChunkWriting(t *testing.T) { assert := With(t) - buffer := buf.NewLocal(512) + buffer := buf.NewSize(512) writer := NewChunkWriter(buffer, NewAuthenticator(ChunkKeyGenerator( []byte{21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36}))) - b := buf.NewLocal(256) + b := buf.NewSize(256) b.Append([]byte{11, 12, 13, 14, 15, 16, 17, 18}) err := writer.WriteMultiBuffer(buf.NewMultiBufferValue(b)) assert(err, IsNil) diff --git a/proxy/shadowsocks/protocol.go b/proxy/shadowsocks/protocol.go index 62fe14989..9a86c958f 100644 --- a/proxy/shadowsocks/protocol.go +++ b/proxy/shadowsocks/protocol.go @@ -8,18 +8,23 @@ import ( "v2ray.com/core/common" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/buf" + "v2ray.com/core/common/dice" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" - "v2ray.com/core/proxy/socks" ) const ( Version = 1 RequestOptionOneTimeAuth bitmask.Byte = 0x01 +) - AddrTypeIPv4 = 1 - AddrTypeIPv6 = 4 - AddrTypeDomain = 3 +var addrParser = protocol.NewAddressParser( + protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), + protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), + protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), + protocol.WithAddressTypeParser(func(b byte) byte { + return b & 0x0F + }), ) // ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts. @@ -30,7 +35,7 @@ func ReadTCPSession(user *protocol.User, reader io.Reader) (*protocol.RequestHea } account := rawAccount.(*MemoryAccount) - buffer := buf.NewLocal(512) + buffer := buf.New() defer buffer.Release() ivLen := account.Cipher.IVSize() @@ -57,10 +62,21 @@ func ReadTCPSession(user *protocol.User, reader io.Reader) (*protocol.RequestHea Command: protocol.RequestCommandTCP, } - if err := buffer.Reset(buf.ReadFullFrom(br, 1)); err != nil { - return nil, nil, newError("failed to read address type").Base(err) + buffer.Clear() + + addr, port, err := addrParser.ReadAddressPort(buffer, br) + + if err != nil { + // Invalid address. Continue to read some bytes to confuse client. + nBytes := dice.Roll(32) + 1 + buffer.Clear() + buffer.AppendSupplier(buf.ReadFullFrom(br, int32(nBytes))) + return nil, nil, newError("failed to read address").Base(err) } + request.Address = addr + request.Port = port + if !account.Cipher.IsAEAD() { if (buffer.Byte(0) & 0x10) == 0x10 { request.Option.Set(RequestOptionOneTimeAuth) @@ -75,38 +91,6 @@ func ReadTCPSession(user *protocol.User, reader io.Reader) (*protocol.RequestHea } } - addrType := (buffer.Byte(0) & 0x0F) - switch addrType { - case AddrTypeIPv4: - if err := buffer.AppendSupplier(buf.ReadFullFrom(br, 4)); err != nil { - return nil, nil, newError("failed to read IPv4 address").Base(err) - } - request.Address = net.IPAddress(buffer.BytesFrom(-4)) - case AddrTypeIPv6: - if err := buffer.AppendSupplier(buf.ReadFullFrom(br, 16)); err != nil { - return nil, nil, newError("failed to read IPv6 address").Base(err) - } - request.Address = net.IPAddress(buffer.BytesFrom(-16)) - case AddrTypeDomain: - if err := buffer.AppendSupplier(buf.ReadFullFrom(br, 1)); err != nil { - return nil, nil, newError("failed to read domain lenth.").Base(err) - } - domainLength := int(buffer.BytesFrom(-1)[0]) - err = buffer.AppendSupplier(buf.ReadFullFrom(br, domainLength)) - if err != nil { - return nil, nil, newError("failed to read domain").Base(err) - } - request.Address = net.DomainAddress(string(buffer.BytesFrom(-domainLength))) - default: - // Check address validity after OTA verification. - } - - err = buffer.AppendSupplier(buf.ReadFullFrom(br, 2)) - if err != nil { - return nil, nil, newError("failed to read port").Base(err) - } - request.Port = net.PortFromBytes(buffer.BytesFrom(-2)) - if request.Option.Has(RequestOptionOneTimeAuth) { actualAuth := make([]byte, AuthSize) authenticator.Authenticate(buffer.Bytes())(actualAuth) @@ -165,9 +149,9 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri return nil, newError("failed to create encoding stream").Base(err).AtError() } - header := buf.NewLocal(512) + header := buf.New() - if err := socks.AppendAddress(header, request.Address, request.Port); err != nil { + if err := addrParser.WriteAddressPort(header, request.Address, request.Port); err != nil { return nil, newError("failed to write address").Base(err) } @@ -247,7 +231,7 @@ func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buff } iv := buffer.Bytes() - if err := socks.AppendAddress(buffer, request.Address, request.Port); err != nil { + if err := addrParser.WriteAddressPort(buffer, request.Address, request.Port); err != nil { return nil, newError("failed to write address").Base(err) } @@ -309,7 +293,7 @@ func DecodeUDPPacket(user *protocol.User, payload *buf.Buffer) (*protocol.Reques authenticator := NewAuthenticator(HeaderKeyGenerator(account.Key, iv)) actualAuth := make([]byte, AuthSize) - authenticator.Authenticate(payload.BytesTo(payloadLen))(actualAuth) + common.Must2(authenticator.Authenticate(payload.BytesTo(payloadLen))(actualAuth)) if !bytes.Equal(actualAuth, authBytes) { return nil, nil, newError("invalid OTA") } @@ -318,25 +302,15 @@ func DecodeUDPPacket(user *protocol.User, payload *buf.Buffer) (*protocol.Reques } } - addrType := (payload.Byte(0) & 0x0F) - payload.SliceFrom(1) - switch addrType { - case AddrTypeIPv4: - request.Address = net.IPAddress(payload.BytesTo(4)) - payload.SliceFrom(4) - case AddrTypeIPv6: - request.Address = net.IPAddress(payload.BytesTo(16)) - payload.SliceFrom(16) - case AddrTypeDomain: - domainLength := int(payload.Byte(0)) - request.Address = net.DomainAddress(string(payload.BytesRange(1, 1+domainLength))) - payload.SliceFrom(1 + domainLength) - default: - return nil, nil, newError("unknown address type: ", addrType).AtError() + payload.SetByte(0, payload.Byte(0)&0x0F) + + addr, port, err := addrParser.ReadAddressPort(nil, payload) + if err != nil { + return nil, nil, newError("failed to parse address").Base(err) } - request.Port = net.PortFromBytes(payload.BytesTo(2)) - payload.SliceFrom(2) + request.Address = addr + request.Port = port return request, payload, nil } diff --git a/proxy/shadowsocks/protocol_test.go b/proxy/shadowsocks/protocol_test.go index f2cb3c0a3..65f72a139 100644 --- a/proxy/shadowsocks/protocol_test.go +++ b/proxy/shadowsocks/protocol_test.go @@ -29,7 +29,7 @@ func TestUDPEncoding(t *testing.T) { }, } - data := buf.NewLocal(256) + data := buf.NewSize(256) data.AppendSupplier(serial.WriteString("test string")) encodedData, err := EncodeUDPPacket(request, data.Bytes()) assert(err, IsNil) @@ -39,6 +39,7 @@ func TestUDPEncoding(t *testing.T) { assert(decodedData.Bytes(), Equals, data.Bytes()) assert(decodedRequest.Address, Equals, request.Address) assert(decodedRequest.Port, Equals, request.Port) + assert(decodedRequest.Command, Equals, request.Command) } func TestTCPRequest(t *testing.T) { @@ -118,6 +119,7 @@ func TestTCPRequest(t *testing.T) { assert(err, IsNil) assert(decodedRequest.Address, Equals, request.Address) assert(decodedRequest.Port, Equals, request.Port) + assert(decodedRequest.Command, Equals, request.Command) decodedData, err := reader.ReadMultiBuffer() assert(err, IsNil) diff --git a/proxy/shadowsocks/server.go b/proxy/shadowsocks/server.go index 31b345a07..a58d19720 100644 --- a/proxy/shadowsocks/server.go +++ b/proxy/shadowsocks/server.go @@ -17,7 +17,7 @@ import ( ) type Server struct { - config *ServerConfig + config ServerConfig user *protocol.User account *MemoryAccount v *core.Instance @@ -36,14 +36,10 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { account := rawAccount.(*MemoryAccount) s := &Server{ - config: config, + config: *config, user: config.GetUser(), account: account, - v: core.FromContext(ctx), - } - - if s.v == nil { - return nil, newError("V is not in context.") + v: core.MustFromContext(ctx), } return s, nil @@ -51,7 +47,10 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { func (s *Server) Network() net.NetworkList { list := net.NetworkList{ - Network: []net.Network{net.Network_TCP}, + Network: s.config.Network, + } + if len(list.Network) == 0 { + list.Network = append(list.Network, net.Network_TCP) } if s.config.UdpEnabled { list.Network = append(list.Network, net.Network_UDP) @@ -84,7 +83,7 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection request, data, err := DecodeUDPPacket(s.user, payload) if err != nil { if source, ok := proxy.SourceFromContext(ctx); ok { - newError("dropping invalid UDP packet from: ", source).Base(err).WriteToLog() + newError("dropping invalid UDP packet from: ", source).Base(err).WithContext(ctx).WriteToLog() log.Record(&log.AccessMessage{ From: source, To: "", @@ -97,13 +96,13 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection } if request.Option.Has(RequestOptionOneTimeAuth) && s.account.OneTimeAuth == Account_Disabled { - newError("client payload enables OTA but server doesn't allow it").WriteToLog() + newError("client payload enables OTA but server doesn't allow it").WithContext(ctx).WriteToLog() payload.Release() continue } if !request.Option.Has(RequestOptionOneTimeAuth) && s.account.OneTimeAuth == Account_Enabled { - newError("client payload disables OTA but server forces it").WriteToLog() + newError("client payload disables OTA but server forces it").WithContext(ctx).WriteToLog() payload.Release() continue } @@ -117,15 +116,14 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection Reason: "", }) } - newError("tunnelling request to ", dest).WriteToLog() + newError("tunnelling request to ", dest).WithContext(ctx).WriteToLog() ctx = protocol.ContextWithUser(ctx, request.User) udpServer.Dispatch(ctx, dest, data, func(payload *buf.Buffer) { - defer payload.Release() - data, err := EncodeUDPPacket(request, payload.Bytes()) + payload.Release() if err != nil { - newError("failed to encode UDP packet").Base(err).AtWarning().WriteToLog() + newError("failed to encode UDP packet").Base(err).AtWarning().WithContext(ctx).WriteToLog() return } defer data.Release() @@ -163,7 +161,7 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, Status: log.AccessAccepted, Reason: "", }) - newError("tunnelling request to ", dest).WriteToLog() + newError("tunnelling request to ", dest).WithContext(ctx).WriteToLog() ctx = protocol.ContextWithUser(ctx, request.User) @@ -175,20 +173,23 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, } responseDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) + bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) responseWriter, err := WriteTCPResponse(request, bufferedWriter) if err != nil { return newError("failed to write response").Base(err) } - payload, err := ray.InboundOutput().ReadMultiBuffer() - if err != nil { - return err + { + payload, err := ray.InboundOutput().ReadMultiBuffer() + if err != nil { + return err + } + if err := responseWriter.WriteMultiBuffer(payload); err != nil { + return err + } } - if err := responseWriter.WriteMultiBuffer(payload); err != nil { - return err - } - payload.Release() if err := bufferedWriter.SetBuffered(false); err != nil { return err @@ -198,18 +199,17 @@ func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, return newError("failed to transport all TCP response").Base(err) } - timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) - return nil }) requestDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) defer ray.InboundInput().Close() if err := buf.Copy(bodyReader, ray.InboundInput(), buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP request").Base(err) } - timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + return nil }) diff --git a/proxy/socks/client.go b/proxy/socks/client.go index ea97b8362..3958ae9de 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -4,6 +4,7 @@ import ( "context" "time" + "v2ray.com/core" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/net" @@ -17,7 +18,8 @@ import ( // Client is a Socks5 client. type Client struct { - serverPicker protocol.ServerPicker + serverPicker protocol.ServerPicker + policyManager core.PolicyManager } // NewClient create a new Socks5 client based on the given config. @@ -30,8 +32,10 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { return nil, newError("0 target server") } + v := core.MustFromContext(ctx) return &Client{ - serverPicker: protocol.NewRoundRobinServerPicker(serverList), + serverPicker: protocol.NewRoundRobinServerPicker(serverList), + policyManager: v.PolicyManager(), }, nil } @@ -45,7 +49,7 @@ func (c *Client) Process(ctx context.Context, ray ray.OutboundRay, dialer proxy. var server *protocol.ServerSpec var conn internet.Connection - err := retry.ExponentialBackoff(5, 100).On(func() error { + if err := retry.ExponentialBackoff(5, 100).On(func() error { server = c.serverPicker.PickServer() dest := server.Destination() rawConn, err := dialer.Dial(ctx, dest) @@ -55,13 +59,17 @@ func (c *Client) Process(ctx context.Context, ray ray.OutboundRay, dialer proxy. conn = rawConn return nil - }) - - if err != nil { + }); err != nil { return newError("failed to find an available destination").Base(err) } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + newError("failed to closed connection").Base(err).WithContext(ctx).WriteToLog() + } + }() + + p := c.policyManager.ForLevel(0) request := &protocol.RequestHeader{ Version: socks5Version, @@ -76,24 +84,33 @@ func (c *Client) Process(ctx context.Context, ray ray.OutboundRay, dialer proxy. user := server.PickUser() if user != nil { request.User = user + p = c.policyManager.ForLevel(user.Level) } + if err := conn.SetDeadline(time.Now().Add(p.Timeouts.Handshake)); err != nil { + newError("failed to set deadline for handshake").Base(err).WithContext(ctx).WriteToLog() + } udpRequest, err := ClientHandshake(request, conn, conn) if err != nil { return newError("failed to establish connection to server").AtWarning().Base(err) } + if err := conn.SetDeadline(time.Time{}); err != nil { + newError("failed to clear deadline after handshake").Base(err).WithContext(ctx).WriteToLog() + } + ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, time.Minute*5) + timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) var requestFunc func() error var responseFunc func() error if request.Command == protocol.RequestCommandTCP { requestFunc = func() error { + defer timer.SetTimeout(p.Timeouts.DownlinkOnly) return buf.Copy(ray.OutboundInput(), buf.NewWriter(conn), buf.UpdateActivity(timer)) } responseFunc = func() error { - defer ray.OutboundOutput().Close() + defer timer.SetTimeout(p.Timeouts.UplinkOnly) return buf.Copy(buf.NewReader(conn), ray.OutboundOutput(), buf.UpdateActivity(timer)) } } else if request.Command == protocol.RequestCommandUDP { @@ -103,10 +120,11 @@ func (c *Client) Process(ctx context.Context, ray ray.OutboundRay, dialer proxy. } defer udpConn.Close() requestFunc = func() error { + defer timer.SetTimeout(p.Timeouts.DownlinkOnly) return buf.Copy(ray.OutboundInput(), buf.NewSequentialWriter(NewUDPWriter(request, udpConn)), buf.UpdateActivity(timer)) } responseFunc = func() error { - defer ray.OutboundOutput().Close() + defer timer.SetTimeout(p.Timeouts.UplinkOnly) reader := &UDPReader{reader: udpConn} return buf.Copy(reader, ray.OutboundOutput(), buf.UpdateActivity(timer)) } diff --git a/proxy/socks/protocol.go b/proxy/socks/protocol.go index b9920630d..bd3a11823 100644 --- a/proxy/socks/protocol.go +++ b/proxy/socks/protocol.go @@ -26,21 +26,25 @@ const ( authPassword = 0x02 authNoMatchingMethod = 0xFF - addrTypeIPv4 = 0x01 - addrTypeIPv6 = 0x04 - addrTypeDomain = 0x03 - statusSuccess = 0x00 statusCmdNotSupport = 0x07 ) +var addrParser = protocol.NewAddressParser( + protocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4), + protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), + protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), +) + type ServerSession struct { config *ServerConfig port net.Port } func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { - buffer := buf.NewLocal(512) + buffer := buf.New() + defer buffer.Release() + request := new(protocol.RequestHeader) if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { @@ -49,6 +53,11 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol version := buffer.Byte(0) if version == socks4Version { + if s.config.AuthType == AuthType_PASSWORD { + writeSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0)) + return nil, newError("socks 4 is not allowed when auth is required.") + } + if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 6)); err != nil { return nil, newError("insufficient header").Base(err) } @@ -83,7 +92,7 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol } if version == socks5Version { - nMethod := int(buffer.Byte(1)) + nMethod := int32(buffer.Byte(1)) if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nMethod)); err != nil { return nil, newError("failed to read auth methods").Base(err) } @@ -117,56 +126,38 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol return nil, newError("failed to write auth response").Base(err) } } - if err := buffer.Reset(buf.ReadFullFrom(reader, 4)); err != nil { + if err := buffer.Reset(buf.ReadFullFrom(reader, 3)); err != nil { return nil, newError("failed to read request").Base(err) } cmd := buffer.Byte(1) - if cmd == cmdTCPBind || (cmd == cmdUDPPort && !s.config.UdpEnabled) { - writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) - return nil, newError("unsupported command: ", cmd) - } - switch cmd { case cmdTCPConnect: request.Command = protocol.RequestCommandTCP case cmdUDPPort: + if !s.config.UdpEnabled { + writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) + return nil, newError("UDP is not enabled.") + } request.Command = protocol.RequestCommandUDP + case cmdTCPBind: + writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) + return nil, newError("TCP bind is not supported.") + default: + writeSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0)) + return nil, newError("unknown command ", cmd) } - addrType := buffer.Byte(3) - buffer.Clear() request.Version = socks5Version - switch addrType { - case addrTypeIPv4: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { - return nil, err - } - request.Address = net.IPAddress(buffer.Bytes()) - case addrTypeIPv6: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil { - return nil, err - } - request.Address = net.IPAddress(buffer.Bytes()) - case addrTypeDomain: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil { - return nil, err - } - domainLength := int(buffer.Byte(0)) - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil { - return nil, err - } - request.Address = net.ParseAddress(string(buffer.BytesFrom(-domainLength))) - default: - return nil, newError("Unknown address type: ", addrType) - } - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { - return nil, err + addr, port, err := addrParser.ReadAddressPort(buffer, reader) + if err != nil { + return nil, newError("failed to read address").Base(err) } - request.Port = net.PortFromBytes(buffer.BytesFrom(-2)) + request.Address = addr + request.Port = port responseAddress := net.AnyIP responsePort := net.Port(1717) @@ -189,13 +180,13 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol } func readUsernamePassword(reader io.Reader) (string, string, error) { - buffer := buf.NewLocal(512) + buffer := buf.New() defer buffer.Release() if err := buffer.Reset(buf.ReadFullFrom(reader, 2)); err != nil { return "", "", err } - nUsername := int(buffer.Byte(1)) + nUsername := int32(buffer.Byte(1)) if err := buffer.Reset(buf.ReadFullFrom(reader, nUsername)); err != nil { return "", "", err @@ -205,7 +196,7 @@ func readUsernamePassword(reader io.Reader) (string, string, error) { if err := buffer.Reset(buf.ReadFullFrom(reader, 1)); err != nil { return "", "", err } - nPassword := int(buffer.Byte(0)) + nPassword := int32(buffer.Byte(0)) if err := buffer.Reset(buf.ReadFullFrom(reader, nPassword)); err != nil { return "", "", err } @@ -245,30 +236,12 @@ func writeSocks5AuthenticationResponse(writer io.Writer, version byte, auth byte return err } -// AppendAddress appends Socks address into the given buffer. -func AppendAddress(buffer *buf.Buffer, address net.Address, port net.Port) error { - switch address.Family() { - case net.AddressFamilyIPv4: - buffer.AppendBytes(addrTypeIPv4) - buffer.Append(address.IP()) - case net.AddressFamilyIPv6: - buffer.AppendBytes(addrTypeIPv6) - buffer.Append(address.IP()) - case net.AddressFamilyDomain: - if protocol.IsDomainTooLong(address.Domain()) { - return newError("Super long domain is not supported in Socks protocol: ", address.Domain()) - } - buffer.AppendBytes(addrTypeDomain, byte(len(address.Domain()))) - common.Must(buffer.AppendSupplier(serial.WriteString(address.Domain()))) - } - common.Must(buffer.AppendSupplier(serial.WriteUint16(port.Value()))) - return nil -} - func writeSocks5Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error { - buffer := buf.NewLocal(64) + buffer := buf.New() + defer buffer.Release() + buffer.AppendBytes(socks5Version, errCode, 0x00 /* reserved */) - if err := AppendAddress(buffer, address, port); err != nil { + if err := addrParser.WriteAddressPort(buffer, address, port); err != nil { return err } @@ -277,17 +250,19 @@ func writeSocks5Response(writer io.Writer, errCode byte, address net.Address, po } func writeSocks4Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error { - buffer := buf.NewLocal(32) + buffer := buf.New() + defer buffer.Release() + buffer.AppendBytes(0x00, errCode) - buffer.AppendSupplier(serial.WriteUint16(port.Value())) + common.Must(buffer.AppendSupplier(serial.WriteUint16(port.Value()))) buffer.Append(address.IP()) _, err := writer.Write(buffer.Bytes()) return err } -func DecodeUDPPacket(packet []byte) (*protocol.RequestHeader, []byte, error) { - if len(packet) < 5 { - return nil, nil, newError("insufficient length of packet.") +func DecodeUDPPacket(packet *buf.Buffer) (*protocol.RequestHeader, error) { + if packet.Len() < 5 { + return nil, newError("insufficient length of packet.") } request := &protocol.RequestHeader{ Version: socks5Version, @@ -295,50 +270,26 @@ func DecodeUDPPacket(packet []byte) (*protocol.RequestHeader, []byte, error) { } // packet[0] and packet[1] are reserved - if packet[2] != 0 /* fragments */ { - return nil, nil, newError("discarding fragmented payload.") + if packet.Byte(2) != 0 /* fragments */ { + return nil, newError("discarding fragmented payload.") } - addrType := packet[3] - var dataBegin int + packet.SliceFrom(3) - switch addrType { - case addrTypeIPv4: - if len(packet) < 10 { - return nil, nil, newError("insufficient length of packet") - } - ip := packet[4:8] - request.Port = net.PortFromBytes(packet[8:10]) - request.Address = net.IPAddress(ip) - dataBegin = 10 - case addrTypeIPv6: - if len(packet) < 22 { - return nil, nil, newError("insufficient length of packet") - } - ip := packet[4:20] - request.Port = net.PortFromBytes(packet[20:22]) - request.Address = net.IPAddress(ip) - dataBegin = 22 - case addrTypeDomain: - domainLength := int(packet[4]) - if len(packet) < 5+domainLength+2 { - return nil, nil, newError("insufficient length of packet") - } - domain := string(packet[5 : 5+domainLength]) - request.Port = net.PortFromBytes(packet[5+domainLength : 5+domainLength+2]) - request.Address = net.ParseAddress(domain) - dataBegin = 5 + domainLength + 2 - default: - return nil, nil, newError("unknown address type ", addrType) + addr, port, err := addrParser.ReadAddressPort(nil, packet) + if err != nil { + return nil, newError("failed to read UDP header").Base(err) } - - return request, packet[dataBegin:], nil + request.Address = addr + request.Port = port + return request, nil } func EncodeUDPPacket(request *protocol.RequestHeader, data []byte) (*buf.Buffer, error) { b := buf.New() b.AppendBytes(0, 0, 0 /* Fragment */) - if err := AppendAddress(b, request.Address, request.Port); err != nil { + if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil { + b.Release() return nil, err } b.Append(data) @@ -358,12 +309,9 @@ func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) { if err := b.AppendSupplier(buf.ReadFrom(r.reader)); err != nil { return nil, err } - _, data, err := DecodeUDPPacket(b.Bytes()) - if err != nil { + if _, err := DecodeUDPPacket(b); err != nil { return nil, err } - b.Clear() - b.Append(data) return buf.NewMultiBufferValue(b), nil } @@ -398,7 +346,9 @@ func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer i authByte = byte(authPassword) } - b := buf.NewLocal(512) + b := buf.New() + defer b.Release() + b.AppendBytes(socks5Version, 0x01, authByte) if authByte == authPassword { rawAccount, err := request.User.GetTypedAccount() @@ -444,13 +394,16 @@ func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer i command = byte(cmdUDPPort) } b.AppendBytes(socks5Version, command, 0x00 /* reserved */) - AppendAddress(b, request.Address, request.Port) + if err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil { + return nil, err + } + if _, err := writer.Write(b.Bytes()); err != nil { return nil, err } b.Clear() - if err := b.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 3)); err != nil { return nil, err } @@ -459,39 +412,12 @@ func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer i return nil, newError("server rejects request: ", resp) } - addrType := b.Byte(3) - b.Clear() - var address net.Address - switch addrType { - case addrTypeIPv4: - if err := b.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { - return nil, err - } - address = net.IPAddress(b.Bytes()) - case addrTypeIPv6: - if err := b.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil { - return nil, err - } - address = net.IPAddress(b.Bytes()) - case addrTypeDomain: - if err := b.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil { - return nil, err - } - domainLength := int(b.Byte(0)) - if err := b.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil { - return nil, err - } - address = net.DomainAddress(string(b.BytesFrom(-domainLength))) - default: - return nil, newError("unknown address type: ", addrType) - } - - if err := b.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { + address, port, err := addrParser.ReadAddressPort(b, reader) + if err != nil { return nil, err } - port := net.PortFromBytes(b.BytesFrom(-2)) if request.Command == protocol.RequestCommandUDP { udpRequest := &protocol.RequestHeader{ diff --git a/proxy/socks/protocol_test.go b/proxy/socks/protocol_test.go index a5cb18cfb..5857471bc 100644 --- a/proxy/socks/protocol_test.go +++ b/proxy/socks/protocol_test.go @@ -5,6 +5,7 @@ import ( "v2ray.com/core/common/buf" "v2ray.com/core/common/net" + _ "v2ray.com/core/common/net/testing" "v2ray.com/core/common/protocol" . "v2ray.com/core/proxy/socks" . "v2ray.com/ext/assert" diff --git a/proxy/socks/server.go b/proxy/socks/server.go index 262ef4ad5..314fd30ef 100644 --- a/proxy/socks/server.go +++ b/proxy/socks/server.go @@ -27,10 +27,7 @@ type Server struct { func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { s := &Server{ config: config, - v: core.FromContext(ctx), - } - if s.v == nil { - return nil, newError("V is not in context.") + v: core.MustFromContext(ctx), } return s, nil } @@ -44,6 +41,7 @@ func (s *Server) policy() core.Policy { return p } +// Network implements proxy.Inbound. func (s *Server) Network() net.NetworkList { list := net.NetworkList{ Network: []net.Network{net.Network_TCP}, @@ -54,6 +52,7 @@ func (s *Server) Network() net.NetworkList { return list } +// Process implements proxy.Inbound. func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher core.Dispatcher) error { switch network { case net.Network_TCP: @@ -66,7 +65,10 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn internet } func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error { - conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)) + if err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil { + newError("failed to set deadline").Base(err).WithContext(ctx).WriteToLog() + } + reader := buf.NewBufferedReader(buf.NewReader(conn)) inboundDest, ok := proxy.InboundEntryPointFromContext(ctx) @@ -90,11 +92,14 @@ func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispa } return newError("failed to read request").Base(err) } - conn.SetReadDeadline(time.Time{}) + + if err := conn.SetReadDeadline(time.Time{}); err != nil { + newError("failed to clear deadline").Base(err).WithContext(ctx).WriteToLog() + } if request.Command == protocol.RequestCommandTCP { dest := request.Destination() - newError("TCP Connect request to ", dest).WriteToLog() + newError("TCP Connect request to ", dest).WithContext(ctx).WriteToLog() if source, ok := proxy.SourceFromContext(ctx); ok { log.Record(&log.AccessMessage{ From: source, @@ -114,16 +119,16 @@ func (s *Server) processTCP(ctx context.Context, conn internet.Connection, dispa return nil } -func (*Server) handleUDP(c net.Conn) error { +func (*Server) handleUDP(c io.Reader) error { // The TCP connection closes after this method returns. We need to wait until // the client closes it. _, err := io.Copy(buf.DiscardBytes, c) return err } -func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher core.Dispatcher) error { +func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher core.Dispatcher) error { ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, v.policy().Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, s.policy().Timeouts.ConnectionIdle) ray, err := dispatcher.Dispatch(ctx, dest) if err != nil { @@ -134,22 +139,25 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ output := ray.InboundOutput() requestDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(s.policy().Timeouts.DownlinkOnly) defer input.Close() v2reader := buf.NewReader(reader) if err := buf.Copy(v2reader, input, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP request").Base(err) } - timer.SetTimeout(v.policy().Timeouts.DownlinkOnly) + return nil }) responseDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(s.policy().Timeouts.UplinkOnly) + v2writer := buf.NewWriter(writer) if err := buf.Copy(output, v2writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transport all TCP response").Base(err) } - timer.SetTimeout(v.policy().Timeouts.UplinkOnly) + return nil }) @@ -162,11 +170,11 @@ func (v *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ return nil } -func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error { +func (s *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, dispatcher core.Dispatcher) error { udpServer := udp.NewDispatcher(dispatcher) if source, ok := proxy.SourceFromContext(ctx); ok { - newError("client UDP connection from ", source).WriteToLog() + newError("client UDP connection from ", source).WithContext(ctx).WriteToLog() } reader := buf.NewReader(conn) @@ -177,18 +185,20 @@ func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, } for _, payload := range mpayload { - request, data, err := DecodeUDPPacket(payload.Bytes()) + request, err := DecodeUDPPacket(payload) if err != nil { - newError("failed to parse UDP request").Base(err).WriteToLog() + newError("failed to parse UDP request").Base(err).WithContext(ctx).WriteToLog() + payload.Release() continue } - if len(data) == 0 { + if payload.IsEmpty() { + payload.Release() continue } - newError("send packet to ", request.Destination(), " with ", len(data), " bytes").AtDebug().WriteToLog() + newError("send packet to ", request.Destination(), " with ", payload.Len(), " bytes").AtDebug().WithContext(ctx).WriteToLog() if source, ok := proxy.SourceFromContext(ctx); ok { log.Record(&log.AccessMessage{ From: source, @@ -198,17 +208,15 @@ func (v *Server) handleUDPPayload(ctx context.Context, conn internet.Connection, }) } - dataBuf := buf.New() - dataBuf.Append(data) - udpServer.Dispatch(ctx, request.Destination(), dataBuf, func(payload *buf.Buffer) { - defer payload.Release() - - newError("writing back UDP response with ", payload.Len(), " bytes").AtDebug().WriteToLog() + udpServer.Dispatch(ctx, request.Destination(), payload, func(payload *buf.Buffer) { + newError("writing back UDP response with ", payload.Len(), " bytes").AtDebug().WithContext(ctx).WriteToLog() udpMessage, err := EncodeUDPPacket(request, payload.Bytes()) + payload.Release() + defer udpMessage.Release() if err != nil { - newError("failed to write UDP response").AtWarning().Base(err).WriteToLog() + newError("failed to write UDP response").AtWarning().Base(err).WithContext(ctx).WriteToLog() } conn.Write(udpMessage.Bytes()) diff --git a/proxy/vmess/account.go b/proxy/vmess/account.go index 57cc1c5a3..1efc7edaf 100644 --- a/proxy/vmess/account.go +++ b/proxy/vmess/account.go @@ -9,7 +9,7 @@ import ( type InternalAccount struct { ID *protocol.ID AlterIDs []*protocol.ID - Security protocol.Security + Security protocol.SecurityType } func (a *InternalAccount) AnyValidID() *protocol.ID { @@ -37,6 +37,6 @@ func (a *Account) AsAccount() (protocol.Account, error) { return &InternalAccount{ ID: protoID, AlterIDs: protocol.NewAlterIDs(protoID, uint16(a.AlterId)), - Security: a.SecuritySettings.AsSecurity(), + Security: a.SecuritySettings.GetSecurityType(), }, nil } diff --git a/proxy/vmess/account.pb.go b/proxy/vmess/account.pb.go old mode 100644 new mode 100755 index d78ba2960..05607fa07 --- a/proxy/vmess/account.pb.go +++ b/proxy/vmess/account.pb.go @@ -17,7 +17,7 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Account struct { - // ID of the account, in the form of an UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". + // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` // Number of alternative IDs. Client and server must share the same number. AlterId uint32 `protobuf:"varint,2,opt,name=alter_id,json=alterId" json:"alter_id,omitempty"` diff --git a/proxy/vmess/account.proto b/proxy/vmess/account.proto old mode 100644 new mode 100755 index c69317d97..343749144 --- a/proxy/vmess/account.proto +++ b/proxy/vmess/account.proto @@ -9,7 +9,7 @@ option java_multiple_files = true; import "v2ray.com/core/common/protocol/headers.proto"; message Account { - // ID of the account, in the form of an UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". + // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; // Number of alternative IDs. Client and server must share the same number. uint32 alter_id = 2; diff --git a/proxy/vmess/encoding/auth.go b/proxy/vmess/encoding/auth.go index 05477661c..2f23fa673 100644 --- a/proxy/vmess/encoding/auth.go +++ b/proxy/vmess/encoding/auth.go @@ -88,7 +88,7 @@ func NewShakeSizeParser(nonce []byte) *ShakeSizeParser { } } -func (*ShakeSizeParser) SizeBytes() int { +func (*ShakeSizeParser) SizeBytes() int32 { return 2 } diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index cc930dc51..c8dae88f1 100644 --- a/proxy/vmess/encoding/client.go +++ b/proxy/vmess/encoding/client.go @@ -15,7 +15,6 @@ import ( "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/dice" - "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" "v2ray.com/core/proxy/vmess" @@ -63,8 +62,7 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)() account, err := header.User.GetTypedAccount() if err != nil { - newError("failed to get user account: ", err).AtError().WriteToLog() - return nil + return newError("failed to get user account: ", err).AtError() } idHash := c.idHash(account.(*vmess.InternalAccount).AnyValidID().Bytes()) common.Must2(idHash.Write(timestamp.Bytes(nil))) @@ -83,28 +81,13 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ buffer.AppendBytes(security, byte(0), byte(header.Command)) if header.Command != protocol.RequestCommandMux { - common.Must(buffer.AppendSupplier(serial.WriteUint16(header.Port.Value()))) - - switch header.Address.Family() { - case net.AddressFamilyIPv4: - buffer.AppendBytes(byte(protocol.AddressTypeIPv4)) - buffer.Append(header.Address.IP()) - case net.AddressFamilyIPv6: - buffer.AppendBytes(byte(protocol.AddressTypeIPv6)) - buffer.Append(header.Address.IP()) - case net.AddressFamilyDomain: - domain := header.Address.Domain() - if protocol.IsDomainTooLong(domain) { - return newError("long domain not supported: ", domain) - } - nDomain := len(domain) - buffer.AppendBytes(byte(protocol.AddressTypeDomain), byte(nDomain)) - common.Must(buffer.AppendSupplier(serial.WriteString(domain))) + if err := addrParser.WriteAddressPort(buffer, header.Address, header.Port); err != nil { + return newError("failed to writer address and port").Base(err) } } if padingLen > 0 { - common.Must(buffer.AppendSupplier(buf.ReadFullFrom(rand.Reader, padingLen))) + common.Must(buffer.AppendSupplier(buf.ReadFullFrom(rand.Reader, int32(padingLen)))) } fnv1a := fnv.New32a() @@ -129,7 +112,8 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.requestBodyIV) } - if request.Security.Is(protocol.SecurityType_NONE) { + switch request.Security { + case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamWriter(sizeParser, writer) @@ -143,9 +127,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } return buf.NewWriter(writer) - } - - if request.Security.Is(protocol.SecurityType_LEGACY) { + case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey, c.requestBodyIV) cryptionWriter := crypto.NewCryptionWriter(aesStream, writer) if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -158,9 +140,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } return buf.NewWriter(cryptionWriter) - } - - if request.Security.Is(protocol.SecurityType_AES128_GCM) { + case protocol.SecurityType_AES128_GCM: block, _ := aes.NewCipher(c.requestBodyKey) aead, _ := cipher.NewGCM(block) @@ -173,9 +153,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) - } - - if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey)) auth := &crypto.AEADAuthenticator{ @@ -187,9 +165,9 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + default: + panic("Unknown security type.") } - - panic("Unknown security type.") } func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) { @@ -200,8 +178,7 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon defer buffer.Release() if err := buffer.AppendSupplier(buf.ReadFullFrom(c.responseReader, 4)); err != nil { - newError("failed to read response header").Base(err).WriteToLog() - return nil, err + return nil, newError("failed to read response header").Base(err) } if buffer.Byte(0) != c.responseHeader { @@ -214,11 +191,10 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon if buffer.Byte(2) != 0 { cmdID := buffer.Byte(2) - dataLen := int(buffer.Byte(3)) + dataLen := int32(buffer.Byte(3)) if err := buffer.Reset(buf.ReadFullFrom(c.responseReader, dataLen)); err != nil { - newError("failed to read response command").Base(err).WriteToLog() - return nil, err + return nil, newError("failed to read response command").Base(err) } command, err := UnmarshalCommand(cmdID, buffer.Bytes()) if err == nil { @@ -234,7 +210,8 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.responseBodyIV) } - if request.Security.Is(protocol.SecurityType_NONE) { + switch request.Security { + case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamReader(sizeParser, reader) @@ -250,9 +227,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read } return buf.NewReader(reader) - } - - if request.Security.Is(protocol.SecurityType_LEGACY) { + case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), @@ -263,9 +238,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read } return buf.NewReader(c.responseReader) - } - - if request.Security.Is(protocol.SecurityType_AES128_GCM) { + case protocol.SecurityType_AES128_GCM: block, _ := aes.NewCipher(c.responseBodyKey) aead, _ := cipher.NewGCM(block) @@ -278,9 +251,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) - } - - if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey)) auth := &crypto.AEADAuthenticator{ @@ -292,9 +263,9 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + default: + panic("Unknown security type.") } - - panic("Unknown security type.") } type ChunkNonceGenerator struct { diff --git a/proxy/vmess/encoding/commands.go b/proxy/vmess/encoding/commands.go index eb25139a4..1b61f7773 100644 --- a/proxy/vmess/encoding/commands.go +++ b/proxy/vmess/encoding/commands.go @@ -32,7 +32,7 @@ func MarshalCommand(command interface{}, writer io.Writer) error { return ErrUnknownCommand } - buffer := buf.NewLocal(512) + buffer := buf.New() defer buffer.Release() err := factory.Marshal(command, buffer) diff --git a/proxy/vmess/encoding/const.go b/proxy/vmess/encoding/const.go deleted file mode 100644 index d9d858787..000000000 --- a/proxy/vmess/encoding/const.go +++ /dev/null @@ -1,5 +0,0 @@ -package encoding - -const ( - Version = byte(1) -) diff --git a/proxy/vmess/encoding/encoding.go b/proxy/vmess/encoding/encoding.go index 3772a39d3..a016f15b4 100644 --- a/proxy/vmess/encoding/encoding.go +++ b/proxy/vmess/encoding/encoding.go @@ -1,3 +1,19 @@ package encoding +import ( + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" +) + //go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg encoding -path Proxy,VMess,Encoding + +const ( + Version = byte(1) +) + +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(), +) diff --git a/proxy/vmess/encoding/encoding_test.go b/proxy/vmess/encoding/encoding_test.go index 2777f8cf3..d372189a3 100644 --- a/proxy/vmess/encoding/encoding_test.go +++ b/proxy/vmess/encoding/encoding_test.go @@ -34,7 +34,7 @@ func TestRequestSerialization(t *testing.T) { Command: protocol.RequestCommandTCP, Address: net.DomainAddress("www.v2ray.com"), Port: net.Port(443), - Security: protocol.Security(protocol.SecurityType_AES128_GCM), + Security: protocol.SecurityType_AES128_GCM, } buffer := buf.New() @@ -87,7 +87,7 @@ func TestInvalidRequest(t *testing.T) { Command: protocol.RequestCommand(100), Address: net.DomainAddress("www.v2ray.com"), Port: net.Port(443), - Security: protocol.Security(protocol.SecurityType_AES128_GCM), + Security: protocol.SecurityType_AES128_GCM, } buffer := buf.New() @@ -127,7 +127,7 @@ func TestMuxRequest(t *testing.T) { Version: 1, User: user, Command: protocol.RequestCommandMux, - Security: protocol.Security(protocol.SecurityType_AES128_GCM), + Security: protocol.SecurityType_AES128_GCM, } buffer := buf.New() diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index bd691db2d..d3f713c34 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -55,21 +55,16 @@ func (h *SessionHistory) Close() error { return h.task.Close() } -func (h *SessionHistory) add(session sessionId) { +func (h *SessionHistory) addIfNotExits(session sessionId) bool { h.Lock() defer h.Unlock() - h.cache[session] = time.Now().Add(time.Minute * 3) -} - -func (h *SessionHistory) has(session sessionId) bool { - h.RLock() - defer h.RUnlock() - - if expire, found := h.cache[session]; found { - return expire.After(time.Now()) + if expire, found := h.cache[session]; found && expire.After(time.Now()) { + return false } - return false + + h.cache[session] = time.Now().Add(time.Minute * 3) + return true } func (h *SessionHistory) removeExpiredEntries() { @@ -105,42 +100,16 @@ func NewServerSession(validator protocol.UserValidator, sessionHistory *SessionH } } -func readAddress(buffer *buf.Buffer, reader io.Reader) (net.Address, net.Port, error) { - var address net.Address - var port net.Port - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 3)); err != nil { - return address, port, newError("failed to read port and address type").Base(err) +func parseSecurityType(b byte) protocol.SecurityType { + if _, f := protocol.SecurityType_name[int32(b)]; f { + st := protocol.SecurityType(b) + // For backward compatibility. + if st == protocol.SecurityType_UNKNOWN { + st = protocol.SecurityType_LEGACY + } + return st } - port = net.PortFromBytes(buffer.BytesRange(-3, -1)) - - addressType := protocol.AddressType(buffer.Byte(buffer.Len() - 1)) - switch addressType { - case protocol.AddressTypeIPv4: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { - return address, port, newError("failed to read IPv4 address").Base(err) - } - address = net.IPAddress(buffer.BytesFrom(-4)) - case protocol.AddressTypeIPv6: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil { - return address, port, newError("failed to read IPv6 address").Base(err) - } - address = net.IPAddress(buffer.BytesFrom(-16)) - case protocol.AddressTypeDomain: - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil { - return address, port, newError("failed to read domain address").Base(err) - } - domainLength := int(buffer.Byte(buffer.Len() - 1)) - if domainLength == 0 { - return address, port, newError("zero length domain") - } - if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil { - return address, port, newError("failed to read domain address").Base(err) - } - address = net.DomainAddress(string(buffer.BytesFrom(-domainLength))) - default: - return address, port, newError("invalid address type", addressType) - } - return address, port, nil + return protocol.SecurityType_UNKNOWN } func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.RequestHeader, error) { @@ -183,44 +152,50 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request copy(sid.user[:], vmessAccount.ID.Bytes()) copy(sid.key[:], s.requestBodyKey) copy(sid.nonce[:], s.requestBodyIV) - if s.sessionHistory.has(sid) { + if !s.sessionHistory.addIfNotExits(sid) { return nil, newError("duplicated session id, possibly under replay attack") } - s.sessionHistory.add(sid) s.responseHeader = buffer.Byte(33) // 1 byte request.Option = bitmask.Byte(buffer.Byte(34)) // 1 byte padingLen := int(buffer.Byte(35) >> 4) - request.Security = protocol.NormSecurity(protocol.Security(buffer.Byte(35) & 0x0F)) + request.Security = parseSecurityType(buffer.Byte(35) & 0x0F) // 1 bytes reserved request.Command = protocol.RequestCommand(buffer.Byte(37)) - invalidRequest := false + var invalidRequestErr error + defer func() { + if invalidRequestErr != nil { + randomLen := dice.Roll(64) + 1 + // Read random number of bytes for prevent detection. + buffer.AppendSupplier(buf.ReadFullFrom(decryptor, int32(randomLen))) + } + }() + + if request.Security == protocol.SecurityType_UNKNOWN || request.Security == protocol.SecurityType_AUTO { + invalidRequestErr = newError("unknown security type") + return nil, invalidRequestErr + } + switch request.Command { case protocol.RequestCommandMux: request.Address = net.DomainAddress("v1.mux.cool") request.Port = 0 case protocol.RequestCommandTCP, protocol.RequestCommandUDP: - if addr, port, err := readAddress(buffer, decryptor); err == nil { + if addr, port, err := addrParser.ReadAddressPort(buffer, decryptor); err == nil { request.Address = addr request.Port = port } else { - invalidRequest = true - newError("failed to read address").Base(err).WriteToLog() + invalidRequestErr = newError("invalid address").Base(err) + return nil, invalidRequestErr } default: - invalidRequest = true - } - - if invalidRequest { - randomLen := dice.Roll(32) + 1 - // Read random number of bytes for prevent detection. - buffer.AppendSupplier(buf.ReadFullFrom(decryptor, randomLen)) - return nil, newError("invalid request") + invalidRequestErr = newError("invalid request command: ", request.Command) + return nil, invalidRequestErr } if padingLen > 0 { - if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, padingLen)); err != nil { + if err := buffer.AppendSupplier(buf.ReadFullFrom(decryptor, int32(padingLen))); err != nil { return nil, newError("failed to read padding").Base(err) } } @@ -250,7 +225,8 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.requestBodyIV) } - if request.Security.Is(protocol.SecurityType_NONE) { + switch request.Security { + case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamReader(sizeParser, reader) @@ -265,9 +241,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade } return buf.NewReader(reader) - } - - if request.Security.Is(protocol.SecurityType_LEGACY) { + case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey, s.requestBodyIV) cryptionReader := crypto.NewCryptionReader(aesStream, reader) if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -280,9 +254,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade } return buf.NewReader(cryptionReader) - } - - if request.Security.Is(protocol.SecurityType_AES128_GCM) { + case protocol.SecurityType_AES128_GCM: block, _ := aes.NewCipher(s.requestBodyKey) aead, _ := cipher.NewGCM(block) @@ -295,9 +267,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) - } - - if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey)) auth := &crypto.AEADAuthenticator{ @@ -309,9 +279,9 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + default: + panic("Unknown security type.") } - - panic("Unknown security type.") } func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) { @@ -336,7 +306,8 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.responseBodyIV) } - if request.Security.Is(protocol.SecurityType_NONE) { + switch request.Security { + case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { return crypto.NewChunkStreamWriter(sizeParser, writer) @@ -351,9 +322,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ } return buf.NewWriter(writer) - } - - if request.Security.Is(protocol.SecurityType_LEGACY) { + case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ AEAD: new(FnvAuthenticator), @@ -364,9 +333,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ } return buf.NewWriter(s.responseWriter) - } - - if request.Security.Is(protocol.SecurityType_AES128_GCM) { + case protocol.SecurityType_AES128_GCM: block, _ := aes.NewCipher(s.responseBodyKey) aead, _ := cipher.NewGCM(block) @@ -379,9 +346,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) - } - - if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey)) auth := &crypto.AEADAuthenticator{ @@ -393,7 +358,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + default: + panic("Unknown security type.") } - - panic("Unknown security type.") } diff --git a/proxy/vmess/inbound/config.pb.go b/proxy/vmess/inbound/config.pb.go index e89c65eda..467653d97 100644 --- a/proxy/vmess/inbound/config.pb.go +++ b/proxy/vmess/inbound/config.pb.go @@ -57,9 +57,10 @@ func (m *DefaultConfig) GetLevel() uint32 { } type Config struct { - User []*v2ray_core_common_protocol.User `protobuf:"bytes,1,rep,name=user" json:"user,omitempty"` - Default *DefaultConfig `protobuf:"bytes,2,opt,name=default" json:"default,omitempty"` - Detour *DetourConfig `protobuf:"bytes,3,opt,name=detour" json:"detour,omitempty"` + User []*v2ray_core_common_protocol.User `protobuf:"bytes,1,rep,name=user" json:"user,omitempty"` + Default *DefaultConfig `protobuf:"bytes,2,opt,name=default" json:"default,omitempty"` + Detour *DetourConfig `protobuf:"bytes,3,opt,name=detour" json:"detour,omitempty"` + SecureEncryptionOnly bool `protobuf:"varint,4,opt,name=secure_encryption_only,json=secureEncryptionOnly" json:"secure_encryption_only,omitempty"` } func (m *Config) Reset() { *m = Config{} } @@ -88,6 +89,13 @@ func (m *Config) GetDetour() *DetourConfig { return nil } +func (m *Config) GetSecureEncryptionOnly() bool { + if m != nil { + return m.SecureEncryptionOnly + } + return false +} + func init() { proto.RegisterType((*DetourConfig)(nil), "v2ray.core.proxy.vmess.inbound.DetourConfig") proto.RegisterType((*DefaultConfig)(nil), "v2ray.core.proxy.vmess.inbound.DefaultConfig") @@ -97,24 +105,26 @@ func init() { func init() { proto.RegisterFile("v2ray.com/core/proxy/vmess/inbound/config.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 297 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xcf, 0x4e, 0x83, 0x40, - 0x10, 0xc6, 0x03, 0xd5, 0xa2, 0x5b, 0xeb, 0x81, 0x78, 0x40, 0x0f, 0x84, 0x70, 0xaa, 0x89, 0xce, - 0x26, 0xe8, 0x03, 0x18, 0x4b, 0x62, 0x7a, 0x23, 0x9b, 0xd8, 0x83, 0x17, 0x43, 0x97, 0xad, 0x21, - 0x01, 0xa6, 0x59, 0x16, 0x22, 0xaf, 0xe4, 0xbb, 0xf8, 0x4e, 0xa6, 0x03, 0xc4, 0x3f, 0x07, 0x7b, - 0xdb, 0xd9, 0xfc, 0xbe, 0x6f, 0xbe, 0x6f, 0x18, 0x6f, 0x23, 0x9d, 0x76, 0x20, 0xb1, 0xe4, 0x12, - 0xb5, 0xe2, 0x3b, 0x8d, 0xef, 0x1d, 0x6f, 0x4b, 0x55, 0xd7, 0x3c, 0xaf, 0x36, 0xd8, 0x54, 0x19, - 0x97, 0x58, 0x6d, 0xf3, 0x37, 0xd8, 0x69, 0x34, 0xe8, 0xfa, 0xa3, 0x40, 0x2b, 0x20, 0x18, 0x08, - 0x86, 0x01, 0xbe, 0xba, 0xfe, 0x63, 0x28, 0xb1, 0x2c, 0xb1, 0xe2, 0x24, 0x96, 0x58, 0xf0, 0xa6, - 0x56, 0xba, 0xb7, 0x0a, 0x7d, 0x76, 0x16, 0x2b, 0x83, 0x8d, 0x5e, 0xd2, 0x02, 0xf7, 0x9c, 0xd9, - 0x06, 0x3d, 0x2b, 0xb0, 0x16, 0xa7, 0xc2, 0x36, 0x18, 0x3e, 0xb0, 0x79, 0xac, 0xb6, 0x69, 0x53, - 0x98, 0x01, 0xb8, 0x64, 0x27, 0x69, 0x61, 0x94, 0x7e, 0xcd, 0x33, 0xc2, 0xe6, 0xc2, 0xa1, 0x79, - 0x95, 0xb9, 0x17, 0xec, 0xb8, 0x50, 0xad, 0x2a, 0x3c, 0x9b, 0xfe, 0xfb, 0x21, 0xfc, 0xb4, 0xd8, - 0x74, 0xd0, 0xde, 0xb3, 0xa3, 0xfd, 0x6a, 0xcf, 0x0a, 0x26, 0x8b, 0x59, 0x14, 0xc0, 0x8f, 0x1a, - 0x7d, 0x44, 0x18, 0x23, 0xc2, 0x73, 0xad, 0xb4, 0x20, 0xda, 0x7d, 0x62, 0x4e, 0xd6, 0x47, 0x20, - 0xe3, 0x59, 0x74, 0x0b, 0xff, 0xf7, 0x87, 0x5f, 0x89, 0xc5, 0xa8, 0x76, 0x63, 0x36, 0xcd, 0xa8, - 0xab, 0x37, 0x21, 0x9f, 0x9b, 0xc3, 0x3e, 0xdf, 0x97, 0x11, 0x83, 0xf6, 0x31, 0x61, 0xa1, 0xc4, - 0xf2, 0x80, 0x34, 0xb1, 0x5e, 0x9c, 0xe1, 0xf9, 0x61, 0xfb, 0xeb, 0x48, 0xa4, 0x1d, 0x2c, 0xf7, - 0x6c, 0x42, 0xec, 0x9a, 0xd8, 0x55, 0x0f, 0x6c, 0xa6, 0xd4, 0xfa, 0xee, 0x2b, 0x00, 0x00, 0xff, - 0xff, 0x8d, 0x37, 0x9e, 0x90, 0x08, 0x02, 0x00, 0x00, + // 333 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0x4f, 0x4f, 0xf2, 0x40, + 0x10, 0xc6, 0xd3, 0xc2, 0x0b, 0xbc, 0x8b, 0x78, 0x68, 0x88, 0xa9, 0x1e, 0x48, 0xd3, 0x13, 0x26, + 0xba, 0x9b, 0x54, 0x3e, 0x80, 0x11, 0x8c, 0xe1, 0x24, 0x69, 0x22, 0x07, 0x2f, 0xa4, 0x6c, 0x07, + 0xd3, 0x64, 0xbb, 0x43, 0xb6, 0x5b, 0x62, 0xcf, 0x7e, 0x1b, 0x3f, 0xa5, 0x61, 0x5a, 0xfc, 0x77, + 0x90, 0xdb, 0xce, 0xce, 0xef, 0x79, 0xe6, 0x99, 0x61, 0x62, 0x17, 0x99, 0xa4, 0xe2, 0x12, 0x73, + 0x21, 0xd1, 0x80, 0xd8, 0x1a, 0x7c, 0xad, 0xc4, 0x2e, 0x87, 0xa2, 0x10, 0x99, 0x5e, 0x63, 0xa9, + 0x53, 0x21, 0x51, 0x6f, 0xb2, 0x17, 0xbe, 0x35, 0x68, 0xd1, 0x1b, 0x1d, 0x04, 0x06, 0x38, 0xc1, + 0x9c, 0x60, 0xde, 0xc0, 0x17, 0x97, 0xbf, 0x0c, 0x25, 0xe6, 0x39, 0x6a, 0x41, 0x62, 0x89, 0x4a, + 0x94, 0x05, 0x98, 0xda, 0x2a, 0x1c, 0xb1, 0x93, 0x19, 0x58, 0x2c, 0xcd, 0x94, 0x06, 0x78, 0xa7, + 0xcc, 0xb5, 0xe8, 0x3b, 0x81, 0x33, 0xfe, 0x1f, 0xbb, 0x16, 0xc3, 0x5b, 0x36, 0x98, 0xc1, 0x26, + 0x29, 0x95, 0x6d, 0x80, 0x73, 0xd6, 0x4b, 0x94, 0x05, 0xb3, 0xca, 0x52, 0xc2, 0x06, 0x71, 0x97, + 0xea, 0x79, 0xea, 0x0d, 0xd9, 0x3f, 0x05, 0x3b, 0x50, 0xbe, 0x4b, 0xff, 0x75, 0x11, 0xbe, 0xb9, + 0xac, 0xd3, 0x68, 0x27, 0xac, 0xbd, 0x1f, 0xed, 0x3b, 0x41, 0x6b, 0xdc, 0x8f, 0x02, 0xfe, 0x6d, + 0x8d, 0x3a, 0x22, 0x3f, 0x44, 0xe4, 0x4f, 0x05, 0x98, 0x98, 0x68, 0xef, 0x81, 0x75, 0xd3, 0x3a, + 0x02, 0x19, 0xf7, 0xa3, 0x6b, 0xfe, 0xf7, 0xfe, 0xfc, 0x47, 0xe2, 0xf8, 0xa0, 0xf6, 0x66, 0xac, + 0x93, 0xd2, 0xae, 0x7e, 0x8b, 0x7c, 0xae, 0x8e, 0xfb, 0x7c, 0x5d, 0x26, 0x6e, 0xb4, 0xde, 0x84, + 0x9d, 0x15, 0x20, 0x4b, 0x03, 0x2b, 0xd0, 0xd2, 0x54, 0x5b, 0x9b, 0xa1, 0x5e, 0xa1, 0x56, 0x95, + 0xdf, 0x0e, 0x9c, 0x71, 0x2f, 0x1e, 0xd6, 0xdd, 0xfb, 0xcf, 0xe6, 0xa3, 0x56, 0xd5, 0xdd, 0x82, + 0x85, 0x12, 0xf3, 0x23, 0x03, 0x17, 0xce, 0x73, 0xb7, 0x79, 0xbe, 0xbb, 0xa3, 0x65, 0x14, 0x27, + 0x15, 0x9f, 0xee, 0xd9, 0x05, 0xb1, 0x4b, 0x62, 0xe7, 0x35, 0xb0, 0xee, 0xd0, 0xad, 0x6e, 0x3e, + 0x02, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x49, 0x84, 0xe1, 0x3e, 0x02, 0x00, 0x00, } diff --git a/proxy/vmess/inbound/config.proto b/proxy/vmess/inbound/config.proto index fa69a2033..63caeff81 100644 --- a/proxy/vmess/inbound/config.proto +++ b/proxy/vmess/inbound/config.proto @@ -21,4 +21,5 @@ message Config { repeated v2ray.core.common.protocol.User user = 1; DefaultConfig default = 2; DetourConfig detour = 3; -} \ No newline at end of file + bool secure_encryption_only = 4; +} diff --git a/proxy/vmess/inbound/inbound.go b/proxy/vmess/inbound/inbound.go index 145a60ba2..dafe2bb43 100644 --- a/proxy/vmess/inbound/inbound.go +++ b/proxy/vmess/inbound/inbound.go @@ -101,15 +101,12 @@ type Handler struct { usersByEmail *userByEmail detours *DetourConfig sessionHistory *encoding.SessionHistory + secure bool } // New creates a new VMess inbound handler. func New(ctx context.Context, config *Config) (*Handler, error) { - v := core.FromContext(ctx) - if v == nil { - return nil, newError("V is not in context.") - } - + v := core.MustFromContext(ctx) handler := &Handler{ policyManager: v.PolicyManager(), inboundHandlerManager: v.InboundHandlerManager(), @@ -117,6 +114,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) { detours: config.Detour, usersByEmail: newUserByEmail(config.GetDefaultValue()), sessionHistory: encoding.NewSessionHistory(), + secure: config.SecureEncryptionOnly, } for _, user := range config.User { @@ -184,16 +182,17 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess bodyWriter := session.EncodeResponseBody(request, output) - // Optimize for small response packet - data, err := input.ReadMultiBuffer() - if err != nil { - return err - } + { + // Optimize for small response packet + data, err := input.ReadMultiBuffer() + if err != nil { + return err + } - if err := bodyWriter.WriteMultiBuffer(data); err != nil { - return err + if err := bodyWriter.WriteMultiBuffer(data); err != nil { + return err + } } - data.Release() if bufferedWriter, ok := output.(*buf.BufferedWriter); ok { if err := bufferedWriter.SetBuffered(false); err != nil { @@ -214,6 +213,10 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess return nil } +func isInecureEncryption(s protocol.SecurityType) bool { + return s == protocol.SecurityType_NONE || s == protocol.SecurityType_LEGACY || s == protocol.SecurityType_UNKNOWN +} + // Process implements proxy.Inbound.Process(). func (h *Handler) Process(ctx context.Context, network net.Network, connection internet.Connection, dispatcher core.Dispatcher) error { sessionPolicy := h.policyManager.ForLevel(0) @@ -239,17 +242,29 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection i return err } - log.Record(&log.AccessMessage{ - From: connection.RemoteAddr(), - To: request.Destination(), - Status: log.AccessAccepted, - Reason: "", - }) + if h.secure && isInecureEncryption(request.Security) { + log.Record(&log.AccessMessage{ + From: connection.RemoteAddr(), + To: "", + Status: log.AccessRejected, + Reason: "Insecure encryption", + }) + return newError("client is using insecure encryption: ", request.Security) + } - newError("received request for ", request.Destination()).WriteToLog() + if request.Command != protocol.RequestCommandMux { + log.Record(&log.AccessMessage{ + From: connection.RemoteAddr(), + To: request.Destination(), + Status: log.AccessAccepted, + Reason: "", + }) + } + + newError("received request for ", request.Destination()).WithContext(ctx).WriteToLog() if err := connection.SetReadDeadline(time.Time{}); err != nil { - newError("unable to set back read deadline").Base(err).WriteToLog() + newError("unable to set back read deadline").Base(err).WithContext(ctx).WriteToLog() } sessionPolicy = h.policyManager.ForLevel(request.User.Level) @@ -296,7 +311,7 @@ func (h *Handler) generateCommand(ctx context.Context, request *protocol.Request if h.inboundHandlerManager != nil { handler, err := h.inboundHandlerManager.GetHandler(ctx, tag) if err != nil { - newError("failed to get detour handler: ", tag).Base(err).AtWarning().WriteToLog() + newError("failed to get detour handler: ", tag).Base(err).AtWarning().WithContext(ctx).WriteToLog() return nil } proxyHandler, port, availableMin := handler.GetRandomInboundProxy() @@ -306,7 +321,7 @@ func (h *Handler) generateCommand(ctx context.Context, request *protocol.Request availableMin = 255 } - newError("pick detour handler for port ", port, " for ", availableMin, " minutes.").AtDebug().WriteToLog() + newError("pick detour handler for port ", port, " for ", availableMin, " minutes.").AtDebug().WithContext(ctx).WriteToLog() user := inboundHandler.GetUser(request.User.Email) if user == nil { return nil diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index c2070aed3..a59904d39 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -35,11 +35,7 @@ func New(ctx context.Context, config *Config) (*Handler, error) { handler := &Handler{ serverList: serverList, serverPicker: protocol.NewRoundRobinServerPicker(serverList), - v: core.FromContext(ctx), - } - - if handler.v == nil { - return nil, newError("V is not in context.") + v: core.MustFromContext(ctx), } return handler, nil @@ -69,7 +65,7 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial if !ok { return newError("target not specified").AtError() } - newError("tunneling request to ", target, " via ", rec.Destination()).WriteToLog() + newError("tunneling request to ", target, " via ", rec.Destination()).WithContext(ctx).WriteToLog() command := protocol.RequestCommandTCP if target.Network == net.Network_UDP { @@ -95,7 +91,7 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial account := rawAccount.(*vmess.InternalAccount) request.Security = account.Security - if request.Security.Is(protocol.SecurityType_AES128_GCM) || request.Security.Is(protocol.SecurityType_NONE) || request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + if request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 { request.Option.Set(protocol.RequestOptionChunkMasking) } @@ -109,21 +105,24 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) requestDone := signal.ExecuteAsync(func() error { + defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + writer := buf.NewBufferedWriter(buf.NewWriter(conn)) if err := session.EncodeRequestHeader(request, writer); err != nil { return newError("failed to encode request").Base(err).AtWarning() } bodyWriter := session.EncodeRequestBody(request, writer) - firstPayload, err := input.ReadTimeout(time.Millisecond * 500) - if err != nil && err != buf.ErrReadTimeout { - return newError("failed to get first payload").Base(err) - } - if !firstPayload.IsEmpty() { - if err := bodyWriter.WriteMultiBuffer(firstPayload); err != nil { - return newError("failed to write first payload").Base(err) + { + firstPayload, err := input.ReadTimeout(time.Millisecond * 500) + if err != nil && err != buf.ErrReadTimeout { + return newError("failed to get first payload").Base(err) + } + if !firstPayload.IsEmpty() { + if err := bodyWriter.WriteMultiBuffer(firstPayload); err != nil { + return newError("failed to write first payload").Base(err) + } } - firstPayload.Release() } if err := writer.SetBuffered(false); err != nil { @@ -139,23 +138,23 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial return err } } - timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) + return nil }) responseDone := signal.ExecuteAsync(func() error { - defer output.Close() defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly) reader := buf.NewBufferedReader(buf.NewReader(conn)) header, err := session.DecodeResponseHeader(reader) if err != nil { - return err + return newError("failed to read header").Base(err) } v.handleCommand(rec.Destination(), header.Command) reader.SetBuffered(false) bodyReader := session.DecodeResponseBody(request, reader) + return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) }) diff --git a/proxy/vmess/vmess.go b/proxy/vmess/vmess.go index 52ead21f2..6d5510ade 100644 --- a/proxy/vmess/vmess.go +++ b/proxy/vmess/vmess.go @@ -159,7 +159,7 @@ func (v *TimedUserValidator) Remove(email string) bool { return false } ulen := len(v.users) - if idx < len(v.users) { + if idx < ulen { v.users[idx] = v.users[ulen-1] v.users[ulen-1] = nil v.users = v.users[:ulen-1] diff --git a/release/config/geoip.dat b/release/config/geoip.dat index 30544ba8d..999c06b78 100755 Binary files a/release/config/geoip.dat and b/release/config/geoip.dat differ diff --git a/release/config/systemv/v2ray b/release/config/systemv/v2ray old mode 100644 new mode 100755 diff --git a/release/install-release.sh b/release/install-release.sh index 0c0c71f4f..0d0d6958e 100755 --- a/release/install-release.sh +++ b/release/install-release.sh @@ -149,7 +149,7 @@ extract(){ mkdir -p /tmp/v2ray unzip $1 -d "/tmp/v2ray/" if [[ $? -ne 0 ]]; then - colorEcho ${RED} "Extracting V2Ray faile!" + colorEcho ${RED} "Extracting V2Ray failed!" exit fi return 0 @@ -206,7 +206,7 @@ startV2ray(){ copyFile() { NAME=$1 MANDATE=$2 - ERROR=`cp "/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}/${NAME}" "/usr/bin/v2ray/${NAME}"` + ERROR=`cp "/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}/${NAME}" "/usr/bin/v2ray/${NAME}" 2>&1` if [[ $? -ne 0 ]]; then colorEcho ${YELLOW} "${ERROR}" if [ "$MANDATE" = true ]; then @@ -251,7 +251,7 @@ installV2Ray(){ } -installInitScrip(){ +installInitScript(){ SYSTEMCTL_CMD=$(command -v systemctl) SERVICE_CMD=$(command -v service) @@ -371,11 +371,11 @@ main(){ NEW_VER=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f2` fi else - # dowload via network and extract + # download via network and extract installSoftware "curl" getVersion if [[ $? == 0 ]] && [[ "$FORCE" != "1" ]]; then - colorEcho ${GREEN} "Lastest version ${NEW_VER} is already installed." + colorEcho ${GREEN} "Latest version ${NEW_VER} is already installed." exit else colorEcho ${BLUE} "Installing V2Ray ${NEW_VER} on ${ARCH}" @@ -389,7 +389,7 @@ main(){ stopV2ray fi installV2Ray - installInitScrip + installInitScript if [[ ${V2RAY_RUNNING} -eq 1 ]];then colorEcho ${BLUE} "Restarting V2Ray service." startV2ray diff --git a/release/install.sh b/release/install.sh index 7922373ef..764e764c7 100755 --- a/release/install.sh +++ b/release/install.sh @@ -1,7 +1,7 @@ #!/bin/bash -GO_AMD64=https://storage.googleapis.com/golang/go1.9.4.linux-amd64.tar.gz -GO_X86=https://storage.googleapis.com/golang/go1.9.4.linux-386.tar.gz +GO_AMD64=https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz +GO_X86=https://storage.googleapis.com/golang/go1.10.linux-386.tar.gz ARCH=$(uname -m) GO_CUR=${GO_AMD64} diff --git a/release/make-release.sh b/release/make-release.sh index 387e1bdcf..d2caa0ba8 100755 --- a/release/make-release.sh +++ b/release/make-release.sh @@ -27,7 +27,7 @@ pushd $GOPATH/src/v2ray.com/core echo "Adding a new tag: " "v$VER" git tag -s -a "v$VER" -m "Version ${VER}" sed -i '' "s/\(version *= *\"\).*\(\"\)/\1$VERN\2/g" core.go -echo "Commiting core.go (may not necessary)" +echo "Committing core.go (may not necessary)" git commit core.go -S -m "Update version" echo "Pushing changes" git push --follow-tags diff --git a/release/release-ci.sh b/release/release-ci.sh index 51b752211..40be7c73e 100755 --- a/release/release-ci.sh +++ b/release/release-ci.sh @@ -25,7 +25,7 @@ echo ${SIGN_KEY_PASS} | gpg --passphrase-fd 0 --batch --import /v2ray/build/sign curl -L -o /v2ray/build/releases https://api.github.com/repos/v2ray/v2ray-core/releases GO_INSTALL=golang.tar.gz -curl -L -o ${GO_INSTALL} https://storage.googleapis.com/golang/go1.9.4.linux-amd64.tar.gz +curl -L -o ${GO_INSTALL} https://storage.googleapis.com/golang/go1.10.1.linux-amd64.tar.gz tar -C /usr/local -xzf ${GO_INSTALL} export PATH=$PATH:/usr/local/go/bin diff --git a/release/verify/official_release.asc b/release/verify/official_release.asc index 2f25b8da1..7a6f5253a 100644 --- a/release/verify/official_release.asc +++ b/release/verify/official_release.asc @@ -12,41 +12,41 @@ QbiKX3UjBdVRIFlr4sL/JvOpLKr1RaEQS3nJ2m/Xuki1AOeKLoX8ebPca34tyXj0 O1onRVaq2bsKPX1Zv9ZC7jZIAMV2pC26UmRc7nJ/xdFj3tafA5hvILUifpO1qdlX iOCK+biPU3T9c6FakNiQ0sXAqhHbKaJNYcjDF3H3QIs1a35P7kfUJ+9Nc1WoCFGV Gh94dVLMGuoh+qo0A0qCg/y0/gGeZQ7G3jT5NXFx6UjlAb42R/dP+VSg6QARAQAB -tCVPZmZpY2lhbCBSZWxlYXNlIDxvZmZpY2lhbEB2MnJheS5jb20+iQI9BBMBCgAn -BQJYrhS3AhsDBQkB4TOABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEOGvpVDH -08Sa1nQP/iCIo3L3HKbi384XXLrnhyLMqa42qxYp8LzX2YeTnXeSW8zYqJyyadt7 -CQ3+tsV9Pg0lEPYtFsMT/hQ+Us0T3FBLRNZ+F32T3+vnDyiboI21kvLPH7MEZG8G -CGfxBMCu8A0/heRM8l7Ue5d9z0ESAaqconyPn5IJ/0vH1d6d3x6HHo2FoNSIN/yD -eYVDr2PxTnLzpbjcumBsn20oRktHJ4SGOsfdNtW4E16RwTVrnHhTBPt/XhtVp44f -dW7oJCQK0LekzgTsavmbZoruu+MmUwEqybutJaWE9MyfNe5IXU0h49lo76bhKO12 -mNuHeMBDBsApZQYqlj8iJkzfk11sOBA71W3mPjBH7u85vQIu+fg9aliv2k9d+2o6 -4Hp76EBN+yehGNsGFV4MLIB4gfxv2U99hQ9nNsw7dLz6iDSPRIbe85suQDz7llIT -77kY08nS4PHW0z9lKLF9zp6Ls5a19hfpCDFtR5P5agC/ybsvrxdZKqOMv2HiLpLu -KSamVN+0X0nR2Cc77laE62EiqTb5PGo0Di6aOxzHmh54PUCUesiQQdAKUi8mmszF -d9ODdMcCuOrQaiQ9+D//oDGf3g7+5wo3eHTleBn4FXDCH8eH178FK7DMk6fK2oFa -DnB/5yPcRqiAvsbrrz28hyKU/d/gh5WHLBsB4YDktP+5mdUYMAY5uQINBFiuFLcB -EAC/tdbZz2lpZ21Y+uI9UIpHftoGUUe1xXzcdURxx6+H8sZl1LXmRUvy+0ByTA1G -JlXusoqunL2n7soKmlHa6fBS6mqRma7J44x+IvodXJ2QYjuLot5gP6GkDPxVY0A0 -NSwXi5VcRg2IY/5pZg32DiLHdMMrNAJplsrT7MdMV523fZZCaSLX4pQEfqe62x3P -u+eWSCFzwjh9W13yc/sdMmn4u04EIBAvzGuEnl1UqcoCmpmcWG3U6Vd+eRWuBRwg -HCvL9/i54ROnP+B15xorggDty5tWYYE9xMa/E45VqnVBSYjJggH/cfFInQaGulBs -lv8iOeQmx9+X+ad04KtJOEo6vDq8AuqiZlhlr9B0wrPZttYnt8gymZY1X2foF0pG -mPOCW3GG5gBFx+NmP3xEHZIccVRCx7ek5NW2L6VtTYEaKcPVP5lfJhEXNB+lafiq -FlOLl4hyud9mEeJFJr1oeVokjxbv3urefv4llcF1c76w1se+nCrsntT4BfwpoSi3 -NSWnCYGhLcbl71VyOHq/mky/x24iEnc33LWGHRFUmJZIyXVd/99B7LqXbFf2Oztg -4hoZbBmuaSl1S5gL7x83Q33NoiiG0UkVkZB54u7sP6/2WO/I+cgccI2ZKKaNwOTS -YQcgTpzYfWWlhwa3NWt1krCLRgJ+TkR1pENSiWO0gxXF7wARAQABiQIlBBgBCgAP -BQJYrhS3AhsMBQkB4TOAAAoJEOGvpVDH08Sa0QgP/3JaTcSWoZT6hLHcwsmBBnl7 -yNXXGb1JhIPjZup9aIUVcNZaj/iW0t9TeJQcgPd0dkuUFYzHa/Sq2p1ywuBfmwiW -pOcG5Oz82vY6no3+X/Z0qsHPjNxJDCNkstlJaJA8CvJt8jLQn970Q4n7zBl6XVFb -Fq3mL4WOaMGX4GA9To8uraGKhN9RxiRATM/pxhipAB1SjmW0AGPV+sgMRmLqFdWd -r3XbuzieCDHaguJsW53ZiobHxr6LTVYBU1kVvQ7Cj/iJuFqzaXBe4cWRSjTJcN0W -766xZAlWLnoy1GPcRG+e4Ki09gD6NC5rV137yVQd19rjnpbvzAJ5wDZj4OBI/Cew -/0Pl199yBlwq1V6NAlRD7HBUqp+jveDYvCDaSAhOhtRFv8vomVnJo8efQJymHhWu -NMYK/+02GZqLhgV3wTYbbIIRZWWOx8O2Q06J9VsXL9eAz4oIiLWJO1pDosj0y6QJ -aFkUs7OxgxYtjEGhZ666yd3qdMByrmmmJSiYdPEHaWpTUoxooGum6DdTB+RZCNjW -AMhsEBr73DHvkwxCuNynB+ALLImTbbl88X5F5KLHu+/Jsxs5Oawa9IxrSgMVdtef -QmiNis0frjeycM6B60UYVbvzlnGi/+TCqjlFIoXNtmff/Bpyh9YLHAYaCBfnKqjn -80yAOiqyRyAtruVaY72n -=CVru ------END PGP PUBLIC KEY BLOCK----- +tCVPZmZpY2lhbCBSZWxlYXNlIDxvZmZpY2lhbEB2MnJheS5jb20+iQJUBBMBCgA+ +AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAFiEEiwxeMlNgMveaPc7Z4a+lUMfT +xJoFAlqRYBMFCQPF0FwACgkQ4a+lUMfTxJoymBAAnyqLfEdmP0ulki3uCQvIH4JD +OXvFRyTLYweLehGqZ63i7yy0c1BzOsQbmQy2Trl2uiCgjOLmA6LdFB2d3rhsFssK +fhFGroqCOHPdG7thSnBu9C0ohWdoiE1hfXVUtRn0P2vfqswNMdxwNwlZiRhWJemw +1WmlaSXRp3PznC1eCYwUaS5IT18rzJyuk8z/Scb9DEWQwPhypz+NTE3j7qvQFmdP +0cEDGUUXVe3HQ7oHlC+hzL79KttJeEMl575YbuLtAeRSJC0M+IgXd8YKuoORhqFM +OwW4CNVMnAiF6mmb2Wf1hM+A9ydWVd3rz7sp3k1n4i5Hl4ftEz2cdicTX1JBG4ZB +wsa9pfC5jk+negIQVvHPQRtWc/2bNYxNBF2cIpKF9wQ00E/wP64vl5QwJzs58Fqc +cl3AwfskfvzeLSpdKlOCLE8FSQiKQ/NNw9fAuAe7YxW9xSKRTFGx8yQCNd11fmFe +iMCDsBE9I51yUy8ywEtnedHi6mxMrnLv24VkD7jQZBWlvMDUEhGy2f6KgrSHTdEJ +ZchSxfEIaM9Thy1E/3f6dQVkiPsf+/4wikS6sCdyW+ITVYc6yE5MvRz5oDjQH4z5 +JoELeuNxR59kpBErgdr8DBHSJNuxIT63QynrglwsG319Dzu5uPUC6WfqUGG9ATJ0 +neWkINHrf53bVk2rUG65Ag0EWK4UtwEQAL+11tnPaWlnbVj64j1Qikd+2gZRR7XF +fNx1RHHHr4fyxmXUteZFS/L7QHJMDUYmVe6yiq6cvafuygqaUdrp8FLqapGZrsnj +jH4i+h1cnZBiO4ui3mA/oaQM/FVjQDQ1LBeLlVxGDYhj/mlmDfYOIsd0wys0AmmW +ytPsx0xXnbd9lkJpItfilAR+p7rbHc+755ZIIXPCOH1bXfJz+x0yafi7TgQgEC/M +a4SeXVSpygKamZxYbdTpV355Fa4FHCAcK8v3+LnhE6c/4HXnGiuCAO3Lm1ZhgT3E +xr8TjlWqdUFJiMmCAf9x8UidBoa6UGyW/yI55CbH35f5p3Tgq0k4Sjq8OrwC6qJm +WGWv0HTCs9m21ie3yDKZljVfZ+gXSkaY84JbcYbmAEXH42Y/fEQdkhxxVELHt6Tk +1bYvpW1NgRopw9U/mV8mERc0H6Vp+KoWU4uXiHK532YR4kUmvWh5WiSPFu/e6t5+ +/iWVwXVzvrDWx76cKuye1PgF/CmhKLc1JacJgaEtxuXvVXI4er+aTL/HbiISdzfc +tYYdEVSYlkjJdV3/30HsupdsV/Y7O2DiGhlsGa5pKXVLmAvvHzdDfc2iKIbRSRWR +kHni7uw/r/ZY78j5yBxwjZkopo3A5NJhByBOnNh9ZaWHBrc1a3WSsItGAn5ORHWk +Q1KJY7SDFcXvABEBAAGJAiUEGAEKAA8FAliuFLcCGwwFCQHhM4AACgkQ4a+lUMfT +xJrRCA//clpNxJahlPqEsdzCyYEGeXvI1dcZvUmEg+Nm6n1ohRVw1lqP+JbS31N4 +lByA93R2S5QVjMdr9KranXLC4F+bCJak5wbk7Pza9jqejf5f9nSqwc+M3EkMI2Sy +2UlokDwK8m3yMtCf3vRDifvMGXpdUVsWreYvhY5owZfgYD1Ojy6toYqE31HGJEBM +z+nGGKkAHVKOZbQAY9X6yAxGYuoV1Z2vddu7OJ4IMdqC4mxbndmKhsfGvotNVgFT +WRW9DsKP+Im4WrNpcF7hxZFKNMlw3RbvrrFkCVYuejLUY9xEb57gqLT2APo0LmtX +XfvJVB3X2uOelu/MAnnANmPg4Ej8J7D/Q+XX33IGXCrVXo0CVEPscFSqn6O94Ni8 +INpICE6G1EW/y+iZWcmjx59AnKYeFa40xgr/7TYZmouGBXfBNhtsghFlZY7Hw7ZD +Ton1Wxcv14DPigiItYk7WkOiyPTLpAloWRSzs7GDFi2MQaFnrrrJ3ep0wHKuaaYl +KJh08QdpalNSjGiga6boN1MH5FkI2NYAyGwQGvvcMe+TDEK43KcH4AssiZNtuXzx +fkXkose778mzGzk5rBr0jGtKAxV2159CaI2KzR+uN7JwzoHrRRhVu/OWcaL/5MKq +OUUihc22Z9/8GnKH1gscBhoIF+cqqOfzTIA6KrJHIC2u5Vpjvac= +=xv/V +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/router.go b/router.go index d3a8031f0..4ea16cd4f 100644 --- a/router.go +++ b/router.go @@ -54,9 +54,14 @@ func (d *syncDispatcher) Close() error { } func (d *syncDispatcher) Set(disp Dispatcher) { + if disp == nil { + return + } + d.Lock() defer d.Unlock() + common.Close(d.Dispatcher) d.Dispatcher = disp } @@ -65,7 +70,7 @@ var ( ErrNoClue = errors.New("not enough information for making a decision") ) -// Router is a feature to choose a outbound tag for the given request. +// Router is a feature to choose an outbound tag for the given request. type Router interface { Feature @@ -108,8 +113,13 @@ func (r *syncRouter) Close() error { } func (r *syncRouter) Set(router Router) { + if router == nil { + return + } + r.Lock() defer r.Unlock() + common.Close(r.Router) r.Router = router } diff --git a/stats.go b/stats.go new file mode 100644 index 000000000..17bab8757 --- /dev/null +++ b/stats.go @@ -0,0 +1,77 @@ +package core + +import ( + "sync" +) + +type StatCounter interface { + Value() int64 + Set(int64) int64 + Add(int64) int64 +} + +type StatManager interface { + Feature + + RegisterCounter(string) (StatCounter, error) + GetCounter(string) StatCounter +} + +type syncStatManager struct { + sync.RWMutex + StatManager +} + +func (s *syncStatManager) Start() error { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil + } + + return s.StatManager.Start() +} + +func (s *syncStatManager) Close() error { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil + } + return s.StatManager.Close() +} + +func (s *syncStatManager) RegisterCounter(name string) (StatCounter, error) { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil, newError("StatManager not set.") + } + return s.StatManager.RegisterCounter(name) +} + +func (s *syncStatManager) GetCounter(name string) StatCounter { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil + } + return s.StatManager.GetCounter(name) +} + +func (s *syncStatManager) Set(m StatManager) { + if m == nil { + return + } + s.Lock() + defer s.Unlock() + + if s.StatManager != nil { + s.StatManager.Close() + } + s.StatManager = m +} diff --git a/testing/coverage/coverall b/testing/coverage/coverall index e6e85800e..9db519d06 100755 --- a/testing/coverage/coverall +++ b/testing/coverage/coverall @@ -45,7 +45,8 @@ cat ${COVERAGE_FILE} | sort -t: -k1 | grep -vw "testing" | grep -v ".pb.go" | gr echo "mode: set" | cat - ${COV_SORTED} > ${COVERAGE_FILE} if [ "$FAIL" -eq 0 ]; then - bash <(curl -s https://codecov.io/bash) -f ${COVERAGE_FILE} || echo "Codecov did not collect coverage reports." + echo "Uploading coverage datea to codecov." + bash <(curl -s https://codecov.io/bash) -f ${COVERAGE_FILE} -v || echo "Codecov did not collect coverage reports." fi exit $FAIL diff --git a/testing/scenarios/command_test.go b/testing/scenarios/command_test.go index 27165a368..67ef8d07e 100644 --- a/testing/scenarios/command_test.go +++ b/testing/scenarios/command_test.go @@ -2,19 +2,31 @@ package scenarios import ( "context" + "crypto/rand" "fmt" + "io" "testing" + "time" "google.golang.org/grpc" + "v2ray.com/core" "v2ray.com/core/app/commander" + "v2ray.com/core/app/policy" "v2ray.com/core/app/proxyman" "v2ray.com/core/app/proxyman/command" "v2ray.com/core/app/router" + "v2ray.com/core/app/stats" + statscmd "v2ray.com/core/app/stats/command" "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" + "v2ray.com/core/common/uuid" "v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/freedom" + "v2ray.com/core/proxy/vmess" + "v2ray.com/core/proxy/vmess/inbound" + "v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/testing/servers/tcp" . "v2ray.com/ext/assert" ) @@ -29,8 +41,8 @@ func TestCommanderRemoveHandler(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - clientPort := pickPort() - cmdPort := pickPort() + clientPort := tcp.PickPort() + cmdPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&commander.Config{ @@ -108,7 +120,7 @@ func TestCommanderRemoveHandler(t *testing.T) { assert(conn.Close(), IsNil) } - cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure()) + cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) assert(err, IsNil) hsClient := command.NewHandlerServiceClient(cmdConn) @@ -128,3 +140,386 @@ func TestCommanderRemoveHandler(t *testing.T) { CloseAllServers(servers) } + +func TestCommanderAddRemoveUser(t *testing.T) { + assert := With(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert(err, IsNil) + defer tcpServer.Close() + + u1 := protocol.NewID(uuid.New()) + u2 := protocol.NewID(uuid.New()) + + cmdPort := tcp.PickPort() + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&commander.Config{ + Tag: "api", + Service: []*serial.TypedMessage{ + serial.ToTypedMessage(&command.Config{}), + }, + }), + serial.ToTypedMessage(&router.Config{ + Rule: []*router.RoutingRule{ + { + InboundTag: []string{"api"}, + Tag: "api", + }, + }, + }), + serial.ToTypedMessage(&policy.Config{ + Level: map[uint32]*policy.Policy{ + 0: { + Timeout: &policy.Policy_Timeout{ + UplinkOnly: &policy.Second{Value: 0}, + DownlinkOnly: &policy.Second{Value: 0}, + }, + }, + }, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + Tag: "v", + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: u1.String(), + AlterId: 64, + }), + }, + }, + }), + }, + { + Tag: "api", + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(cmdPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&policy.Config{ + Level: map[uint32]*policy.Policy{ + 0: { + Timeout: &policy.Policy_Timeout{ + UplinkOnly: &policy.Second{Value: 0}, + DownlinkOnly: &policy.Second{Value: 0}, + }, + }, + }, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + Tag: "d", + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: u2.String(), + AlterId: 64, + SecuritySettings: &protocol.SecurityConfig{ + Type: protocol.SecurityType_AES128_GCM, + }, + }), + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + assert(err, IsNil) + + { + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + payload := "commander request." + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert(nBytes, Equals, 0) + assert(err, Equals, io.EOF) + assert(conn.Close(), IsNil) + } + + cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) + assert(err, IsNil) + + hsClient := command.NewHandlerServiceClient(cmdConn) + resp, err := hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{ + Tag: "v", + Operation: serial.ToTypedMessage( + &command.AddUserOperation{ + User: &protocol.User{ + Email: "test@v2ray.com", + Account: serial.ToTypedMessage(&vmess.Account{ + Id: u2.String(), + AlterId: 64, + }), + }, + }), + }) + assert(err, IsNil) + assert(resp, IsNotNil) + + { + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + payload := "commander request." + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert(err, IsNil) + assert(response[:nBytes], Equals, xor([]byte(payload))) + assert(conn.Close(), IsNil) + } + + resp, err = hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{ + Tag: "v", + Operation: serial.ToTypedMessage(&command.RemoveUserOperation{Email: "test@v2ray.com"}), + }) + assert(resp, IsNotNil) + assert(err, IsNil) + + CloseAllServers(servers) +} + +func TestCommanderStats(t *testing.T) { + assert := With(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert(err, IsNil) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + cmdPort := tcp.PickPort() + + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&stats.Config{}), + serial.ToTypedMessage(&commander.Config{ + Tag: "api", + Service: []*serial.TypedMessage{ + serial.ToTypedMessage(&statscmd.Config{}), + }, + }), + serial.ToTypedMessage(&router.Config{ + Rule: []*router.RoutingRule{ + { + InboundTag: []string{"api"}, + Tag: "api", + }, + }, + }), + serial.ToTypedMessage(&policy.Config{ + Level: map[uint32]*policy.Policy{ + 0: { + Timeout: &policy.Policy_Timeout{ + UplinkOnly: &policy.Second{Value: 0}, + DownlinkOnly: &policy.Second{Value: 0}, + }, + }, + 1: { + Stats: &policy.Policy_Stats{ + UserUplink: true, + UserDownlink: true, + }, + }, + }, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Level: 1, + Email: "test", + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + }), + }, + }, + }), + }, + { + Tag: "api", + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(cmdPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + SecuritySettings: &protocol.SecurityConfig{ + Type: protocol.SecurityType_AES128_GCM, + }, + }), + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + assert(err, IsNil) + + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + payload := make([]byte, 10240*1024) + rand.Read(payload) + + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := readFrom(conn, time.Second*20, 10240*1024) + assert(response, Equals, xor([]byte(payload))) + assert(conn.Close(), IsNil) + + cmdConn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", cmdPort), grpc.WithInsecure(), grpc.WithBlock()) + assert(err, IsNil) + + const name = "user>>>test>>>traffic>>>uplink" + sClient := statscmd.NewStatsServiceClient(cmdConn) + + sresp, err := sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{ + Name: name, + Reset_: true, + }) + assert(err, IsNil) + assert(sresp.Stat.Name, Equals, name) + assert(sresp.Stat.Value, Equals, int64(10240*1024)) + + sresp, err = sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{ + Name: name, + }) + assert(err, IsNil) + assert(sresp.Stat.Name, Equals, name) + assert(sresp.Stat.Value, Equals, int64(0)) + + CloseAllServers(servers) +} diff --git a/testing/scenarios/common.go b/testing/scenarios/common.go index ac6155bb8..bfc8c03c2 100644 --- a/testing/scenarios/common.go +++ b/testing/scenarios/common.go @@ -22,15 +22,6 @@ import ( "v2ray.com/core/common/serial" ) -func pickPort() net.Port { - listener, err := net.Listen("tcp4", "127.0.0.1:0") - common.Must(err) - defer listener.Close() - - addr := listener.Addr().(*net.TCPAddr) - return net.Port(addr.Port) -} - func xor(b []byte) []byte { r := make([]byte, len(b)) for i, v := range b { diff --git a/testing/scenarios/common_coverage.go b/testing/scenarios/common_coverage.go index c29aceeae..6b14a3682 100644 --- a/testing/scenarios/common_coverage.go +++ b/testing/scenarios/common_coverage.go @@ -17,7 +17,7 @@ func BuildV2Ray() error { return nil } - cmd := exec.Command("go", "test", "-tags", "json coverage coveragemain", "-coverpkg", "v2ray.com/core/...", "-c", "-o", testBinaryPath, GetSourcePath()) + cmd := exec.Command("go", "test", "-tags", "coverage coveragemain", "-coverpkg", "v2ray.com/core/...", "-c", "-o", testBinaryPath, GetSourcePath()) return cmd.Run() } @@ -26,7 +26,8 @@ func RunV2RayProtobuf(config []byte) *exec.Cmd { covDir := filepath.Join(os.Getenv("GOPATH"), "out", "v2ray", "cov") os.MkdirAll(covDir, os.ModeDir) - profile := uuid.New().String() + ".out" + randomID := uuid.New() + profile := randomID.String() + ".out" proc := exec.Command(testBinaryPath, "-config=stdin:", "-format=pb", "-test.run", "TestRunMainForCoverage", "-test.coverprofile", profile, "-test.outputdir", covDir) proc.Stdin = bytes.NewBuffer(config) proc.Stderr = os.Stderr diff --git a/testing/scenarios/common_regular.go b/testing/scenarios/common_regular.go index 6c5662d82..fc3d29970 100644 --- a/testing/scenarios/common_regular.go +++ b/testing/scenarios/common_regular.go @@ -16,7 +16,7 @@ func BuildV2Ray() error { } fmt.Printf("Building V2Ray into path (%s)\n", testBinaryPath) - cmd := exec.Command("go", "build", "-tags=json", "-o="+testBinaryPath, GetSourcePath()) + cmd := exec.Command("go", "build", "-o="+testBinaryPath, GetSourcePath()) return cmd.Run() } diff --git a/testing/scenarios/dns_test.go b/testing/scenarios/dns_test.go index 5f168211d..bb9e92ac7 100644 --- a/testing/scenarios/dns_test.go +++ b/testing/scenarios/dns_test.go @@ -28,7 +28,7 @@ func TestResolveIP(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&dns.Config{ diff --git a/testing/scenarios/dokodemo_test.go b/testing/scenarios/dokodemo_test.go index 2a821f35b..5148280d5 100644 --- a/testing/scenarios/dokodemo_test.go +++ b/testing/scenarios/dokodemo_test.go @@ -32,7 +32,7 @@ func TestDokodemoTCP(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -64,7 +64,7 @@ func TestDokodemoTCP(t *testing.T) { }, } - clientPort := uint32(pickPort()) + clientPort := uint32(tcp.PickPort()) clientPortRange := uint32(5) clientConfig := &core.Config{ App: []*serial.TypedMessage{ @@ -145,7 +145,7 @@ func TestDokodemoUDP(t *testing.T) { defer udpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -171,7 +171,7 @@ func TestDokodemoUDP(t *testing.T) { }, } - clientPort := uint32(pickPort()) + clientPort := uint32(tcp.PickPort()) clientPortRange := uint32(5) clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ diff --git a/testing/scenarios/feature_test.go b/testing/scenarios/feature_test.go index 22c3ff727..84aaaccd8 100644 --- a/testing/scenarios/feature_test.go +++ b/testing/scenarios/feature_test.go @@ -1,6 +1,7 @@ package scenarios import ( + "context" "io/ioutil" "net/http" "net/url" @@ -9,8 +10,11 @@ import ( xproxy "golang.org/x/net/proxy" "v2ray.com/core" + "v2ray.com/core/app/dispatcher" "v2ray.com/core/app/log" "v2ray.com/core/app/proxyman" + _ "v2ray.com/core/app/proxyman/inbound" + _ "v2ray.com/core/app/proxyman/outbound" "v2ray.com/core/app/router" clog "v2ray.com/core/common/log" "v2ray.com/core/common/net" @@ -42,7 +46,7 @@ func TestPassiveConnection(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -113,7 +117,7 @@ func TestProxy(t *testing.T) { defer tcpServer.Close() serverUserID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -140,7 +144,7 @@ func TestProxy(t *testing.T) { } proxyUserID := protocol.NewID(uuid.New()) - proxyPort := pickPort() + proxyPort := tcp.PickPort() proxyConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -166,7 +170,7 @@ func TestProxy(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -261,7 +265,7 @@ func TestProxyOverKCP(t *testing.T) { defer tcpServer.Close() serverUserID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -291,7 +295,7 @@ func TestProxyOverKCP(t *testing.T) { } proxyUserID := protocol.NewID(uuid.New()) - proxyPort := pickPort() + proxyPort := tcp.PickPort() proxyConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -322,7 +326,7 @@ func TestProxyOverKCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -426,8 +430,8 @@ func TestBlackhole(t *testing.T) { assert(err, IsNil) defer tcpServer2.Close() - serverPort := pickPort() - serverPort2 := pickPort() + serverPort := tcp.PickPort() + serverPort2 := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -517,7 +521,7 @@ func TestForward(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -583,7 +587,7 @@ func TestUDPConnection(t *testing.T) { assert(err, IsNil) defer udpServer.Close() - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -659,8 +663,8 @@ func TestUDPConnection(t *testing.T) { func TestDomainSniffing(t *testing.T) { assert := With(t) - sniffingPort := pickPort() - httpPort := pickPort() + sniffingPort := tcp.PickPort() + httpPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -748,3 +752,102 @@ func TestDomainSniffing(t *testing.T) { CloseAllServers(servers) } + +func TestDialV2Ray(t *testing.T) { + assert := With(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert(err, IsNil) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&dispatcher.Config{}), + serial.ToTypedMessage(&proxyman.InboundConfig{}), + serial.ToTypedMessage(&proxyman.OutboundConfig{}), + }, + Inbound: []*core.InboundHandlerConfig{}, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + SecuritySettings: &protocol.SecurityConfig{ + Type: protocol.SecurityType_AES128_GCM, + }, + }), + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig) + assert(err, IsNil) + + client, err := core.New(clientConfig) + assert(err, IsNil) + + conn, err := core.Dial(context.Background(), client, dest) + assert(err, IsNil) + + payload := "commander request." + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert(err, IsNil) + assert(response[:nBytes], Equals, xor([]byte(payload))) + assert(conn.Close(), IsNil) + + CloseAllServers(servers) +} diff --git a/testing/scenarios/http_test.go b/testing/scenarios/http_test.go index 942718e33..e490c4a42 100644 --- a/testing/scenarios/http_test.go +++ b/testing/scenarios/http_test.go @@ -26,7 +26,7 @@ import ( func TestHttpConformance(t *testing.T) { assert := With(t) - httpServerPort := pickPort() + httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: make(map[string]http.HandlerFunc), @@ -35,7 +35,7 @@ func TestHttpConformance(t *testing.T) { assert(err, IsNil) defer httpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -90,7 +90,7 @@ func TestHttpConnectMethod(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -146,7 +146,7 @@ func TestHttpConnectMethod(t *testing.T) { func TestHttpPost(t *testing.T) { assert := With(t) - httpServerPort := pickPort() + httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: map[string]http.HandlerFunc{ @@ -169,7 +169,7 @@ func TestHttpPost(t *testing.T) { assert(err, IsNil) defer httpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -226,7 +226,7 @@ func setProxyBasicAuth(req *http.Request, user, pass string) { func TestHttpBasicAuth(t *testing.T) { assert := With(t) - httpServerPort := pickPort() + httpServerPort := tcp.PickPort() httpServer := &v2httptest.Server{ Port: httpServerPort, PathHandler: make(map[string]http.HandlerFunc), @@ -235,7 +235,7 @@ func TestHttpBasicAuth(t *testing.T) { assert(err, IsNil) defer httpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { diff --git a/testing/scenarios/policy_test.go b/testing/scenarios/policy_test.go new file mode 100644 index 000000000..93e4de3e1 --- /dev/null +++ b/testing/scenarios/policy_test.go @@ -0,0 +1,169 @@ +package scenarios + +import ( + "io" + "testing" + "time" + + "v2ray.com/core" + "v2ray.com/core/app/policy" + "v2ray.com/core/app/proxyman" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" + "v2ray.com/core/common/uuid" + "v2ray.com/core/proxy/dokodemo" + "v2ray.com/core/proxy/freedom" + "v2ray.com/core/proxy/vmess" + "v2ray.com/core/proxy/vmess/inbound" + "v2ray.com/core/proxy/vmess/outbound" + "v2ray.com/core/testing/servers/tcp" + . "v2ray.com/ext/assert" +) + +func startQuickClosingTCPServer() (net.Listener, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + go func() { + for { + conn, err := listener.Accept() + if err != nil { + break + } + b := make([]byte, 1024) + conn.Read(b) + conn.Close() + } + }() + return listener, nil +} + +func TestVMessClosing(t *testing.T) { + assert := With(t) + + tcpServer, err := startQuickClosingTCPServer() + assert(err, IsNil) + defer tcpServer.Close() + + dest := net.DestinationFromAddr(tcpServer.Addr()) + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&policy.Config{ + Level: map[uint32]*policy.Policy{ + 0: { + Timeout: &policy.Policy_Timeout{ + UplinkOnly: &policy.Second{Value: 0}, + DownlinkOnly: &policy.Second{Value: 0}, + }, + }, + }, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&policy.Config{ + Level: map[uint32]*policy.Policy{ + 0: { + Timeout: &policy.Policy_Timeout{ + UplinkOnly: &policy.Second{Value: 0}, + DownlinkOnly: &policy.Second{Value: 0}, + }, + }, + }, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + AlterId: 64, + SecuritySettings: &protocol.SecurityConfig{ + Type: protocol.SecurityType_AES128_GCM, + }, + }), + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + assert(err, IsNil) + + defer CloseAllServers(servers) + + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + conn.SetDeadline(time.Now().Add(time.Second * 2)) + + nBytes, err := conn.Write([]byte("test payload")) + assert(nBytes, GreaterThan, 0) + assert(err, IsNil) + + resp := make([]byte, 1024) + nBytes, err = conn.Read(resp) + assert(err, Equals, io.EOF) + assert(nBytes, Equals, 0) + + CloseAllServers(servers) +} diff --git a/testing/scenarios/shadowsocks_test.go b/testing/scenarios/shadowsocks_test.go index 7f827bb9b..0db921b77 100644 --- a/testing/scenarios/shadowsocks_test.go +++ b/testing/scenarios/shadowsocks_test.go @@ -41,7 +41,7 @@ func TestShadowsocksAES256TCP(t *testing.T) { Ota: shadowsocks.Account_Enabled, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -60,6 +60,7 @@ func TestShadowsocksAES256TCP(t *testing.T) { Account: account, Level: 1, }, + Network: []net.Network{net.Network_TCP}, }), }, }, @@ -70,7 +71,7 @@ func TestShadowsocksAES256TCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -159,7 +160,7 @@ func TestShadowsocksAES128UDP(t *testing.T) { Ota: shadowsocks.Account_Enabled, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -174,11 +175,11 @@ func TestShadowsocksAES128UDP(t *testing.T) { Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ - UdpEnabled: true, User: &protocol.User{ Account: account, Level: 1, }, + Network: []net.Network{net.Network_UDP}, }), }, }, @@ -189,7 +190,7 @@ func TestShadowsocksAES128UDP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -278,7 +279,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) { Ota: shadowsocks.Account_Enabled, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -297,6 +298,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) { Account: account, Level: 1, }, + Network: []net.Network{net.Network_TCP}, }), }, }, @@ -307,7 +309,7 @@ func TestShadowsocksChacha20TCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -395,7 +397,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) { CipherType: shadowsocks.CipherType_AES_256_GCM, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -414,6 +416,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) { Account: account, Level: 1, }, + Network: []net.Network{net.Network_TCP}, }), }, }, @@ -424,7 +427,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -512,7 +515,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) { CipherType: shadowsocks.CipherType_AES_128_GCM, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -527,11 +530,11 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) { Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ - UdpEnabled: true, User: &protocol.User{ Account: account, Level: 1, }, + Network: []net.Network{net.Network_UDP}, }), }, }, @@ -542,7 +545,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -615,22 +618,22 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) { CloseAllServers(servers) } -func TestShadowsocksAES256GCMConformance(t *testing.T) { +func TestShadowsocksAES128GCMUDPMux(t *testing.T) { assert := With(t) - tcpServer := tcp.Server{ + udpServer := udp.Server{ MsgProcessor: xor, } - dest, err := tcpServer.Start() + dest, err := udpServer.Start() assert(err, IsNil) - defer tcpServer.Close() + defer udpServer.Close() account := serial.ToTypedMessage(&shadowsocks.Account{ - Password: "ss-password", - CipherType: shadowsocks.CipherType_AES_256_GCM, + Password: "shadowsocks-password", + CipherType: shadowsocks.CipherType_AES_128_GCM, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -649,6 +652,131 @@ func TestShadowsocksAES256GCMConformance(t *testing.T) { Account: account, Level: 1, }, + Network: []net.Network{net.Network_TCP}, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_UDP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + MultiplexSettings: &proxyman.MultiplexingConfig{ + Enabled: true, + Concurrency: 8, + }, + }), + ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{ + Server: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: account, + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + assert(err, IsNil) + + var wg sync.WaitGroup + wg.Add(10) + for i := 0; i < 10; i++ { + go func() { + conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + payload := make([]byte, 1024) + rand.Read(payload) + + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := readFrom(conn, time.Second*5, 1024) + assert(response, Equals, xor([]byte(payload))) + assert(conn.Close(), IsNil) + wg.Done() + }() + } + wg.Wait() + + CloseAllServers(servers) +} + +func TestShadowsocksAES256GCMConformance(t *testing.T) { + assert := With(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert(err, IsNil) + defer tcpServer.Close() + + account := serial.ToTypedMessage(&shadowsocks.Account{ + Password: "ss-password", + CipherType: shadowsocks.CipherType_AES_256_GCM, + }) + + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ + User: &protocol.User{ + Account: account, + Level: 1, + }, + Network: []net.Network{net.Network_TCP}, }), }, }, @@ -708,7 +836,7 @@ func TestShadowsocksChacha20Poly1305UDPConformance(t *testing.T) { CipherType: shadowsocks.CipherType_CHACHA20_POLY1305, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -723,11 +851,11 @@ func TestShadowsocksChacha20Poly1305UDPConformance(t *testing.T) { Listen: net.NewIPOrDomain(net.LocalHostIP), }), ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{ - UdpEnabled: true, User: &protocol.User{ Account: account, Level: 1, }, + Network: []net.Network{net.Network_UDP}, }), }, }, @@ -759,7 +887,7 @@ func TestShadowsocksChacha20Poly1305UDPConformance(t *testing.T) { Port: int(serverPort), }) assert(err, IsNil) - assert(nBytes, Equals, payload.Len()) + assert(int32(nBytes), Equals, payload.Len()) conn.SetReadDeadline(time.Now().Add(time.Second * 10)) response := make([]byte, 10240) @@ -791,7 +919,7 @@ func TestShadowsocksChacha20Conformance(t *testing.T) { Ota: shadowsocks.Account_Disabled, }) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -810,6 +938,7 @@ func TestShadowsocksChacha20Conformance(t *testing.T) { Account: account, Level: 1, }, + Network: []net.Network{net.Network_TCP}, }), }, }, diff --git a/testing/scenarios/socks_test.go b/testing/scenarios/socks_test.go index 04ebe9660..4bbaddb27 100644 --- a/testing/scenarios/socks_test.go +++ b/testing/scenarios/socks_test.go @@ -28,7 +28,7 @@ func TestSocksBridgeTCP(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -53,7 +53,7 @@ func TestSocksBridgeTCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -125,7 +125,7 @@ func TestSocksBridageUDP(t *testing.T) { assert(err, IsNil) defer udpServer.Close() - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -150,7 +150,7 @@ func TestSocksBridageUDP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -222,8 +222,8 @@ func TestSocksConformance(t *testing.T) { assert(err, IsNil) defer tcpServer.Close() - authPort := pickPort() - noAuthPort := pickPort() + authPort := tcp.PickPort() + noAuthPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { diff --git a/testing/scenarios/tls_test.go b/testing/scenarios/tls_test.go index c6d5505d4..e8eb69e3b 100644 --- a/testing/scenarios/tls_test.go +++ b/testing/scenarios/tls_test.go @@ -2,6 +2,7 @@ package scenarios import ( "crypto/rand" + "sync" "testing" "time" @@ -20,6 +21,7 @@ import ( "v2ray.com/core/testing/servers/udp" tlsgen "v2ray.com/core/testing/tls" "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/internet/http" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/websocket" . "v2ray.com/ext/assert" @@ -36,7 +38,7 @@ func TestSimpleTLSConnection(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -70,7 +72,7 @@ func TestSimpleTLSConnection(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -185,7 +187,7 @@ func TestTLSOverKCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -266,7 +268,7 @@ func TestTLSOverWebSocket(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -301,7 +303,7 @@ func TestTLSOverWebSocket(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -377,3 +379,147 @@ func TestTLSOverWebSocket(t *testing.T) { CloseAllServers(servers) } + +func TestHTTP2(t *testing.T) { + assert := With(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert(err, IsNil) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_HTTP, + TransportSettings: []*internet.TransportConfig{ + { + Protocol: internet.TransportProtocol_HTTP, + Settings: serial.ToTypedMessage(&http.Config{ + Host: []string{"v2ray.com"}, + Path: "/testpath", + }), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(clientPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_HTTP, + TransportSettings: []*internet.TransportConfig{ + { + Protocol: internet.TransportProtocol_HTTP, + Settings: serial.ToTypedMessage(&http.Config{ + Host: []string{"v2ray.com"}, + Path: "/testpath", + }), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + assert(err, IsNil) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert(err, IsNil) + + payload := make([]byte, 10240*1024) + rand.Read(payload) + nBytes, err := conn.Write([]byte(payload)) + assert(err, IsNil) + assert(nBytes, Equals, len(payload)) + + response := readFrom(conn, time.Second*20, len(payload)) + assert(response, Equals, xor([]byte(payload))) + assert(conn.Close(), IsNil) + }() + } + wg.Wait() + + CloseAllServers(servers) +} diff --git a/testing/scenarios/transport_test.go b/testing/scenarios/transport_test.go index 253cd2806..0ce1462c3 100644 --- a/testing/scenarios/transport_test.go +++ b/testing/scenarios/transport_test.go @@ -33,7 +33,7 @@ func TestHttpConnectionHeader(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { @@ -69,7 +69,7 @@ func TestHttpConnectionHeader(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ Inbound: []*core.InboundHandlerConfig{ { diff --git a/testing/scenarios/vmess_test.go b/testing/scenarios/vmess_test.go index c392d91f2..9b746baa9 100644 --- a/testing/scenarios/vmess_test.go +++ b/testing/scenarios/vmess_test.go @@ -36,7 +36,7 @@ func TestVMessDynamicPort(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -91,7 +91,7 @@ func TestVMessDynamicPort(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -171,7 +171,7 @@ func TestVMessGCM(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -204,7 +204,7 @@ func TestVMessGCM(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -294,7 +294,7 @@ func TestVMessGCMUDP(t *testing.T) { defer udpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -327,7 +327,7 @@ func TestVMessGCMUDP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -427,7 +427,7 @@ func TestVMessChacha20(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -460,7 +460,7 @@ func TestVMessChacha20(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -550,7 +550,7 @@ func TestVMessNone(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -583,7 +583,7 @@ func TestVMessNone(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -711,7 +711,7 @@ func TestVMessKCP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -808,7 +808,7 @@ func TestVMessIPv6(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -841,7 +841,7 @@ func TestVMessIPv6(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -923,7 +923,7 @@ func TestVMessGCMMux(t *testing.T) { defer tcpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -956,7 +956,7 @@ func TestVMessGCMMux(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -1065,7 +1065,7 @@ func TestVMessGCMMuxUDP(t *testing.T) { defer udpServer.Close() userID := protocol.NewID(uuid.New()) - serverPort := pickPort() + serverPort := tcp.PickPort() serverConfig := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(&log.Config{ @@ -1098,7 +1098,7 @@ func TestVMessGCMMuxUDP(t *testing.T) { }, } - clientPort := pickPort() + clientPort := tcp.PickPort() clientUDPPort := udp.PickPort() clientConfig := &core.Config{ App: []*serial.TypedMessage{ diff --git a/testing/servers/tcp/port.go b/testing/servers/tcp/port.go new file mode 100644 index 000000000..cf4729d2c --- /dev/null +++ b/testing/servers/tcp/port.go @@ -0,0 +1,16 @@ +package tcp + +import ( + "v2ray.com/core/common" + "v2ray.com/core/common/net" +) + +// PickPort returns an unused TCP port in the system. The port returned is highly likely to be unused, but not guaranteed. +func PickPort() net.Port { + listener, err := net.Listen("tcp4", "127.0.0.1:0") + common.Must(err) + defer listener.Close() + + addr := listener.Addr().(*net.TCPAddr) + return net.Port(addr.Port) +} diff --git a/testing/servers/udp/port.go b/testing/servers/udp/port.go index 0c77c4cff..75da5df44 100644 --- a/testing/servers/udp/port.go +++ b/testing/servers/udp/port.go @@ -5,6 +5,7 @@ import ( "v2ray.com/core/common/net" ) +// PickPort returns an unused UDP port in the system. The port returned is highly likely to be unused, but not guaranteed. func PickPort() net.Port { conn, err := net.ListenUDP("udp4", &net.UDPAddr{ IP: net.LocalHostIP.IP(), diff --git a/testing/tls/tls.go b/testing/tls/tls.go index 22f81df93..7858e7333 100644 --- a/testing/tls/tls.go +++ b/testing/tls/tls.go @@ -29,12 +29,12 @@ func GenerateCertificateForTest() *v2tls.Certificate { Subject: pkix.Name{ Organization: []string{"V2Ray Inc"}, }, - NotBefore: time.Now(), + NotBefore: time.Now().Add(time.Hour * -1), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, - DNSNames: []string{"www.v2ray.com"}, + DNSNames: []string{"www.v2ray.com", "v2ray.com"}, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) diff --git a/transport/config.go b/transport/config.go index de51c0e37..486ea45a6 100644 --- a/transport/config.go +++ b/transport/config.go @@ -9,8 +9,5 @@ func (c *Config) Apply() error { if c == nil { return nil } - if err := internet.ApplyGlobalTransportSettings(c.TransportSettings); err != nil { - return err - } - return nil + return internet.ApplyGlobalTransportSettings(c.TransportSettings) } diff --git a/transport/internet/config.go b/transport/internet/config.go index 72d20f8e1..83f097549 100644 --- a/transport/internet/config.go +++ b/transport/internet/config.go @@ -10,7 +10,9 @@ var ( ) func RegisterProtocolConfigCreator(protocol TransportProtocol, creator ConfigCreator) error { - // TODO: check duplicate + if _, found := globalTransportConfigCreatorCache[protocol]; found { + return newError("protocol ", TransportProtocol_name[int32(protocol)], " is already registered").AtError() + } globalTransportConfigCreatorCache[protocol] = creator return nil } diff --git a/transport/internet/config.pb.go b/transport/internet/config.pb.go index ff89b9c73..129ad2083 100644 --- a/transport/internet/config.pb.go +++ b/transport/internet/config.pb.go @@ -23,6 +23,7 @@ const ( TransportProtocol_UDP TransportProtocol = 1 TransportProtocol_MKCP TransportProtocol = 2 TransportProtocol_WebSocket TransportProtocol = 3 + TransportProtocol_HTTP TransportProtocol = 4 ) var TransportProtocol_name = map[int32]string{ @@ -30,12 +31,14 @@ var TransportProtocol_name = map[int32]string{ 1: "UDP", 2: "MKCP", 3: "WebSocket", + 4: "HTTP", } var TransportProtocol_value = map[string]int32{ "TCP": 0, "UDP": 1, "MKCP": 2, "WebSocket": 3, + "HTTP": 4, } func (x TransportProtocol) String() string { @@ -138,29 +141,29 @@ func init() { func init() { proto.RegisterFile("v2ray.com/core/transport/internet/config.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 374 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x91, 0x4d, 0x4b, 0xeb, 0x40, - 0x14, 0x86, 0x6f, 0x92, 0x72, 0x6f, 0x7a, 0xda, 0x5e, 0xd3, 0x59, 0x15, 0xa1, 0x58, 0x2b, 0x48, - 0x70, 0x31, 0x29, 0x71, 0xef, 0xa2, 0x71, 0x23, 0x5a, 0x0c, 0x49, 0x55, 0x10, 0xa4, 0xa4, 0xe3, - 0x18, 0x82, 0x4d, 0xa6, 0x4c, 0x46, 0x31, 0xbf, 0xc7, 0x9d, 0x7b, 0xff, 0x9f, 0xe4, 0x63, 0x86, - 0xa2, 0x50, 0xba, 0x71, 0x37, 0x64, 0xde, 0xf3, 0x9c, 0x27, 0xef, 0x00, 0x7e, 0x75, 0x79, 0x54, - 0x60, 0xc2, 0x52, 0x87, 0x30, 0x4e, 0x1d, 0xc1, 0xa3, 0x2c, 0x5f, 0x33, 0x2e, 0x9c, 0x24, 0x13, - 0x94, 0x67, 0x54, 0x38, 0x84, 0x65, 0x4f, 0x49, 0x8c, 0xd7, 0x9c, 0x09, 0x86, 0x86, 0x32, 0xcf, - 0x29, 0x56, 0x59, 0x2c, 0xb3, 0xfb, 0x93, 0x6f, 0x38, 0xc2, 0xd2, 0x94, 0x65, 0x4e, 0x4e, 0x79, - 0x12, 0xad, 0x1c, 0x51, 0xac, 0xe9, 0xe3, 0x22, 0xa5, 0x79, 0x1e, 0xc5, 0xb4, 0x06, 0x8e, 0xdf, - 0x35, 0xd8, 0x9b, 0x4b, 0x90, 0x57, 0xad, 0x42, 0x57, 0x60, 0x56, 0x97, 0x84, 0xad, 0x06, 0xda, - 0x48, 0xb3, 0xff, 0xbb, 0x13, 0xbc, 0x75, 0x2f, 0x56, 0x04, 0xbf, 0x99, 0x0b, 0x14, 0x01, 0x4d, - 0xc1, 0xcc, 0xa9, 0x10, 0x49, 0x16, 0xe7, 0x03, 0x7d, 0xa4, 0xd9, 0x1d, 0xf7, 0x78, 0x93, 0x56, - 0x2b, 0xe2, 0x5a, 0x11, 0xcf, 0x4b, 0xc5, 0x59, 0x6d, 0x18, 0xa8, 0xb9, 0xf1, 0xa7, 0x0e, 0xdd, - 0x50, 0x70, 0x1a, 0xa5, 0xbf, 0xa2, 0xf8, 0x00, 0x48, 0x4d, 0x2c, 0x36, 0x64, 0x0d, 0xbb, 0xe3, - 0xe2, 0x5d, 0xb9, 0xb5, 0x59, 0xd0, 0x57, 0x99, 0xb0, 0x01, 0xa1, 0x23, 0xe8, 0xe5, 0x94, 0xbc, - 0xf0, 0x44, 0x14, 0x8b, 0xf2, 0x0d, 0x06, 0xc6, 0x48, 0xb3, 0xdb, 0x41, 0x57, 0x7e, 0x2c, 0x7f, - 0x1a, 0x85, 0xd0, 0x57, 0x21, 0xa5, 0xd0, 0xaa, 0x14, 0x76, 0xed, 0xcb, 0x92, 0x00, 0xb9, 0x79, - 0x7c, 0x00, 0x1d, 0x9f, 0xb3, 0xb7, 0xa2, 0x69, 0xcd, 0x02, 0x43, 0x44, 0x71, 0x55, 0x58, 0x3b, - 0x28, 0x8f, 0x27, 0x67, 0xd0, 0xff, 0x51, 0x0c, 0xfa, 0x07, 0xc6, 0xdc, 0xf3, 0xad, 0x3f, 0xe5, - 0xe1, 0xe6, 0xdc, 0xb7, 0x34, 0x64, 0x42, 0x6b, 0x76, 0xe9, 0xf9, 0x96, 0x8e, 0x7a, 0xd0, 0xbe, - 0xa3, 0xcb, 0x90, 0x91, 0x67, 0x2a, 0x2c, 0x63, 0x7a, 0x0d, 0x87, 0x84, 0xa5, 0xdb, 0x2b, 0xf2, - 0xb5, 0x7b, 0x53, 0x9e, 0x3f, 0xf4, 0xe1, 0xad, 0x1b, 0x44, 0x05, 0xf6, 0xca, 0xac, 0x5a, 0x8d, - 0x2f, 0x9a, 0xfb, 0xe5, 0xdf, 0xea, 0x51, 0x4e, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x4a, - 0x68, 0x51, 0x19, 0x03, 0x00, 0x00, + // 379 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x91, 0x4f, 0x4b, 0xf3, 0x40, + 0x10, 0x87, 0xdf, 0x24, 0xe5, 0x35, 0x9d, 0xb6, 0x9a, 0xee, 0xa9, 0x08, 0xc5, 0x5a, 0x41, 0x82, + 0x87, 0x4d, 0x89, 0xdf, 0xa0, 0xf1, 0x50, 0xd1, 0x62, 0x48, 0xa2, 0x82, 0x20, 0x25, 0x5d, 0xd7, + 0x10, 0x6c, 0xb2, 0x65, 0xb3, 0x8a, 0xf9, 0x3c, 0xde, 0xbc, 0xfb, 0xfd, 0x24, 0xff, 0x96, 0xa2, + 0x50, 0x7a, 0xf1, 0x36, 0x64, 0x7e, 0xf3, 0xcc, 0x93, 0x59, 0xc0, 0x6f, 0x36, 0x0f, 0x73, 0x4c, + 0x58, 0x62, 0x11, 0xc6, 0xa9, 0x25, 0x78, 0x98, 0x66, 0x6b, 0xc6, 0x85, 0x15, 0xa7, 0x82, 0xf2, + 0x94, 0x0a, 0x8b, 0xb0, 0xf4, 0x39, 0x8e, 0xf0, 0x9a, 0x33, 0xc1, 0xd0, 0xb0, 0xc9, 0x73, 0x8a, + 0x65, 0x16, 0x37, 0xd9, 0xc3, 0xc9, 0x0f, 0x1c, 0x61, 0x49, 0xc2, 0x52, 0x2b, 0xa3, 0x3c, 0x0e, + 0x57, 0x96, 0xc8, 0xd7, 0xf4, 0x69, 0x91, 0xd0, 0x2c, 0x0b, 0x23, 0x5a, 0x01, 0xc7, 0x1f, 0x0a, + 0x1c, 0x04, 0x0d, 0xc8, 0x29, 0x57, 0xa1, 0x6b, 0xd0, 0xcb, 0x26, 0x61, 0xab, 0x81, 0x32, 0x52, + 0xcc, 0x7d, 0x7b, 0x82, 0xb7, 0xee, 0xc5, 0x92, 0xe0, 0xd6, 0x73, 0x9e, 0x24, 0xa0, 0x29, 0xe8, + 0x19, 0x15, 0x22, 0x4e, 0xa3, 0x6c, 0xa0, 0x8e, 0x14, 0xb3, 0x63, 0x9f, 0x6e, 0xd2, 0x2a, 0x45, + 0x5c, 0x29, 0xe2, 0xa0, 0x50, 0x9c, 0x57, 0x86, 0x9e, 0x9c, 0x1b, 0x7f, 0xa9, 0xd0, 0xf5, 0x05, + 0xa7, 0x61, 0xf2, 0x27, 0x8a, 0x8f, 0x80, 0xe4, 0xc4, 0x62, 0x43, 0x56, 0x33, 0x3b, 0x36, 0xde, + 0x95, 0x5b, 0x99, 0x79, 0x7d, 0x99, 0xf1, 0x6b, 0x10, 0x3a, 0x81, 0x5e, 0x46, 0xc9, 0x2b, 0x8f, + 0x45, 0xbe, 0x28, 0xde, 0x60, 0xa0, 0x8d, 0x14, 0xb3, 0xed, 0x75, 0x9b, 0x8f, 0xc5, 0x4f, 0x23, + 0x1f, 0xfa, 0x32, 0x24, 0x15, 0x5a, 0xa5, 0xc2, 0xae, 0xf7, 0x32, 0x1a, 0x40, 0xb3, 0x79, 0x7c, + 0x04, 0x1d, 0x97, 0xb3, 0xf7, 0xbc, 0xbe, 0x9a, 0x01, 0x9a, 0x08, 0xa3, 0xf2, 0x60, 0x6d, 0xaf, + 0x28, 0xcf, 0x66, 0xd0, 0xff, 0x75, 0x18, 0xb4, 0x07, 0x5a, 0xe0, 0xb8, 0xc6, 0xbf, 0xa2, 0xb8, + 0xbd, 0x70, 0x0d, 0x05, 0xe9, 0xd0, 0x9a, 0x5f, 0x39, 0xae, 0xa1, 0xa2, 0x1e, 0xb4, 0xef, 0xe9, + 0xd2, 0x67, 0xe4, 0x85, 0x0a, 0x43, 0x2b, 0x1a, 0xb3, 0x20, 0x70, 0x8d, 0xd6, 0xf4, 0x06, 0x8e, + 0x09, 0x4b, 0xb6, 0x1f, 0xcb, 0x55, 0x1e, 0xf4, 0xa6, 0xfe, 0x54, 0x87, 0x77, 0xb6, 0x17, 0xe6, + 0xd8, 0x29, 0xb2, 0x52, 0x02, 0x5f, 0xd6, 0xfd, 0xe5, 0xff, 0xf2, 0x79, 0xce, 0xbf, 0x03, 0x00, + 0x00, 0xff, 0xff, 0xf3, 0x7b, 0xd5, 0x57, 0x23, 0x03, 0x00, 0x00, } diff --git a/transport/internet/config.proto b/transport/internet/config.proto index ad4f34093..b6b8a7983 100644 --- a/transport/internet/config.proto +++ b/transport/internet/config.proto @@ -13,6 +13,7 @@ enum TransportProtocol { UDP = 1; MKCP = 2; WebSocket = 3; + HTTP = 4; } message TransportConfig { diff --git a/transport/internet/header.go b/transport/internet/header.go index 20c86d882..c6bde9258 100644 --- a/transport/internet/header.go +++ b/transport/internet/header.go @@ -8,7 +8,7 @@ import ( ) type PacketHeader interface { - Size() int + Size() int32 Write([]byte) (int, error) } diff --git a/transport/internet/header_test.go b/transport/internet/header_test.go index 65a7afa19..980b62a90 100644 --- a/transport/internet/header_test.go +++ b/transport/internet/header_test.go @@ -15,13 +15,13 @@ func TestAllHeadersLoadable(t *testing.T) { noopAuth, err := CreatePacketHeader((*noop.Config)(nil)) assert(err, IsNil) - assert(noopAuth.Size(), Equals, 0) + assert(noopAuth.Size(), Equals, int32(0)) srtp, err := CreatePacketHeader((*srtp.Config)(nil)) assert(err, IsNil) - assert(srtp.Size(), Equals, 4) + assert(srtp.Size(), Equals, int32(4)) utp, err := CreatePacketHeader((*utp.Config)(nil)) assert(err, IsNil) - assert(utp.Size(), Equals, 4) + assert(utp.Size(), Equals, int32(4)) } diff --git a/transport/internet/headers/http/http.go b/transport/internet/headers/http/http.go index 397b5497a..0a2f9e277 100644 --- a/transport/internet/headers/http/http.go +++ b/transport/internet/headers/http/http.go @@ -57,7 +57,7 @@ type HeaderReader struct { func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { buffer := buf.New() - totalBytes := 0 + totalBytes := int32(0) endingDetected := false for totalBytes < maxHeaderLength { err := buffer.AppendSupplier(buf.ReadFrom(reader)) @@ -66,13 +66,13 @@ func (*HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) { return nil, err } if n := bytes.Index(buffer.Bytes(), []byte(ENDING)); n != -1 { - buffer.SliceFrom(n + len(ENDING)) + buffer.SliceFrom(int32(n + len(ENDING))) endingDetected = true break } - if buffer.Len() >= len(ENDING) { - totalBytes += buffer.Len() - len(ENDING) - leftover := buffer.BytesFrom(-len(ENDING)) + if buffer.Len() >= int32(len(ENDING)) { + totalBytes += buffer.Len() - int32(len(ENDING)) + leftover := buffer.BytesFrom(-int32(len(ENDING))) buffer.Reset(func(b []byte) (int, error) { return copy(b, leftover), nil }) diff --git a/transport/internet/headers/http/http_test.go b/transport/internet/headers/http/http_test.go index dbe6d3f85..ff8b9c540 100644 --- a/transport/internet/headers/http/http_test.go +++ b/transport/internet/headers/http/http_test.go @@ -16,12 +16,12 @@ func TestReaderWriter(t *testing.T) { assert := With(t) cache := buf.New() - b := buf.NewLocal(256) + b := buf.NewSize(256) b.AppendSupplier(serial.WriteString("abcd" + ENDING)) writer := NewHeaderWriter(b) err := writer.Write(cache) assert(err, IsNil) - assert(cache.Len(), Equals, 8) + assert(cache.Len(), Equals, int32(8)) _, err = cache.Write([]byte{'e', 'f', 'g'}) assert(err, IsNil) diff --git a/transport/internet/headers/noop/noop.go b/transport/internet/headers/noop/noop.go index 9d1dd7113..a9c87385c 100644 --- a/transport/internet/headers/noop/noop.go +++ b/transport/internet/headers/noop/noop.go @@ -9,7 +9,7 @@ import ( type NoOpHeader struct{} -func (NoOpHeader) Size() int { +func (NoOpHeader) Size() int32 { return 0 } diff --git a/transport/internet/headers/srtp/srtp.go b/transport/internet/headers/srtp/srtp.go index 69e08b4b6..4d5cacf4c 100644 --- a/transport/internet/headers/srtp/srtp.go +++ b/transport/internet/headers/srtp/srtp.go @@ -13,7 +13,7 @@ type SRTP struct { number uint16 } -func (*SRTP) Size() int { +func (*SRTP) Size() int32 { return 4 } diff --git a/transport/internet/headers/srtp/srtp_test.go b/transport/internet/headers/srtp/srtp_test.go index b4f498d88..8856df80a 100644 --- a/transport/internet/headers/srtp/srtp_test.go +++ b/transport/internet/headers/srtp/srtp_test.go @@ -18,9 +18,9 @@ func TestSRTPWrite(t *testing.T) { srtp := srtpRaw.(*SRTP) - payload := buf.NewLocal(2048) + payload := buf.New() payload.AppendSupplier(srtp.Write) payload.Append(content) - assert(payload.Len(), Equals, len(content)+srtp.Size()) + assert(payload.Len(), Equals, int32(len(content))+srtp.Size()) } diff --git a/transport/internet/headers/utp/utp.go b/transport/internet/headers/utp/utp.go index c76c5fe02..e556bf52f 100644 --- a/transport/internet/headers/utp/utp.go +++ b/transport/internet/headers/utp/utp.go @@ -14,7 +14,7 @@ type UTP struct { connectionId uint16 } -func (*UTP) Size() int { +func (*UTP) Size() int32 { return 4 } diff --git a/transport/internet/headers/utp/utp_test.go b/transport/internet/headers/utp/utp_test.go index a8b637dcb..845f8b1fb 100644 --- a/transport/internet/headers/utp/utp_test.go +++ b/transport/internet/headers/utp/utp_test.go @@ -18,9 +18,9 @@ func TestUTPWrite(t *testing.T) { utp := utpRaw.(*UTP) - payload := buf.NewLocal(2048) + payload := buf.New() payload.AppendSupplier(utp.Write) payload.Append(content) - assert(payload.Len(), Equals, len(content)+utp.Size()) + assert(payload.Len(), Equals, int32(len(content))+utp.Size()) } diff --git a/transport/internet/headers/wechat/wechat.go b/transport/internet/headers/wechat/wechat.go index 2d0473c96..5baa3e853 100644 --- a/transport/internet/headers/wechat/wechat.go +++ b/transport/internet/headers/wechat/wechat.go @@ -12,7 +12,7 @@ type VideoChat struct { sn int } -func (vc *VideoChat) Size() int { +func (vc *VideoChat) Size() int32 { return 13 } diff --git a/transport/internet/headers/wechat/wechat_test.go b/transport/internet/headers/wechat/wechat_test.go index 2afc94665..80185d1e2 100644 --- a/transport/internet/headers/wechat/wechat_test.go +++ b/transport/internet/headers/wechat/wechat_test.go @@ -17,7 +17,7 @@ func TestUTPWrite(t *testing.T) { video := videoRaw.(*VideoChat) - payload := buf.NewLocal(2048) + payload := buf.New() payload.AppendSupplier(video.Write) assert(payload.Len(), Equals, video.Size()) diff --git a/transport/internet/http/config.go b/transport/internet/http/config.go new file mode 100644 index 000000000..a746b595d --- /dev/null +++ b/transport/internet/http/config.go @@ -0,0 +1,45 @@ +package http + +import ( + "v2ray.com/core/common" + "v2ray.com/core/common/dice" + "v2ray.com/core/transport/internet" +) + +func (c *Config) getHosts() []string { + if len(c.Host) == 0 { + return []string{"www.example.com"} + } + return c.Host +} + +func (c *Config) isValidHost(host string) bool { + hosts := c.getHosts() + for _, h := range hosts { + if h == host { + return true + } + } + return false +} + +func (c *Config) getRandomHost() string { + hosts := c.getHosts() + return hosts[dice.Roll(len(hosts))] +} + +func (c *Config) getNormalizedPath() string { + if len(c.Path) == 0 { + return "/" + } + if c.Path[0] != '/' { + return "/" + c.Path + } + return c.Path +} + +func init() { + common.Must(internet.RegisterProtocolConfigCreator(internet.TransportProtocol_HTTP, func() interface{} { + return new(Config) + })) +} diff --git a/transport/internet/http/config.pb.go b/transport/internet/http/config.pb.go new file mode 100644 index 000000000..2ba5cd9bf --- /dev/null +++ b/transport/internet/http/config.pb.go @@ -0,0 +1,63 @@ +package http + +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 { + Host []string `protobuf:"bytes,1,rep,name=host" json:"host,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Config) GetHost() []string { + if m != nil { + return m.Host + } + return nil +} + +func (m *Config) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func init() { + proto.RegisterType((*Config)(nil), "v2ray.core.transport.internet.http.Config") +} + +func init() { + proto.RegisterFile("v2ray.com/core/transport/internet/http/config.proto", fileDescriptor0) +} + +var fileDescriptor0 = []byte{ + // 173 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x2e, 0x33, 0x2a, 0x4a, + 0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x2f, 0x29, 0x4a, 0xcc, 0x2b, + 0x2e, 0xc8, 0x2f, 0x2a, 0xd1, 0xcf, 0xcc, 0x2b, 0x49, 0x2d, 0xca, 0x4b, 0x2d, 0xd1, 0xcf, 0x28, + 0x29, 0x29, 0xd0, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, + 0x52, 0x82, 0x69, 0x2a, 0x4a, 0xd5, 0x83, 0x6b, 0xd0, 0x83, 0x69, 0xd0, 0x03, 0x69, 0x50, 0x32, + 0xe0, 0x62, 0x73, 0x06, 0xeb, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0xc8, 0x2f, 0x2e, 0x91, 0x60, 0x54, + 0x60, 0xd6, 0xe0, 0x0c, 0x02, 0xb3, 0x41, 0x62, 0x05, 0x89, 0x25, 0x19, 0x12, 0x4c, 0x0a, 0x8c, + 0x20, 0x31, 0x10, 0xdb, 0x29, 0x94, 0x4b, 0x2d, 0x39, 0x3f, 0x57, 0x8f, 0xb0, 0xd9, 0x01, 0x8c, + 0x51, 0x2c, 0x20, 0x7a, 0x15, 0x93, 0x52, 0x98, 0x51, 0x50, 0x62, 0xa5, 0x9e, 0x33, 0x48, 0x71, + 0x08, 0x5c, 0xb1, 0x27, 0x4c, 0xb1, 0x47, 0x49, 0x49, 0x41, 0x12, 0x1b, 0xd8, 0xcd, 0xc6, 0x80, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xf2, 0x95, 0x63, 0xea, 0x00, 0x00, 0x00, +} diff --git a/transport/internet/http/config.proto b/transport/internet/http/config.proto new file mode 100644 index 000000000..cb995c13d --- /dev/null +++ b/transport/internet/http/config.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package v2ray.core.transport.internet.http; +option csharp_namespace = "V2Ray.Core.Transport.Internet.Http"; +option go_package = "http"; +option java_package = "com.v2ray.core.transport.internet.http"; +option java_multiple_files = true; + +message Config { + repeated string host = 1; + string path = 2; +} diff --git a/transport/internet/http/connection.go b/transport/internet/http/connection.go new file mode 100644 index 000000000..8f6808672 --- /dev/null +++ b/transport/internet/http/connection.go @@ -0,0 +1,49 @@ +package http + +import ( + "io" + "time" + + "v2ray.com/core/common" + "v2ray.com/core/common/net" +) + +type Connection struct { + Reader io.Reader + Writer io.Writer + Closer common.Closable + Local net.Addr + Remote net.Addr +} + +func (c *Connection) Read(b []byte) (int, error) { + return c.Reader.Read(b) +} + +func (c *Connection) Write(b []byte) (int, error) { + return c.Writer.Write(b) +} + +func (c *Connection) Close() error { + return c.Closer.Close() +} + +func (c *Connection) LocalAddr() net.Addr { + return c.Local +} + +func (c *Connection) RemoteAddr() net.Addr { + return c.Remote +} + +func (c *Connection) SetDeadline(t time.Time) error { + return nil +} + +func (c *Connection) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *Connection) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go new file mode 100644 index 000000000..b4b835308 --- /dev/null +++ b/transport/internet/http/dialer.go @@ -0,0 +1,125 @@ +package http + +import ( + "context" + gotls "crypto/tls" + "io" + "net/http" + "net/url" + "sync" + + "golang.org/x/net/http2" + + "v2ray.com/core/common" + "v2ray.com/core/common/net" + "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/internet/tls" +) + +var ( + globalDialerMap map[net.Destination]*http.Client + globalDailerAccess sync.Mutex +) + +func getHTTPClient(ctx context.Context, dest net.Destination) (*http.Client, error) { + globalDailerAccess.Lock() + defer globalDailerAccess.Unlock() + + if globalDialerMap == nil { + globalDialerMap = make(map[net.Destination]*http.Client) + } + + if client, found := globalDialerMap[dest]; found { + return client, nil + } + + config := tls.ConfigFromContext(ctx) + if config == nil { + return nil, newError("TLS must be enabled for http transport.").AtWarning() + } + + transport := &http2.Transport{ + DialTLS: func(network string, addr string, tlsConfig *gotls.Config) (net.Conn, error) { + rawHost, rawPort, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + if len(rawPort) == 0 { + rawPort = "443" + } + port, err := net.PortFromString(rawPort) + if err != nil { + return nil, err + } + address := net.ParseAddress(rawHost) + + pconn, err := internet.DialSystem(context.Background(), nil, net.TCPDestination(address, port)) + if err != nil { + return nil, err + } + return gotls.Client(pconn, tlsConfig), nil + }, + TLSClientConfig: config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("h2")), + } + + client := &http.Client{ + Transport: transport, + } + + globalDialerMap[dest] = client + return client, nil +} + +// Dial dials a new TCP connection to the given destination. +func Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { + rawSettings := internet.TransportSettingsFromContext(ctx) + httpSettings, ok := rawSettings.(*Config) + if !ok { + return nil, newError("HTTP config is not set.").AtError() + } + + client, err := getHTTPClient(ctx, dest) + if err != nil { + return nil, err + } + + preader, pwriter := io.Pipe() + request := &http.Request{ + Method: "PUT", + Host: httpSettings.getRandomHost(), + Body: preader, + URL: &url.URL{ + Scheme: "https", + Host: dest.NetAddr(), + Path: httpSettings.getNormalizedPath(), + }, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + } + response, err := client.Do(request) + if err != nil { + return nil, newError("failed to dial to ", dest).Base(err).AtWarning() + } + if response.StatusCode != 200 { + return nil, newError("unexpected status", response.StatusCode).AtWarning() + } + + return &Connection{ + Reader: response.Body, + Writer: pwriter, + Closer: common.NewChainedClosable(preader, pwriter, response.Body), + Local: &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + }, + Remote: &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + }, + }, nil +} + +func init() { + common.Must(internet.RegisterTransportDialer(internet.TransportProtocol_HTTP, Dial)) +} diff --git a/transport/internet/http/errors.generated.go b/transport/internet/http/errors.generated.go new file mode 100644 index 000000000..d89f957d7 --- /dev/null +++ b/transport/internet/http/errors.generated.go @@ -0,0 +1,7 @@ +package http + +import "v2ray.com/core/common/errors" + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).Path("Transport", "Internet", "HTTP") +} diff --git a/transport/internet/http/http.go b/transport/internet/http/http.go new file mode 100644 index 000000000..e532ed9c1 --- /dev/null +++ b/transport/internet/http/http.go @@ -0,0 +1,3 @@ +package http + +//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg http -path Transport,Internet,HTTP diff --git a/transport/internet/http/http_test.go b/transport/internet/http/http_test.go new file mode 100644 index 000000000..c0809fffa --- /dev/null +++ b/transport/internet/http/http_test.go @@ -0,0 +1,82 @@ +package http_test + +import ( + "context" + "crypto/rand" + "testing" + "time" + + "v2ray.com/core/common" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" + "v2ray.com/core/testing/servers/tcp" + tlsgen "v2ray.com/core/testing/tls" + "v2ray.com/core/transport/internet" + . "v2ray.com/core/transport/internet/http" + "v2ray.com/core/transport/internet/tls" + . "v2ray.com/ext/assert" +) + +func TestHTTPConnection(t *testing.T) { + assert := With(t) + + port := tcp.PickPort() + + lctx := context.Background() + lctx = internet.ContextWithSecuritySettings(lctx, &tls.Config{ + Certificate: []*tls.Certificate{tlsgen.GenerateCertificateForTest()}, + }) + lctx = internet.ContextWithTransportSettings(lctx, &Config{}) + + listener, err := Listen(lctx, net.LocalHostIP, port, func(conn internet.Connection) { + go func() { + defer conn.Close() + + b := buf.New() + defer b.Release() + + for { + if err := b.Reset(buf.ReadFrom(conn)); err != nil { + return + } + nBytes, err := conn.Write(b.Bytes()) + assert(err, IsNil) + assert(int32(nBytes), Equals, b.Len()) + } + }() + }) + assert(err, IsNil) + + defer listener.Close() + + time.Sleep(time.Second) + + dctx := context.Background() + dctx = internet.ContextWithSecuritySettings(dctx, &tls.Config{ + ServerName: "www.v2ray.com", + AllowInsecure: true, + }) + dctx = internet.ContextWithTransportSettings(dctx, &Config{}) + conn, err := Dial(dctx, net.TCPDestination(net.LocalHostIP, port)) + assert(err, IsNil) + defer conn.Close() + + const N = 1024 + b1 := make([]byte, N) + common.Must2(rand.Read(b1)) + b2 := buf.New() + + nBytes, err := conn.Write(b1) + assert(nBytes, Equals, N) + assert(err, IsNil) + + assert(b2.Reset(buf.ReadFullFrom(conn, N)), IsNil) + assert(b2.Bytes(), Equals, b1) + + nBytes, err = conn.Write(b1) + assert(nBytes, Equals, N) + assert(err, IsNil) + + assert(b2.Reset(buf.ReadFullFrom(conn, N)), IsNil) + assert(b2.Bytes(), Equals, b1) +} diff --git a/transport/internet/http/hub.go b/transport/internet/http/hub.go new file mode 100644 index 000000000..0ad91f1bc --- /dev/null +++ b/transport/internet/http/hub.go @@ -0,0 +1,117 @@ +package http + +import ( + "context" + "io" + "net/http" + "strings" + + "v2ray.com/core/common" + "v2ray.com/core/common/net" + "v2ray.com/core/common/serial" + "v2ray.com/core/common/signal" + "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/internet/tls" +) + +type Listener struct { + server *http.Server + handler internet.ConnHandler + local net.Addr + config Config +} + +func (l *Listener) Addr() net.Addr { + return l.local +} + +func (l *Listener) Close() error { + return l.server.Shutdown(context.Background()) +} + +type flushWriter struct { + w io.Writer + d *signal.Done +} + +func (fw flushWriter) Write(p []byte) (n int, err error) { + if fw.d.Done() { + return 0, io.ErrClosedPipe + } + + n, err = fw.w.Write(p) + if f, ok := fw.w.(http.Flusher); ok { + f.Flush() + } + return +} + +func (l *Listener) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + host := request.Host + if !l.config.isValidHost(host) { + writer.WriteHeader(404) + return + } + path := l.config.getNormalizedPath() + if !strings.HasPrefix(request.URL.Path, path) { + writer.WriteHeader(404) + return + } + + writer.Header().Set("Cache-Control", "no-store") + writer.WriteHeader(200) + if f, ok := writer.(http.Flusher); ok { + f.Flush() + } + done := signal.NewDone() + l.handler(&Connection{ + Reader: request.Body, + Writer: flushWriter{w: writer, d: done}, + Closer: common.NewChainedClosable(done, request.Body), + Local: l.Addr(), + Remote: l.Addr(), + }) + <-done.C() +} + +func Listen(ctx context.Context, address net.Address, port net.Port, handler internet.ConnHandler) (internet.Listener, error) { + rawSettings := internet.TransportSettingsFromContext(ctx) + httpSettings, ok := rawSettings.(*Config) + if !ok { + return nil, newError("HTTP config is not set.").AtError() + } + + listener := &Listener{ + handler: handler, + local: &net.TCPAddr{ + IP: address.IP(), + Port: int(port), + }, + config: *httpSettings, + } + + config := tls.ConfigFromContext(ctx) + if config == nil { + return nil, newError("TLS must be enabled for http transport.").AtWarning() + } + + server := &http.Server{ + Addr: serial.Concat(address, ":", port), + TLSConfig: config.GetTLSConfig(tls.WithNextProto("h2")), + Handler: listener, + } + + listener.server = server + go func() { + err := server.ListenAndServeTLS("", "") + if err != nil { + newError("stoping serving TLS").Base(err).WriteToLog() + } + }() + + return listener, nil +} + +func init() { + common.Must(internet.RegisterTransportListener(internet.TransportProtocol_HTTP, Listen)) +} diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index 44f9fd479..e44e6b1b4 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -19,16 +19,26 @@ var ( ) func fetchInput(ctx context.Context, input io.Reader, reader PacketReader, conn *Connection) { - payload := buf.New() - defer payload.Release() - - for { - err := payload.Reset(buf.ReadFrom(input)) - if err != nil { - payload.Release() - return + cache := make(chan *buf.Buffer, 1024) + go func() { + for { + payload := buf.New() + if err := payload.Reset(buf.ReadFrom(input)); err != nil { + payload.Release() + close(cache) + return + } + select { + case cache <- payload: + default: + payload.Release() + } } + }() + + for payload := range cache { segments := reader.Read(payload.Bytes()) + payload.Release() if len(segments) > 0 { conn.Input(segments) } @@ -76,8 +86,8 @@ func DialKCP(ctx context.Context, dest net.Destination) (internet.Connection, er var iConn internet.Connection = session - if config := v2tls.ConfigFromContext(ctx, v2tls.WithDestination(dest)); config != nil { - tlsConn := tls.Client(iConn, config.GetTLSConfig()) + if config := v2tls.ConfigFromContext(ctx); config != nil { + tlsConn := tls.Client(iConn, config.GetTLSConfig(v2tls.WithDestination(dest))) iConn = tlsConn } diff --git a/transport/internet/kcp/io.go b/transport/internet/kcp/io.go index 8548b7e2e..a8e5f67b3 100644 --- a/transport/internet/kcp/io.go +++ b/transport/internet/kcp/io.go @@ -5,6 +5,8 @@ import ( "crypto/rand" "io" + "v2ray.com/core/common" + "v2ray.com/core/common/buf" "v2ray.com/core/transport/internet" ) @@ -50,14 +52,12 @@ type KCPPacketWriter struct { Header internet.PacketHeader Security cipher.AEAD Writer io.Writer - - buffer [2048]byte } func (w *KCPPacketWriter) Overhead() int { overhead := 0 if w.Header != nil { - overhead += w.Header.Size() + overhead += int(w.Header.Size()) } if w.Security != nil { overhead += w.Security.Overhead() @@ -66,27 +66,28 @@ func (w *KCPPacketWriter) Overhead() int { } func (w *KCPPacketWriter) Write(b []byte) (int, error) { - x := w.buffer[:] - size := 0 + bb := buf.NewSize(int32(len(b) + w.Overhead())) + defer bb.Release() + if w.Header != nil { - nBytes, _ := w.Header.Write(x) - size += nBytes - x = x[nBytes:] + common.Must(bb.AppendSupplier(func(x []byte) (int, error) { + return w.Header.Write(x) + })) } if w.Security != nil { nonceSize := w.Security.NonceSize() - var nonce []byte - if nonceSize > 0 { - nonce = x[:nonceSize] - rand.Read(nonce) - x = x[nonceSize:] - } - x = w.Security.Seal(x[:0], nonce, b, nil) - size += nonceSize + len(x) + common.Must(bb.AppendSupplier(func(x []byte) (int, error) { + return rand.Read(x[:nonceSize]) + })) + nonce := bb.BytesFrom(int32(-nonceSize)) + common.Must(bb.AppendSupplier(func(x []byte) (int, error) { + eb := w.Security.Seal(x[:0], nonce, b, nil) + return len(eb), nil + })) } else { - size += copy(x, b) + bb.Append(b) } - _, err := w.Writer.Write(w.buffer[:size]) + _, err := w.Writer.Write(bb.Bytes()) return len(b), err } diff --git a/transport/internet/kcp/listener.go b/transport/internet/kcp/listener.go index 3fdcbf9f4..00ea56f29 100644 --- a/transport/internet/kcp/listener.go +++ b/transport/internet/kcp/listener.go @@ -61,7 +61,7 @@ func NewListener(ctx context.Context, address net.Address, port net.Port, addCon l.tlsConfig = config.GetTLSConfig() } - hub, err := udp.ListenUDP(address, port, udp.ListenOption{Callback: l.OnReceive, Concurrency: 2}) + hub, err := udp.ListenUDP(address, port, l.OnReceive, udp.HubCapacity(1024)) if err != nil { return nil, err } @@ -73,9 +73,9 @@ func NewListener(ctx context.Context, address net.Address, port net.Port, addCon } func (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination, originalDest net.Destination) { - defer payload.Release() - segments := l.reader.Read(payload.Bytes()) + payload.Release() + if len(segments) == 0 { newError("discarding invalid payload from ", src).WriteToLog() return diff --git a/transport/internet/kcp/segment.go b/transport/internet/kcp/segment.go old mode 100644 new mode 100755 index 5acf25be1..b5d10de4a --- a/transport/internet/kcp/segment.go +++ b/transport/internet/kcp/segment.go @@ -9,7 +9,7 @@ import ( type Command byte const ( - // CommandACK indicates a AckSegment. + // CommandACK indicates an AckSegment. CommandACK Command = 0 // CommandData indicates a DataSegment. CommandData Command = 1 @@ -29,8 +29,9 @@ type Segment interface { Release() Conversation() uint16 Command() Command - ByteSize() int + ByteSize() int32 Bytes() buf.Supplier + parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) } const ( @@ -53,6 +54,34 @@ func NewDataSegment() *DataSegment { return new(DataSegment) } +func (s *DataSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { + s.Conv = conv + s.Option = opt + if len(buf) < 15 { + return false, nil + } + s.Timestamp = serial.BytesToUint32(buf) + buf = buf[4:] + + s.Number = serial.BytesToUint32(buf) + buf = buf[4:] + + s.SendingNext = serial.BytesToUint32(buf) + buf = buf[4:] + + dataLen := int(serial.BytesToUint16(buf)) + buf = buf[2:] + + if len(buf) < dataLen { + return false, nil + } + s.Data().Clear() + s.Data().Append(buf[:dataLen]) + buf = buf[dataLen:] + + return true, buf +} + func (s *DataSegment) Conversation() uint16 { return s.Conv } @@ -87,7 +116,7 @@ func (s *DataSegment) Bytes() buf.Supplier { } } -func (s *DataSegment) ByteSize() int { +func (s *DataSegment) ByteSize() int32 { return 2 + 1 + 1 + 4 + 4 + 4 + 2 + s.payload.Len() } @@ -113,6 +142,36 @@ func NewAckSegment() *AckSegment { } } +func (s *AckSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { + s.Conv = conv + s.Option = opt + if len(buf) < 13 { + return false, nil + } + + s.ReceivingWindow = serial.BytesToUint32(buf) + buf = buf[4:] + + s.ReceivingNext = serial.BytesToUint32(buf) + buf = buf[4:] + + s.Timestamp = serial.BytesToUint32(buf) + buf = buf[4:] + + count := int(buf[0]) + buf = buf[1:] + + if len(buf) < count*4 { + return false, nil + } + for i := 0; i < count; i++ { + s.PutNumber(serial.BytesToUint32(buf)) + buf = buf[4:] + } + + return true, buf +} + func (s *AckSegment) Conversation() uint16 { return s.Conv } @@ -139,8 +198,8 @@ func (s *AckSegment) IsEmpty() bool { return len(s.NumberList) == 0 } -func (s *AckSegment) ByteSize() int { - return 2 + 1 + 1 + 4 + 4 + 4 + 1 + len(s.NumberList)*4 +func (s *AckSegment) ByteSize() int32 { + return 2 + 1 + 1 + 4 + 4 + 4 + 1 + int32(len(s.NumberList)*4) } func (s *AckSegment) Bytes() buf.Supplier { @@ -155,7 +214,7 @@ func (s *AckSegment) Bytes() buf.Supplier { for _, number := range s.NumberList { b = serial.Uint32ToBytes(number, b) } - return s.ByteSize(), nil + return int(s.ByteSize()), nil } } @@ -176,6 +235,27 @@ func NewCmdOnlySegment() *CmdOnlySegment { return new(CmdOnlySegment) } +func (s *CmdOnlySegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) { + s.Conv = conv + s.Cmd = cmd + s.Option = opt + + if len(buf) < 12 { + return false, nil + } + + s.SendingNext = serial.BytesToUint32(buf) + buf = buf[4:] + + s.ReceivingNext = serial.BytesToUint32(buf) + buf = buf[4:] + + s.PeerRTO = serial.BytesToUint32(buf) + buf = buf[4:] + + return true, buf +} + func (s *CmdOnlySegment) Conversation() uint16 { return s.Conv } @@ -184,7 +264,7 @@ func (s *CmdOnlySegment) Command() Command { return s.Cmd } -func (*CmdOnlySegment) ByteSize() int { +func (*CmdOnlySegment) ByteSize() int32 { return 2 + 1 + 1 + 4 + 4 + 4 } @@ -213,83 +293,19 @@ func ReadSegment(buf []byte) (Segment, []byte) { opt := SegmentOption(buf[1]) buf = buf[2:] - if cmd == CommandData { - seg := NewDataSegment() - seg.Conv = conv - seg.Option = opt - if len(buf) < 15 { - return nil, nil - } - seg.Timestamp = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.Number = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.SendingNext = serial.BytesToUint32(buf) - buf = buf[4:] - - dataLen := int(serial.BytesToUint16(buf)) - buf = buf[2:] - - if len(buf) < dataLen { - return nil, nil - } - seg.Data().Clear() - seg.Data().Append(buf[:dataLen]) - buf = buf[dataLen:] - - return seg, buf + var seg Segment + switch cmd { + case CommandData: + seg = NewDataSegment() + case CommandACK: + seg = NewAckSegment() + default: + seg = NewCmdOnlySegment() } - if cmd == CommandACK { - seg := NewAckSegment() - seg.Conv = conv - seg.Option = opt - if len(buf) < 13 { - return nil, nil - } - - seg.ReceivingWindow = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.ReceivingNext = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.Timestamp = serial.BytesToUint32(buf) - buf = buf[4:] - - count := int(buf[0]) - buf = buf[1:] - - if len(buf) < count*4 { - return nil, nil - } - for i := 0; i < count; i++ { - seg.PutNumber(serial.BytesToUint32(buf)) - buf = buf[4:] - } - - return seg, buf - } - - seg := NewCmdOnlySegment() - seg.Conv = conv - seg.Cmd = cmd - seg.Option = opt - - if len(buf) < 12 { + valid, extra := seg.parse(conv, cmd, opt, buf) + if !valid { return nil, nil } - - seg.SendingNext = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.ReceivingNext = serial.BytesToUint32(buf) - buf = buf[4:] - - seg.PeerRTO = serial.BytesToUint32(buf) - buf = buf[4:] - - return seg, buf + return seg, extra } diff --git a/transport/internet/kcp/segment_test.go b/transport/internet/kcp/segment_test.go index f12d488e1..2f1ba70a8 100644 --- a/transport/internet/kcp/segment_test.go +++ b/transport/internet/kcp/segment_test.go @@ -30,7 +30,7 @@ func TestDataSegment(t *testing.T) { bytes := make([]byte, nBytes) seg.Bytes()(bytes) - assert(len(bytes), Equals, nBytes) + assert(int32(len(bytes)), Equals, nBytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*DataSegment) @@ -56,7 +56,7 @@ func Test1ByteDataSegment(t *testing.T) { bytes := make([]byte, nBytes) seg.Bytes()(bytes) - assert(len(bytes), Equals, nBytes) + assert(int32(len(bytes)), Equals, nBytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*DataSegment) @@ -82,7 +82,7 @@ func TestACKSegment(t *testing.T) { bytes := make([]byte, nBytes) seg.Bytes()(bytes) - assert(len(bytes), Equals, nBytes) + assert(int32(len(bytes)), Equals, nBytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*AckSegment) @@ -112,7 +112,7 @@ func TestCmdSegment(t *testing.T) { bytes := make([]byte, nBytes) seg.Bytes()(bytes) - assert(len(bytes), Equals, nBytes) + assert(int32(len(bytes)), Equals, nBytes) iseg, _ := ReadSegment(bytes) seg2 := iseg.(*CmdOnlySegment) diff --git a/transport/internet/kcp/sending.go b/transport/internet/kcp/sending.go index 0bc02c8d8..6cefe6dbb 100644 --- a/transport/internet/kcp/sending.go +++ b/transport/internet/kcp/sending.go @@ -191,11 +191,12 @@ type SendingWorker struct { conn *Connection window *SendingWindow firstUnacknowledged uint32 - firstUnacknowledgedUpdated bool nextNumber uint32 remoteNextNumber uint32 controlWindow uint32 fastResend uint32 + firstUnacknowledgedUpdated bool + closed bool } func NewSendingWorker(kcp *Connection) *SendingWorker { @@ -212,6 +213,7 @@ func NewSendingWorker(kcp *Connection) *SendingWorker { func (w *SendingWorker) Release() { w.Lock() w.window.Release() + w.closed = true w.Unlock() } @@ -258,6 +260,10 @@ func (w *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint w.Lock() defer w.Unlock() + if w.closed { + return + } + if w.remoteNextNumber < seg.ReceivingWindow { w.remoteNextNumber = seg.ReceivingWindow } @@ -289,6 +295,10 @@ func (w *SendingWorker) Push(f buf.Supplier) bool { w.Lock() defer w.Unlock() + if w.closed { + return false + } + if w.window.IsFull() { return false } @@ -333,6 +343,11 @@ func (w *SendingWorker) OnPacketLoss(lossRate uint32) { func (w *SendingWorker) Flush(current uint32) { w.Lock() + if w.closed { + w.Unlock() + return + } + cwnd := w.firstUnacknowledged + w.conn.Config.GetSendingInFlightSize() if cwnd > w.remoteNextNumber { cwnd = w.remoteNextNumber diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index b31b431ea..d90ed46e2 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -19,7 +19,7 @@ func getTCPSettingsFromContext(ctx context.Context) *Config { // Dial dials a new TCP connection to the given destination. func Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { - newError("dialing TCP to ", dest).WriteToLog() + newError("dialing TCP to ", dest).WithContext(ctx).WriteToLog() src := internet.DialerSourceFromContext(ctx) conn, err := internet.DialSystem(ctx, src, dest) @@ -27,8 +27,8 @@ func Dial(ctx context.Context, dest net.Destination) (internet.Connection, error return nil, err } - if config := tls.ConfigFromContext(ctx, tls.WithDestination(dest), tls.WithNextProto("h2")); config != nil { - conn = tls.Client(conn, config.GetTLSConfig()) + if config := tls.ConfigFromContext(ctx); config != nil { + conn = tls.Client(conn, config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("h2"))) } tcpSettings := getTCPSettingsFromContext(ctx) diff --git a/transport/internet/tcp/hub.go b/transport/internet/tcp/hub.go index 05c7e4eb3..e52cc5c00 100644 --- a/transport/internet/tcp/hub.go +++ b/transport/internet/tcp/hub.go @@ -29,7 +29,7 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, handler if err != nil { return nil, err } - newError("listening TCP on ", address, ":", port).WriteToLog() + newError("listening TCP on ", address, ":", port).WithContext(ctx).WriteToLog() networkSettings := internet.TransportSettingsFromContext(ctx) tcpSettings := networkSettings.(*Config) @@ -39,8 +39,8 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, handler addConn: handler, } - if config := tls.ConfigFromContext(ctx, tls.WithNextProto("h2")); config != nil { - l.tlsConfig = config.GetTLSConfig() + if config := tls.ConfigFromContext(ctx); config != nil { + l.tlsConfig = config.GetTLSConfig(tls.WithNextProto("h2")) } if tcpSettings.HeaderSettings != nil { diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 1faf1f7a5..eb4bc34fa 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -25,15 +25,18 @@ func (c *Config) BuildCertificates() []tls.Certificate { return certs } -func (c *Config) GetTLSConfig() *tls.Config { +func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config := &tls.Config{ ClientSessionCache: globalSessionCache, - NextProtos: []string{"http/1.1"}, } if c == nil { return config } + for _, opt := range opts { + opt(config) + } + config.InsecureSkipVerify = c.AllowInsecure config.Certificates = c.BuildCertificates() config.BuildNameToCertificate() @@ -43,14 +46,17 @@ func (c *Config) GetTLSConfig() *tls.Config { if len(c.NextProtocol) > 0 { config.NextProtos = c.NextProtocol } + if len(config.NextProtos) == 0 { + config.NextProtos = []string{"http/1.1"} + } return config } -type Option func(*Config) +type Option func(*tls.Config) func WithDestination(dest net.Destination) Option { - return func(config *Config) { + return func(config *tls.Config) { if dest.Address.Family().IsDomain() && len(config.ServerName) == 0 { config.ServerName = dest.Address.Domain() } @@ -58,23 +64,21 @@ func WithDestination(dest net.Destination) Option { } func WithNextProto(protocol ...string) Option { - return func(config *Config) { - if len(config.NextProtocol) == 0 { - config.NextProtocol = protocol + return func(config *tls.Config) { + if len(config.NextProtos) == 0 { + config.NextProtos = protocol } } } -func ConfigFromContext(ctx context.Context, opts ...Option) *Config { +func ConfigFromContext(ctx context.Context) *Config { securitySettings := internet.SecuritySettingsFromContext(ctx) if securitySettings == nil { return nil } - if config, ok := securitySettings.(*Config); ok { - for _, opt := range opts { - opt(config) - } - return config + config, ok := securitySettings.(*Config) + if !ok { + return nil } - return nil + return config } diff --git a/transport/internet/udp/dispatcher.go b/transport/internet/udp/dispatcher.go index 0db6ae54c..5fd3bf824 100644 --- a/transport/internet/udp/dispatcher.go +++ b/transport/internet/udp/dispatcher.go @@ -72,13 +72,13 @@ func (v *Dispatcher) getInboundRay(dest net.Destination, callback ResponseCallba func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination, payload *buf.Buffer, callback ResponseCallback) { // TODO: Add user to destString - newError("dispatch request to: ", destination).AtDebug().WriteToLog() + newError("dispatch request to: ", destination).AtDebug().WithContext(ctx).WriteToLog() conn := v.getInboundRay(destination, callback) outputStream := conn.inbound.InboundInput() if outputStream != nil { if err := outputStream.WriteMultiBuffer(buf.NewMultiBufferValue(payload)); err != nil { - newError("failed to write first UDP payload").Base(err).WriteToLog() + newError("failed to write first UDP payload").Base(err).WithContext(ctx).WriteToLog() conn.cancel() return } @@ -98,7 +98,7 @@ func handleInput(ctx context.Context, conn *connEntry, callback ResponseCallback mb, err := input.ReadMultiBuffer() if err != nil { - newError("failed to handle UDP input").Base(err).WriteToLog() + newError("failed to handle UDP input").Base(err).WithContext(ctx).WriteToLog() conn.cancel() return } diff --git a/transport/internet/udp/dispatcher_test.go b/transport/internet/udp/dispatcher_test.go index e6c1958ab..22f4a4277 100644 --- a/transport/internet/udp/dispatcher_test.go +++ b/transport/internet/udp/dispatcher_test.go @@ -33,7 +33,7 @@ func TestSameDestinationDispatching(t *testing.T) { assert := With(t) ctx, cancel := context.WithCancel(context.Background()) - link := ray.NewRay(ctx) + link := ray.New(ctx) go func() { for { data, err := link.OutboundInput().ReadMultiBuffer() diff --git a/transport/internet/udp/hub.go b/transport/internet/udp/hub.go index 5fb267591..d92df47d9 100644 --- a/transport/internet/udp/hub.go +++ b/transport/internet/udp/hub.go @@ -2,7 +2,6 @@ package udp import ( "v2ray.com/core/common/buf" - "v2ray.com/core/common/dice" "v2ray.com/core/common/net" ) @@ -16,71 +15,28 @@ type Payload struct { // PayloadHandler is function to handle Payload. type PayloadHandler func(payload *buf.Buffer, source net.Destination, originalDest net.Destination) -// PayloadQueue is a queue of Payload. -type PayloadQueue struct { - queue []chan Payload - callback PayloadHandler -} +type HubOption func(h *Hub) -// NewPayloadQueue returns a new PayloadQueue. -func NewPayloadQueue(option ListenOption) *PayloadQueue { - queue := &PayloadQueue{ - callback: option.Callback, - queue: make([]chan Payload, option.Concurrency), - } - for i := range queue.queue { - queue.queue[i] = make(chan Payload, 64) - go queue.Dequeue(queue.queue[i]) - } - return queue -} - -// Enqueue adds the payload to the end of this queue. -func (q *PayloadQueue) Enqueue(payload Payload) { - size := len(q.queue) - idx := 0 - if size > 1 { - idx = dice.Roll(size) - } - for i := 0; i < size; i++ { - select { - case q.queue[idx%size] <- payload: - return - default: - idx++ - } +func HubCapacity(cap int) HubOption { + return func(h *Hub) { + h.capacity = cap } } -func (q *PayloadQueue) Dequeue(queue <-chan Payload) { - for payload := range queue { - q.callback(payload.payload, payload.source, payload.originalDest) +func HubReceiveOriginalDestination(r bool) HubOption { + return func(h *Hub) { + h.recvOrigDest = r } } -func (q *PayloadQueue) Close() error { - for _, queue := range q.queue { - close(queue) - } - return nil -} - -type ListenOption struct { - Callback PayloadHandler - ReceiveOriginalDest bool - Concurrency int -} - type Hub struct { - conn *net.UDPConn - queue *PayloadQueue - option ListenOption + conn *net.UDPConn + callback PayloadHandler + capacity int + recvOrigDest bool } -func ListenUDP(address net.Address, port net.Port, option ListenOption) (*Hub, error) { - if option.Concurrency < 1 { - option.Concurrency = 1 - } +func ListenUDP(address net.Address, port net.Port, callback PayloadHandler, options ...HubOption) (*Hub, error) { udpConn, err := net.ListenUDP("udp", &net.UDPAddr{ IP: address.IP(), Port: int(port), @@ -89,7 +45,17 @@ func ListenUDP(address net.Address, port net.Port, option ListenOption) (*Hub, e return nil, err } newError("listening UDP on ", address, ":", port).WriteToLog() - if option.ReceiveOriginalDest { + hub := &Hub{ + conn: udpConn, + capacity: 256, + callback: callback, + recvOrigDest: false, + } + for _, opt := range options { + opt(hub) + } + + if hub.recvOrigDest { rawConn, err := udpConn.SyscallConn() if err != nil { return nil, newError("failed to get fd").Base(err) @@ -103,15 +69,15 @@ func ListenUDP(address net.Address, port net.Port, option ListenOption) (*Hub, e return nil, newError("failed to control socket").Base(err) } } - hub := &Hub{ - conn: udpConn, - queue: NewPayloadQueue(option), - option: option, - } - go hub.start() + + c := make(chan *Payload, hub.capacity) + + go hub.start(c) + go hub.process(c) return hub, nil } +// Close implements net.Listener. func (h *Hub) Close() error { h.conn.Close() return nil @@ -124,7 +90,15 @@ func (h *Hub) WriteTo(payload []byte, dest net.Destination) (int, error) { }) } -func (h *Hub) start() { +func (h *Hub) process(c <-chan *Payload) { + for p := range c { + h.callback(p.payload, p.source, p.originalDest) + } +} + +func (h *Hub) start(c chan<- *Payload) { + defer close(c) + oobBytes := make([]byte, 256) for { @@ -144,18 +118,28 @@ func (h *Hub) start() { break } - payload := Payload{ + payload := &Payload{ payload: buffer, } payload.source = net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)) - if h.option.ReceiveOriginalDest && noob > 0 { + if h.recvOrigDest && noob > 0 { payload.originalDest = RetrieveOriginalDest(oobBytes[:noob]) + if payload.originalDest.IsValid() { + newError("UDP original destination: ", payload.originalDest).AtDebug().WriteToLog() + } else { + newError("failed to read UDP original destination").WriteToLog() + } } - h.queue.Enqueue(payload) + + select { + case c <- payload: + default: + } + } - h.queue.Close() } +// Addr implements net.Listener. func (h *Hub) Addr() net.Addr { return h.conn.LocalAddr() } diff --git a/transport/internet/websocket/config.go b/transport/internet/websocket/config.go index 4469a83a1..ef55adda3 100644 --- a/transport/internet/websocket/config.go +++ b/transport/internet/websocket/config.go @@ -7,7 +7,7 @@ import ( "v2ray.com/core/transport/internet" ) -func (c *Config) GetNormailzedPath() string { +func (c *Config) GetNormalizedPath() string { path := c.Path if len(path) == 0 { return "/" diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index fa9718123..040c98791 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -13,7 +13,7 @@ import ( // Dial dials a WebSocket connection to the given destination. func Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { - newError("creating connection to ", dest).WriteToLog() + newError("creating connection to ", dest).WithContext(ctx).WriteToLog() conn, err := dialWebsocket(ctx, dest) if err != nil { @@ -41,16 +41,16 @@ func dialWebsocket(ctx context.Context, dest net.Destination) (net.Conn, error) protocol := "ws" - if config := tls.ConfigFromContext(ctx, tls.WithDestination(dest)); config != nil { + if config := tls.ConfigFromContext(ctx); config != nil { protocol = "wss" - dialer.TLSClientConfig = config.GetTLSConfig() + dialer.TLSClientConfig = config.GetTLSConfig(tls.WithDestination(dest)) } host := dest.NetAddr() if (protocol == "ws" && dest.Port == 80) || (protocol == "wss" && dest.Port == 443) { host = dest.Address.String() } - uri := protocol + "://" + host + wsSettings.GetNormailzedPath() + uri := protocol + "://" + host + wsSettings.GetNormalizedPath() conn, resp, err := dialer.Dial(uri, wsSettings.GetRequestHeader()) if err != nil { diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go index 36275fe2f..f0a39fc20 100644 --- a/transport/internet/websocket/hub.go +++ b/transport/internet/websocket/hub.go @@ -92,7 +92,7 @@ func (ln *Listener) listenws(address net.Address, port net.Port) error { go func() { err := http.Serve(listener, &requestHandler{ - path: ln.config.GetNormailzedPath(), + path: ln.config.GetNormalizedPath(), ln: ln, }) if err != nil { diff --git a/transport/internet/websocket/ws.go b/transport/internet/websocket/ws.go old mode 100644 new mode 100755 index 7e5f1c7af..e9863b031 --- a/transport/internet/websocket/ws.go +++ b/transport/internet/websocket/ws.go @@ -1,6 +1,6 @@ /*Package websocket implements Websocket transport -Websocket transport implements a HTTP(S) compliable, surveillance proof transport method with plausible deniability. +Websocket transport implements an HTTP(S) compliable, surveillance proof transport method with plausible deniability. */ package websocket diff --git a/transport/ray/connection.go b/transport/ray/connection.go index 2bc4d39b9..27753ac8c 100644 --- a/transport/ray/connection.go +++ b/transport/ray/connection.go @@ -79,7 +79,7 @@ func (c *connection) Write(b []byte) (int, error) { } l := len(b) - mb := buf.NewMultiBufferCap(l/buf.Size + 1) + mb := buf.NewMultiBufferCap(int32(l)/buf.Size + 1) mb.Write(b) return l, c.output.WriteMultiBuffer(mb) } @@ -123,7 +123,7 @@ func (c *connection) SetReadDeadline(t time.Time) error { return nil } -// SetWriteDeadline implement net.Conn.SetWriteDeadline(). +// SetWriteDeadline implements net.Conn.SetWriteDeadline(). func (c *connection) SetWriteDeadline(t time.Time) error { return nil } diff --git a/transport/ray/direct.go b/transport/ray/direct.go index 53af0f4d5..c84b0b227 100644 --- a/transport/ray/direct.go +++ b/transport/ray/direct.go @@ -12,12 +12,38 @@ import ( "v2ray.com/core/common/signal" ) -// NewRay creates a new Ray for direct traffic transport. -func NewRay(ctx context.Context) Ray { - return &directRay{ +type Option func(*directRay) + +type addInt64 interface { + Add(int64) int64 +} + +func WithUplinkStatCounter(c addInt64) Option { + return func(s *directRay) { + s.Input.onDataSize = append(s.Input.onDataSize, func(delta uint64) { + c.Add(int64(delta)) + }) + } +} + +func WithDownlinkStatCounter(c addInt64) Option { + return func(s *directRay) { + s.Output.onDataSize = append(s.Output.onDataSize, func(delta uint64) { + c.Add(int64(delta)) + }) + } +} + +// New creates a new Ray for direct traffic transport. +func New(ctx context.Context, opts ...Option) Ray { + r := &directRay{ Input: NewStream(ctx), Output: NewStream(ctx), } + for _, opt := range opts { + opt(r) + } + return r } type directRay struct { @@ -60,18 +86,20 @@ type Stream struct { ctx context.Context readSignal *signal.Notifier writeSignal *signal.Notifier + onDataSize []func(uint64) close bool err bool } // NewStream creates a new Stream. func NewStream(ctx context.Context) *Stream { - return &Stream{ + s := &Stream{ ctx: ctx, readSignal: signal.NewNotifier(), writeSignal: signal.NewNotifier(), size: 0, } + return s } func (s *Stream) getData() (buf.MultiBuffer, error) { @@ -85,14 +113,14 @@ func (s *Stream) getData() (buf.MultiBuffer, error) { return mb, nil } - if s.close { - return nil, io.EOF - } - if s.err { return nil, io.ErrClosedPipe } + if s.close { + return nil, io.EOF + } + return nil, nil } @@ -121,7 +149,7 @@ func (s *Stream) ReadMultiBuffer() (buf.MultiBuffer, error) { select { case <-s.ctx.Done(): - return nil, io.EOF + return nil, s.ctx.Err() case <-s.writeSignal.Wait(): } } @@ -142,7 +170,7 @@ func (s *Stream) ReadTimeout(timeout time.Duration) (buf.MultiBuffer, error) { select { case <-s.ctx.Done(): - return nil, io.EOF + return nil, s.ctx.Err() case <-time.After(timeout): return nil, buf.ErrReadTimeout case <-s.writeSignal.Wait(): @@ -167,7 +195,7 @@ func (s *Stream) waitForStreamSize() error { for s.Size() >= streamSizeLimit { select { case <-s.ctx.Done(): - return io.ErrClosedPipe + return s.ctx.Err() case <-s.readSignal.Wait(): if s.err || s.close { return io.ErrClosedPipe @@ -201,8 +229,13 @@ func (s *Stream) WriteMultiBuffer(data buf.MultiBuffer) error { s.data = buf.NewMultiBufferCap(128) } + dataSize := uint64(data.Len()) + for _, f := range s.onDataSize { + f(dataSize) + } + s.data.AppendMulti(data) - s.size += uint64(data.Len()) + s.size += dataSize s.writeSignal.Signal() return nil @@ -227,7 +260,9 @@ func (s *Stream) CloseError() { s.data = nil s.size = 0 } + s.access.Unlock() + s.readSignal.Signal() s.writeSignal.Signal() - s.access.Unlock() + } diff --git a/transport/ray/ray.go b/transport/ray/ray.go index 557bc64eb..0c779784c 100644 --- a/transport/ray/ray.go +++ b/transport/ray/ray.go @@ -13,7 +13,7 @@ type OutboundRay interface { // OutboundOutput provides a stream to retrieve the response from the // outbound connection. The outbound connection shall close the channel - // after all responses are receivced and put into the channel. + // after all responses are received and put into the channel. OutboundOutput() OutputStream } @@ -24,13 +24,13 @@ type InboundRay interface { // is received and put into the channel. InboundInput() OutputStream - // InboudBound provides a stream of data for the inbound connection to write + // InboundOutput provides a stream of data for the inbound connection to write // as response. The inbound connection shall write all the data from the // channel until it is closed. InboundOutput() InputStream } -// Ray is an internal tranport channel between inbound and outbound connection. +// Ray is an internal transport channel between inbound and outbound connection. type Ray interface { InboundRay OutboundRay diff --git a/v2ray.go b/v2ray.go old mode 100644 new mode 100755 index a95a14b5e..72c32628c --- a/v2ray.go +++ b/v2ray.go @@ -28,8 +28,7 @@ type Instance struct { router syncRouter ihm syncInboundHandlerManager ohm syncOutboundHandlerManager - clock syncClock - cmd syncCommander + stats syncStatManager access sync.Mutex features []Feature @@ -39,7 +38,7 @@ type Instance struct { // New returns a new V2Ray instance based on given configuration. // The instance is not started at this point. -// To make sure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional. +// To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional. func New(config *Config) (*Instance, error) { var server = &Instance{ id: uuid.New(), @@ -95,7 +94,7 @@ func (s *Instance) CreateObject(config interface{}) (interface{}, error) { return common.CreateObject(ctx, config) } -// ID returns an unique ID for this V2Ray instance. +// ID returns a unique ID for this V2Ray instance. func (s *Instance) ID() uuid.UUID { return s.id } @@ -106,7 +105,7 @@ func (s *Instance) Close() error { defer s.access.Unlock() s.running = false - for _, f := range s.features { + for _, f := range s.allFeatures() { f.Close() } @@ -120,21 +119,23 @@ func (s *Instance) Start() error { defer s.access.Unlock() s.running = true - for _, f := range s.features { + for _, f := range s.allFeatures() { if err := f.Start(); err != nil { return err } } - newError("V2Ray started").AtWarning().WriteToLog() + newError("V2Ray ", Version(), " started").AtWarning().WriteToLog() return nil } // RegisterFeature registers the given feature into V2Ray. -// If feature is one of the following types, the corressponding feature in this Instance +// If feature is one of the following types, the corresponding feature in this Instance // will be replaced: DNSClient, PolicyManager, Router, Dispatcher, InboundHandlerManager, OutboundHandlerManager. func (s *Instance) RegisterFeature(feature interface{}, instance Feature) error { + running := false + switch feature.(type) { case DNSClient, *DNSClient: s.dnsClient.Set(instance.(DNSClient)) @@ -148,21 +149,38 @@ func (s *Instance) RegisterFeature(feature interface{}, instance Feature) error s.ihm.Set(instance.(InboundHandlerManager)) case OutboundHandlerManager, *OutboundHandlerManager: s.ohm.Set(instance.(OutboundHandlerManager)) - case Clock, *Clock: - s.clock.Set(instance.(Clock)) - case Commander, *Commander: - s.cmd.Set(instance.(Commander)) + case StatManager, *StatManager: + s.stats.Set(instance.(StatManager)) + default: + s.access.Lock() + s.features = append(s.features, instance) + running = s.running + s.access.Unlock() } - s.access.Lock() - defer s.access.Unlock() - s.features = append(s.features, instance) - if s.running { + if running { return instance.Start() } return nil } +func (s *Instance) allFeatures() []Feature { + return append([]Feature{s.DNSClient(), s.PolicyManager(), s.Dispatcher(), s.Router(), s.InboundHandlerManager(), s.OutboundHandlerManager(), s.Stats()}, s.features...) +} + +// GetFeature returns a feature that was registered in this Instance. Nil if not found. +// The returned Feature must implement common.HasType and whose type equals to the given feature type. +func (s *Instance) GetFeature(featureType interface{}) Feature { + for _, f := range s.features { + if hasType, ok := f.(common.HasType); ok { + if hasType.Type() == featureType { + return f + } + } + } + return nil +} + // DNSClient returns the DNSClient used by this Instance. The returned DNSClient is always functional. func (s *Instance) DNSClient() DNSClient { return &(s.dnsClient) @@ -193,12 +211,7 @@ func (s *Instance) OutboundHandlerManager() OutboundHandlerManager { return &(s.ohm) } -// Clock returns the Clock used by this Instance. The returned Clock is always functional. -func (s *Instance) Clock() Clock { - return &(s.clock) -} - -// Commander returns the Commander used by this Instance. The returned Commander is always functional. -func (s *Instance) Commander() Commander { - return &(s.cmd) +// Stats returns the StatManager used by this Instance. If StatManager was not registered before, the returned value doesn't work. +func (s *Instance) Stats() StatManager { + return &(s.stats) }