diff --git a/common/loader/json_conf.go b/common/loader/json_conf.go index a9f778366..d4f42e1e4 100644 --- a/common/loader/json_conf.go +++ b/common/loader/json_conf.go @@ -34,28 +34,32 @@ func (this *JSONConfigLoader) LoadWithID(raw []byte, id string) (interface{}, er return config, nil } -func (this *JSONConfigLoader) Load(raw []byte) (interface{}, error) { +func (this *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) { var obj map[string]json.RawMessage if err := json.Unmarshal(raw, &obj); err != nil { - return nil, err + return nil, "", err } rawID, found := obj[this.idKey] if !found { log.Error(this.idKey, " not found in JSON content.") - return nil, ErrConfigIDKeyNotFound + return nil, "", ErrConfigIDKeyNotFound } var id string if err := json.Unmarshal(rawID, &id); err != nil { - return nil, err + return nil, "", err } rawConfig := json.RawMessage(raw) if len(this.configKey) > 0 { configValue, found := obj[this.configKey] if !found { log.Error(this.configKey, " not found in JSON content.") - return nil, ErrConfigIDKeyNotFound + return nil, "", ErrConfigIDKeyNotFound } rawConfig = configValue } - return this.LoadWithID([]byte(rawConfig), id) + config, err := this.LoadWithID([]byte(rawConfig), id) + if err != nil { + return nil, id, err + } + return config, id, nil } diff --git a/common/loader/loader.go b/common/loader/loader.go index e580ce748..b4435e8d4 100644 --- a/common/loader/loader.go +++ b/common/loader/loader.go @@ -15,7 +15,7 @@ type ConfigCreator func() interface{} type ConfigLoader interface { RegisterCreator(string, ConfigCreator) error CreateConfig(string) (interface{}, error) - Load([]byte) (interface{}, error) + Load([]byte) (interface{}, string, error) LoadWithID([]byte, string) (interface{}, error) } diff --git a/proxy/blackhole/config_json.go b/proxy/blackhole/config_json.go index e0050e760..c5277afe8 100644 --- a/proxy/blackhole/config_json.go +++ b/proxy/blackhole/config_json.go @@ -24,7 +24,7 @@ func (this *Config) UnmarshalJSON(data []byte) error { loader := loader.NewJSONConfigLoader("type", "") loader.RegisterCreator("none", func() interface{} { return new(NoneResponse) }) loader.RegisterCreator("http", func() interface{} { return new(HTTPResponse) }) - response, err := loader.Load(jsonConfig.Response) + response, _, err := loader.Load(jsonConfig.Response) if err != nil { return errors.New("Blackhole: Failed to parse response config: " + err.Error()) } diff --git a/transport/internet/authenticator.go b/transport/internet/authenticator.go new file mode 100644 index 000000000..b6eea637e --- /dev/null +++ b/transport/internet/authenticator.go @@ -0,0 +1,87 @@ +package internet + +import ( + "errors" + + "github.com/v2ray/v2ray-core/common/alloc" + "github.com/v2ray/v2ray-core/common/loader" +) + +type Authenticator interface { + Seal(*alloc.Buffer) + Open(*alloc.Buffer) bool + Overhead() int +} + +type AuthenticatorFactory interface { + Create(AuthenticatorConfig) Authenticator +} + +type AuthenticatorConfig interface { +} + +var ( + ErrDuplicatedAuthenticator = errors.New("Authenticator already registered.") + ErrAuthenticatorNotFound = errors.New("Authenticator not found.") + + authenticatorCache = make(map[string]AuthenticatorFactory) + configCache loader.ConfigLoader +) + +func RegisterAuthenticator(name string, factory AuthenticatorFactory, configCreator loader.ConfigCreator) error { + if _, found := authenticatorCache[name]; found { + return ErrDuplicatedAuthenticator + } + authenticatorCache[name] = factory + return configCache.RegisterCreator(name, configCreator) +} + +func CreateAuthenticator(name string, config AuthenticatorConfig) (Authenticator, error) { + factory, found := authenticatorCache[name] + if !found { + return nil, ErrAuthenticatorNotFound + } + return factory.Create(config.(AuthenticatorConfig)), nil +} + +func CreateAuthenticatorConfig(rawConfig []byte) (string, AuthenticatorConfig, error) { + config, name, err := configCache.Load(rawConfig) + if err != nil { + return name, nil, err + } + return name, config, nil +} + +type AuthenticatorChain struct { + authenticators []Authenticator +} + +func NewAuthenticatorChain(auths ...Authenticator) Authenticator { + return &AuthenticatorChain{ + authenticators: auths, + } +} + +func (this *AuthenticatorChain) Overhead() int { + total := 0 + for _, auth := range this.authenticators { + total += auth.Overhead() + } + return total +} + +func (this *AuthenticatorChain) Open(payload *alloc.Buffer) bool { + for _, auth := range this.authenticators { + if !auth.Open(payload) { + return false + } + } + return true +} + +func (this *AuthenticatorChain) Seal(payload *alloc.Buffer) { + for i := len(this.authenticators) - 1; i >= 0; i-- { + auth := this.authenticators[i] + auth.Seal(payload) + } +} diff --git a/transport/internet/authenticator_json.go b/transport/internet/authenticator_json.go new file mode 100644 index 000000000..200309c33 --- /dev/null +++ b/transport/internet/authenticator_json.go @@ -0,0 +1,9 @@ +// +build json + +package internet + +import "github.com/v2ray/v2ray-core/common/loader" + +func init() { + configCache = loader.NewJSONConfigLoader("type", "") +} diff --git a/transport/internet/internal/obsrtp/obsrtp.go b/transport/internet/internal/obsrtp/obsrtp.go new file mode 100644 index 000000000..1c0e5bd5c --- /dev/null +++ b/transport/internet/internal/obsrtp/obsrtp.go @@ -0,0 +1,51 @@ +package obsrtp + +import ( + "math/rand" + + "github.com/v2ray/v2ray-core/common/alloc" + "github.com/v2ray/v2ray-core/transport/internet" +) + +type Config struct { + Version byte + Padding bool + Extension bool + CSRCCount byte + Marker bool + PayloadType byte +} + +type ObfuscatorSRTP struct { + header uint16 + number uint16 +} + +func (this *ObfuscatorSRTP) Overhead() int { + return 4 +} + +func (this *ObfuscatorSRTP) Open(payload *alloc.Buffer) bool { + payload.SliceFrom(this.Overhead()) + return true +} + +func (this *ObfuscatorSRTP) Seal(payload *alloc.Buffer) { + this.number++ + payload.PrependUint16(this.number) + payload.PrependUint16(this.header) +} + +type ObfuscatorSRTPFactory struct { +} + +func (this ObfuscatorSRTPFactory) Create(rawSettings internet.AuthenticatorConfig) internet.Authenticator { + return &ObfuscatorSRTP{ + header: 0xB5E8, + number: uint16(rand.Intn(65536)), + } +} + +func init() { + internet.RegisterAuthenticator("srtp", ObfuscatorSRTPFactory{}, func() interface{} { return new(Config) }) +} diff --git a/transport/internet/kcp/config.go b/transport/internet/kcp/config.go index 8292fe702..18b86fdf6 100644 --- a/transport/internet/kcp/config.go +++ b/transport/internet/kcp/config.go @@ -1,5 +1,9 @@ package kcp +import ( + "github.com/v2ray/v2ray-core/transport/internet" +) + type Config struct { Mtu uint32 // Maximum transmission unit Tti uint32 @@ -8,12 +12,26 @@ type Config struct { Congestion bool WriteBuffer uint32 ReadBuffer uint32 + HeaderType string + HeaderConfig internet.AuthenticatorConfig } func (this *Config) Apply() { effectiveConfig = *this } +func (this *Config) GetAuthenticator() (internet.Authenticator, error) { + auth := NewSimpleAuthenticator() + if this.HeaderConfig != nil { + header, err := internet.CreateAuthenticator(this.HeaderType, this.HeaderConfig) + if err != nil { + return nil, err + } + auth = internet.NewAuthenticatorChain(header, auth) + } + return auth, nil +} + func (this *Config) GetSendingInFlightSize() uint32 { size := this.UplinkCapacity * 1024 * 1024 / this.Mtu / (1000 / this.Tti) / 2 if size == 0 { diff --git a/transport/internet/kcp/config_json.go b/transport/internet/kcp/config_json.go index 26f53e742..0ead70a9c 100644 --- a/transport/internet/kcp/config_json.go +++ b/transport/internet/kcp/config_json.go @@ -11,13 +11,14 @@ import ( func (this *Config) UnmarshalJSON(data []byte) error { type JSONConfig struct { - Mtu *uint32 `json:"mtu"` - Tti *uint32 `json:"tti"` - UpCap *uint32 `json:"uplinkCapacity"` - DownCap *uint32 `json:"downlinkCapacity"` - Congestion *bool `json:"congestion"` - ReadBufferSize *uint32 `json:"readBufferSize"` - WriteBufferSize *uint32 `json:"writeBufferSize"` + Mtu *uint32 `json:"mtu"` + Tti *uint32 `json:"tti"` + UpCap *uint32 `json:"uplinkCapacity"` + DownCap *uint32 `json:"downlinkCapacity"` + Congestion *bool `json:"congestion"` + ReadBufferSize *uint32 `json:"readBufferSize"` + WriteBufferSize *uint32 `json:"writeBufferSize"` + HeaderConfig json.RawMessage `json:"header"` } jsonConfig := new(JSONConfig) if err := json.Unmarshal(data, &jsonConfig); err != nil { @@ -64,6 +65,9 @@ func (this *Config) UnmarshalJSON(data []byte) error { this.WriteBuffer = 512 * 1024 } } + if len(jsonConfig.HeaderConfig) > 0 { + this.HeaderConfig = jsonConfig.HeaderConfig + } return nil } diff --git a/transport/internet/kcp/connection.go b/transport/internet/kcp/connection.go index 3e3d88f26..3194af60b 100644 --- a/transport/internet/kcp/connection.go +++ b/transport/internet/kcp/connection.go @@ -10,6 +10,7 @@ import ( "github.com/v2ray/v2ray-core/common/alloc" "github.com/v2ray/v2ray-core/common/log" + "github.com/v2ray/v2ray-core/transport/internet" ) var ( @@ -121,7 +122,7 @@ func (this *RoundTripInfo) SmoothedTime() uint32 { // Connection is a KCP connection over UDP. type Connection struct { - block Authenticator + block internet.Authenticator local, remote net.Addr rd time.Time wd time.Time // write deadline @@ -149,7 +150,7 @@ type Connection struct { } // NewConnection create a new KCP connection between local and remote. -func NewConnection(conv uint16, writerCloser io.WriteCloser, local *net.UDPAddr, remote *net.UDPAddr, block Authenticator) *Connection { +func NewConnection(conv uint16, writerCloser io.WriteCloser, local *net.UDPAddr, remote *net.UDPAddr, block internet.Authenticator) *Connection { log.Info("KCP|Connection: creating connection ", conv) conn := new(Connection) diff --git a/transport/internet/kcp/connection_test.go b/transport/internet/kcp/connection_test.go index cfbabc2cc..6f3cfb000 100644 --- a/transport/internet/kcp/connection_test.go +++ b/transport/internet/kcp/connection_test.go @@ -9,6 +9,8 @@ import ( v2net "github.com/v2ray/v2ray-core/common/net" "github.com/v2ray/v2ray-core/testing/assert" + "github.com/v2ray/v2ray-core/transport/internet" + "github.com/v2ray/v2ray-core/transport/internet/internal/obsrtp" . "github.com/v2ray/v2ray-core/transport/internet/kcp" ) @@ -40,10 +42,12 @@ func TestConnectionReadWrite(t *testing.T) { upReader, upWriter := io.Pipe() downReader, downWriter := io.Pipe() - connClient := NewConnection(1, upWriter, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 1}, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 2}, NewSimpleAuthenticator()) + auth := internet.NewAuthenticatorChain(obsrtp.ObfuscatorSRTPFactory{}.Create(nil), NewSimpleAuthenticator()) + + connClient := NewConnection(1, upWriter, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 1}, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 2}, auth) connClient.FetchInputFrom(downReader) - connServer := NewConnection(1, downWriter, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 2}, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 1}, NewSimpleAuthenticator()) + connServer := NewConnection(1, downWriter, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 2}, &net.UDPAddr{IP: v2net.LocalHostIP.IP(), Port: 1}, auth) connServer.FetchInputFrom(upReader) totalWritten := 1024 * 1024 diff --git a/transport/internet/kcp/crypt.go b/transport/internet/kcp/crypt.go index 6b8f2754c..cdb4b1f1b 100644 --- a/transport/internet/kcp/crypt.go +++ b/transport/internet/kcp/crypt.go @@ -5,26 +5,16 @@ import ( "github.com/v2ray/v2ray-core/common/alloc" "github.com/v2ray/v2ray-core/common/serial" + "github.com/v2ray/v2ray-core/transport/internet" ) -type Authenticator interface { - HeaderSize() int - // Encrypt encrypts the whole block in src into dst. - // Dst and src may point at the same memory. - Seal(buffer *alloc.Buffer) - - // Decrypt decrypts the whole block in src into dst. - // Dst and src may point at the same memory. - Open(buffer *alloc.Buffer) bool -} - type SimpleAuthenticator struct{} -func NewSimpleAuthenticator() Authenticator { +func NewSimpleAuthenticator() internet.Authenticator { return &SimpleAuthenticator{} } -func (this *SimpleAuthenticator) HeaderSize() int { +func (this *SimpleAuthenticator) Overhead() int { return 6 } diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index c21ee7808..f66520f31 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -22,7 +22,10 @@ func DialKCP(src v2net.Address, dest v2net.Destination) (internet.Connection, er return nil, err } - cpip := NewSimpleAuthenticator() + cpip, err := effectiveConfig.GetAuthenticator() + if err != nil { + return nil, err + } conv := uint16(atomic.AddUint32(&globalConv, 1)) session := NewConnection(conv, conn, conn.LocalAddr().(*net.UDPAddr), conn.RemoteAddr().(*net.UDPAddr), cpip) session.FetchInputFrom(conn) diff --git a/transport/internet/kcp/listener.go b/transport/internet/kcp/listener.go index 1f82ca6fb..0099c0818 100644 --- a/transport/internet/kcp/listener.go +++ b/transport/internet/kcp/listener.go @@ -17,7 +17,7 @@ import ( type Listener struct { sync.Mutex running bool - block Authenticator + authenticator internet.Authenticator sessions map[string]*Connection awaitingConns chan *Connection hub *udp.UDPHub @@ -25,8 +25,12 @@ type Listener struct { } func NewListener(address v2net.Address, port v2net.Port) (*Listener, error) { + auth, err := effectiveConfig.GetAuthenticator() + if err != nil { + return nil, err + } l := &Listener{ - block: NewSimpleAuthenticator(), + authenticator: auth, sessions: make(map[string]*Connection), awaitingConns: make(chan *Connection, 64), localAddr: &net.UDPAddr{ @@ -47,7 +51,7 @@ func NewListener(address v2net.Address, port v2net.Port) (*Listener, error) { func (this *Listener) OnReceive(payload *alloc.Buffer, src v2net.Destination) { defer payload.Release() - if valid := this.block.Open(payload); !valid { + if valid := this.authenticator.Open(payload); !valid { log.Info("KCP|Listener: discarding invalid payload from ", src) return } @@ -81,7 +85,11 @@ func (this *Listener) OnReceive(payload *alloc.Buffer, src v2net.Destination) { IP: src.Address().IP(), Port: int(src.Port()), } - conn = NewConnection(conv, writer, this.localAddr, srcAddr, this.block) + auth, err := effectiveConfig.GetAuthenticator() + if err != nil { + log.Error("KCP|Listener: Failed to create authenticator: ", err) + } + conn = NewConnection(conv, writer, this.localAddr, srcAddr, auth) select { case this.awaitingConns <- conn: case <-time.After(time.Second * 5): diff --git a/transport/internet/kcp/output.go b/transport/internet/kcp/output.go index 16867dca3..d9642f27b 100644 --- a/transport/internet/kcp/output.go +++ b/transport/internet/kcp/output.go @@ -6,6 +6,7 @@ import ( "github.com/v2ray/v2ray-core/common/alloc" v2io "github.com/v2ray/v2ray-core/common/io" + "github.com/v2ray/v2ray-core/transport/internet" ) type SegmentWriter interface { @@ -59,7 +60,7 @@ func (this *BufferedSegmentWriter) Flush() { } type AuthenticationWriter struct { - Authenticator Authenticator + Authenticator internet.Authenticator Writer io.Writer } @@ -74,5 +75,5 @@ func (this *AuthenticationWriter) Write(payload *alloc.Buffer) error { func (this *AuthenticationWriter) Release() {} func (this *AuthenticationWriter) Mtu() uint32 { - return effectiveConfig.Mtu - uint32(this.Authenticator.HeaderSize()) + return effectiveConfig.Mtu - uint32(this.Authenticator.Overhead()) }