Browse Source

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

Darien Raymond 8 years ago
parent
commit
723207158f

+ 1 - 0
main/distro/all/all.go

@@ -27,4 +27,5 @@ import (
 	_ "v2ray.com/core/transport/internet/headers/noop"
 	_ "v2ray.com/core/transport/internet/headers/srtp"
 	_ "v2ray.com/core/transport/internet/headers/utp"
+	_ "v2ray.com/core/transport/internet/headers/wechat"
 )

+ 279 - 0
proxy/socks/protocol.go

@@ -0,0 +1,279 @@
+package socks
+
+import (
+	"io"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/errors"
+	v2net "v2ray.com/core/common/net"
+	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/serial"
+	"v2ray.com/core/proxy"
+)
+
+const (
+	socks5Version = 0x05
+	socks4Version = 0x04
+
+	cmdTCPConnect = 0x01
+	cmdTCPBind    = 0x02
+	cmdUDPPort    = 0x03
+
+	socks4RequestGranted  = 90
+	socks4RequestRejected = 91
+
+	authNotRequired      = 0x00
+	authGssAPI           = 0x01
+	authPassword         = 0x02
+	authNoMatchingMethod = 0xFF
+
+	addrTypeIPv4   = 0x01
+	addrTypeIPv6   = 0x04
+	addrTypeDomain = 0x03
+
+	statusSuccess       = 0x00
+	statusCmdNotSupport = 0x07
+)
+
+type ServerSession struct {
+	config *ServerConfig
+	meta   *proxy.InboundHandlerMeta
+}
+
+func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
+	buffer := buf.NewLocal(512)
+	request := new(protocol.RequestHeader)
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+		return nil, errors.Base(err).Message("Socks|Server: Insufficient header.")
+	}
+
+	version := buffer.Byte(0)
+	if version == socks4Version {
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 6)); err != nil {
+			return nil, errors.Base(err).Message("Socks|Server: Insufficient header.")
+		}
+		port := v2net.PortFromBytes(buffer.BytesRange(2, 4))
+		address := v2net.IPAddress(buffer.BytesRange(4, 8))
+		_, err := readUntilNull(reader) // user id
+		if err != nil {
+			return nil, err
+		}
+		if address.IP()[0] == 0x00 {
+			domain, err := readUntilNull(reader)
+			if err != nil {
+				return nil, errors.Base(err).Message("Socks|Server: Failed to read domain for socks 4a.")
+			}
+			address = v2net.DomainAddress(domain)
+		}
+
+		switch buffer.Byte(1) {
+		case cmdTCPConnect:
+			request.Command = protocol.RequestCommandTCP
+			request.Address = address
+			request.Port = port
+			request.Version = socks4Version
+			if err := writeSocks4Response(writer, socks4RequestGranted, address, port); err != nil {
+				return nil, err
+			}
+			return request, nil
+		default:
+			writeSocks4Response(writer, socks4RequestRejected, address, port)
+			return nil, errors.New("Socks|Server: Unsupported command: ", buffer.Byte(1))
+		}
+	}
+
+	if version == socks5Version {
+		nMethod := int(buffer.Byte(1))
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nMethod)); err != nil {
+			return nil, err
+		}
+
+		var expectedAuth byte = authNotRequired
+		if len(s.config.Accounts) > 0 {
+			expectedAuth = authPassword
+		}
+
+		if !hasAuthMethod(expectedAuth, buffer.BytesRange(2, 2+nMethod)) {
+			writeSocks5AuthenticationResponse(writer, authNoMatchingMethod)
+			return nil, errors.New("Socks|Server: No matching auth method.")
+		}
+
+		if expectedAuth == authPassword {
+			username, password, err := readUsernamePassword(reader)
+			if err != nil {
+				return nil, errors.Base(err).Message("Socks|Server: Failed to read username or password.")
+			}
+			if !s.validate(username, password) {
+				writeSocks5AuthenticationResponse(writer, 0xFF)
+				return nil, errors.Base(err).Message("Socks|Server: Invalid username or password.")
+			}
+		}
+
+		if err := writeSocks5AuthenticationResponse(writer, 0x00); err != nil {
+			return nil, err
+		}
+
+		buffer.Clear()
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil {
+			return nil, err
+		}
+
+		cmd := buffer.Byte(1)
+		if cmd == cmdTCPBind || (cmd == cmdUDPPort && !s.config.UdpEnabled) {
+			writeSocks5Response(writer, statusCmdNotSupport, v2net.AnyIP, v2net.Port(0))
+			return nil, errors.New("Socks|Server: Unsupported command: ", cmd)
+		}
+
+		switch cmd {
+		case cmdTCPConnect:
+			request.Command = protocol.RequestCommandTCP
+		case cmdUDPPort:
+			request.Command = protocol.RequestCommandUDP
+		}
+
+		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 = v2net.IPAddress(buffer.Bytes())
+		case addrTypeIPv6:
+			if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil {
+				return nil, err
+			}
+			request.Address = v2net.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 = v2net.DomainAddress(string(buffer.BytesFrom(-domainLength)))
+		default:
+			return nil, errors.New("Socks|Server: Unknown address type: ", addrType)
+		}
+
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+			return nil, err
+		}
+		request.Port = v2net.PortFromBytes(buffer.BytesFrom(-2))
+
+		responseAddress := v2net.AnyIP
+		responsePort := v2net.Port(1717)
+		if request.Command == protocol.RequestCommandUDP {
+			addr := s.config.Address.AsAddress()
+			if addr == nil {
+				addr = v2net.LocalHostIP
+			}
+			responseAddress = addr
+			responsePort = s.meta.Port
+		}
+		if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {
+			return nil, err
+		}
+
+		return request, nil
+	}
+
+	return nil, errors.New("Socks|Server: Unknown Socks version: ", version)
+}
+
+func (s *ServerSession) validate(username, password string) bool {
+	p, found := s.config.Accounts[username]
+	return found && p == password
+}
+
+func readUsernamePassword(reader io.Reader) (string, string, error) {
+	buffer := buf.NewLocal(512)
+	defer buffer.Release()
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+		return "", "", err
+	}
+	nUsername := int(buffer.Byte(1))
+
+	buffer.Clear()
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nUsername)); err != nil {
+		return "", "", err
+	}
+	username := buffer.String()
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+		return "", "", err
+	}
+	nPassword := int(buffer.Byte(0))
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nPassword)); err != nil {
+		return "", "", err
+	}
+	password := buffer.String()
+	return username, password, nil
+}
+
+func readUntilNull(reader io.Reader) (string, error) {
+	var b [256]byte
+	size := 0
+	for {
+		_, err := reader.Read(b[size : size+1])
+		if err != nil {
+			return "", err
+		}
+		if b[size] == 0x00 {
+			return string(b[:size]), nil
+		}
+		size++
+		if size == 256 {
+			return "", errors.New("Socks|Server: Buffer overrun.")
+		}
+	}
+}
+
+func hasAuthMethod(expectedAuth byte, authCandidates []byte) bool {
+	for _, a := range authCandidates {
+		if a == expectedAuth {
+			return true
+		}
+	}
+	return false
+}
+
+func writeSocks5AuthenticationResponse(writer io.Writer, auth byte) error {
+	_, err := writer.Write([]byte{socks5Version, auth})
+	return err
+}
+
+func writeSocks5Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error {
+	buffer := buf.NewLocal(64)
+	buffer.AppendBytes(socks5Version, errCode, 0x00 /* reserved */)
+	switch address.Family() {
+	case v2net.AddressFamilyIPv4:
+		buffer.AppendBytes(0x01)
+		buffer.Append(address.IP())
+	case v2net.AddressFamilyIPv6:
+		buffer.AppendBytes(0x04)
+		buffer.Append(address.IP())
+	case v2net.AddressFamilyDomain:
+		buffer.AppendBytes(0x03, byte(len(address.Domain())))
+		buffer.AppendSupplier(serial.WriteString(address.Domain()))
+	}
+	buffer.AppendSupplier(serial.WriteUint16(port.Value()))
+
+	_, err := writer.Write(buffer.Bytes())
+	return err
+}
+
+func writeSocks4Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error {
+	buffer := buf.NewLocal(32)
+	buffer.AppendBytes(0x00, errCode)
+	buffer.AppendSupplier(serial.WriteUint16(port.Value()))
+	buffer.Append(address.IP())
+	_, err := writer.Write(buffer.Bytes())
+	return err
+}

+ 23 - 170
proxy/socks/server.go

@@ -10,23 +10,17 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/bufio"
-	"v2ray.com/core/common/crypto"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
+	proto "v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
-	"v2ray.com/core/proxy/socks/protocol"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet/udp"
 )
 
-var (
-	ErrUnsupportedSocksCommand = errors.New("Unsupported socks command.")
-	ErrUnsupportedAuthMethod   = errors.New("Unsupported auth method.")
-)
-
 // Server is a SOCKS 5 proxy server
 type Server struct {
 	tcpMutex         sync.RWMutex
@@ -79,7 +73,7 @@ func (v *Server) Close() {
 	}
 }
 
-// Listen implements InboundHandler.Listen().
+// Start implements InboundHandler.Start().
 func (v *Server) Start() error {
 	if v.accepting {
 		return nil
@@ -113,153 +107,41 @@ func (v *Server) handleConnection(connection internet.Connection) {
 	reader := bufio.NewReader(timedReader)
 	defer reader.Release()
 
-	writer := bufio.NewWriter(connection)
-	defer writer.Release()
-
-	auth, auth4, err := protocol.ReadAuthentication(reader)
-	if err != nil && errors.Cause(err) != protocol.Socks4Downgrade {
-		if errors.Cause(err) != io.EOF {
-			log.Warning("Socks: failed to read authentication: ", err)
-		}
-		return
+	session := &ServerSession{
+		config: v.config,
+		meta:   v.meta,
 	}
 
 	clientAddr := v2net.DestinationFromAddr(connection.RemoteAddr())
-	if err != nil && err == protocol.Socks4Downgrade {
-		v.handleSocks4(clientAddr, reader, writer, auth4)
-	} else {
-		v.handleSocks5(clientAddr, reader, writer, auth)
-	}
-}
-
-func (v *Server) handleSocks5(clientAddr v2net.Destination, reader *bufio.BufferedReader, writer *bufio.BufferedWriter, auth protocol.Socks5AuthenticationRequest) error {
-	expectedAuthMethod := protocol.AuthNotRequired
-	if v.config.AuthType == AuthType_PASSWORD {
-		expectedAuthMethod = protocol.AuthUserPass
-	}
-
-	if !auth.HasAuthMethod(expectedAuthMethod) {
-		authResponse := protocol.NewAuthenticationResponse(protocol.AuthNoMatchingMethod)
-		err := protocol.WriteAuthentication(writer, authResponse)
-		writer.Flush()
-		if err != nil {
-			log.Warning("Socks: failed to write authentication: ", err)
-			return err
-		}
-		log.Warning("Socks: client doesn't support any allowed auth methods.")
-		return ErrUnsupportedAuthMethod
-	}
-
-	authResponse := protocol.NewAuthenticationResponse(expectedAuthMethod)
-	protocol.WriteAuthentication(writer, authResponse)
-	err := writer.Flush()
-	if err != nil {
-		log.Error("Socks: failed to write authentication: ", err)
-		return err
-	}
-	if v.config.AuthType == AuthType_PASSWORD {
-		upRequest, err := protocol.ReadUserPassRequest(reader)
-		if err != nil {
-			log.Warning("Socks: failed to read username and password: ", err)
-			return err
-		}
-		status := byte(0)
-		if !v.config.HasAccount(upRequest.Username(), upRequest.Password()) {
-			status = byte(0xFF)
-		}
-		upResponse := protocol.NewSocks5UserPassResponse(status)
-		err = protocol.WriteUserPassResponse(writer, upResponse)
-		writer.Flush()
-		if err != nil {
-			log.Error("Socks: failed to write user pass response: ", err)
-			return err
-		}
-		if status != byte(0) {
-			log.Warning("Socks: Invalid user account: ", upRequest.AuthDetail())
-			log.Access(clientAddr, "", log.AccessRejected, crypto.ErrAuthenticationFailed)
-			return crypto.ErrAuthenticationFailed
-		}
-	}
 
-	request, err := protocol.ReadRequest(reader)
+	request, err := session.Handshake(reader, connection)
 	if err != nil {
-		log.Warning("Socks: failed to read request: ", err)
-		return err
-	}
-
-	if request.Command == protocol.CmdUdpAssociate && v.config.UdpEnabled {
-		return v.handleUDP(reader, writer)
+		log.Access(clientAddr, "", log.AccessRejected, err)
+		log.Info("Socks|Server: Failed to read request: ", err)
+		return
 	}
 
-	if request.Command == protocol.CmdBind || request.Command == protocol.CmdUdpAssociate {
-		response := protocol.NewSocks5Response()
-		response.Error = protocol.ErrorCommandNotSupported
-		response.Port = v2net.Port(0)
-		response.SetIPv4([]byte{0, 0, 0, 0})
-
-		response.Write(writer)
-		writer.Flush()
-		if err != nil {
-			log.Error("Socks: failed to write response: ", err)
-			return err
+	if request.Command == proto.RequestCommandTCP {
+		dest := request.Destination()
+		session := &proxy.SessionInfo{
+			Source:      clientAddr,
+			Destination: dest,
+			Inbound:     v.meta,
 		}
-		log.Warning("Socks: Unsupported socks command ", request.Command)
-		return ErrUnsupportedSocksCommand
-	}
-
-	response := protocol.NewSocks5Response()
-	response.Error = protocol.ErrorSuccess
+		log.Info("Socks|Server: TCP Connect request to ", dest)
+		log.Access(clientAddr, dest, log.AccessAccepted, "")
 
-	// Some SOCKS software requires a value other than dest. Let's fake one:
-	response.Port = v2net.Port(1717)
-	response.SetIPv4([]byte{0, 0, 0, 0})
-
-	response.Write(writer)
-	if err != nil {
-		log.Error("Socks: failed to write response: ", err)
-		return err
+		v.transport(reader, connection, session)
+		return
 	}
 
-	reader.SetBuffered(false)
-	writer.SetBuffered(false)
-
-	dest := request.Destination()
-	session := &proxy.SessionInfo{
-		Source:      clientAddr,
-		Destination: dest,
-		Inbound:     v.meta,
+	if request.Command == proto.RequestCommandUDP {
+		v.handleUDP()
+		return
 	}
-	log.Info("Socks: TCP Connect request to ", dest)
-	log.Access(clientAddr, dest, log.AccessAccepted, "")
-
-	v.transport(reader, writer, session)
-	return nil
 }
 
-func (v *Server) handleUDP(reader io.Reader, writer *bufio.BufferedWriter) error {
-	response := protocol.NewSocks5Response()
-	response.Error = protocol.ErrorSuccess
-
-	udpAddr := v.udpAddress
-
-	response.Port = udpAddr.Port
-	switch udpAddr.Address.Family() {
-	case v2net.AddressFamilyIPv4:
-		response.SetIPv4(udpAddr.Address.IP())
-	case v2net.AddressFamilyIPv6:
-		response.SetIPv6(udpAddr.Address.IP())
-	case v2net.AddressFamilyDomain:
-		response.SetDomain(udpAddr.Address.Domain())
-	}
-
-	response.Write(writer)
-	err := writer.Flush()
-
-	if err != nil {
-		log.Error("Socks: failed to write response: ", err)
-		return err
-	}
-
+func (v *Server) handleUDP() error {
 	// The TCP connection closes after v method returns. We need to wait until
 	// the client closes it.
 	// TODO: get notified from UDP part
@@ -268,35 +150,6 @@ func (v *Server) handleUDP(reader io.Reader, writer *bufio.BufferedWriter) error
 	return nil
 }
 
-func (v *Server) handleSocks4(clientAddr v2net.Destination, reader *bufio.BufferedReader, writer *bufio.BufferedWriter, auth protocol.Socks4AuthenticationRequest) error {
-	result := protocol.Socks4RequestGranted
-	if auth.Command == protocol.CmdBind {
-		result = protocol.Socks4RequestRejected
-	}
-	socks4Response := protocol.NewSocks4AuthenticationResponse(result, auth.Port, auth.IP[:])
-
-	socks4Response.Write(writer)
-
-	if result == protocol.Socks4RequestRejected {
-		log.Warning("Socks: Unsupported socks 4 command ", auth.Command)
-		log.Access(clientAddr, "", log.AccessRejected, ErrUnsupportedSocksCommand)
-		return ErrUnsupportedSocksCommand
-	}
-
-	reader.SetBuffered(false)
-	writer.SetBuffered(false)
-
-	dest := v2net.TCPDestination(v2net.IPAddress(auth.IP[:]), auth.Port)
-	session := &proxy.SessionInfo{
-		Source:      clientAddr,
-		Destination: dest,
-		Inbound:     v.meta,
-	}
-	log.Access(clientAddr, dest, log.AccessAccepted, "")
-	v.transport(reader, writer, session)
-	return nil
-}
-
 func (v *Server) transport(reader io.Reader, writer io.Writer, session *proxy.SessionInfo) {
 	ray := v.packetDispatcher.DispatchToOutbound(session)
 	input := ray.InboundInput()

+ 7 - 0
tools/conf/transport_authenticators.go

@@ -7,6 +7,7 @@ import (
 	"v2ray.com/core/transport/internet/headers/noop"
 	"v2ray.com/core/transport/internet/headers/srtp"
 	"v2ray.com/core/transport/internet/headers/utp"
+	"v2ray.com/core/transport/internet/headers/wechat"
 )
 
 type NoOpAuthenticator struct{}
@@ -33,6 +34,12 @@ func (UTPAuthenticator) Build() (*serial.TypedMessage, error) {
 	return serial.ToTypedMessage(new(utp.Config)), nil
 }
 
+type WechatVideoAuthenticator struct{}
+
+func (WechatVideoAuthenticator) Build() (*serial.TypedMessage, error) {
+	return serial.ToTypedMessage(new(wechat.VideoConfig)), nil
+}
+
 type HTTPAuthenticatorRequest struct {
 	Version string                 `json:"version"`
 	Method  string                 `json:"method"`

+ 4 - 3
tools/conf/transport_internet.go

@@ -17,9 +17,10 @@ import (
 
 var (
 	kcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
-		"none": func() interface{} { return new(NoOpAuthenticator) },
-		"srtp": func() interface{} { return new(SRTPAuthenticator) },
-		"utp":  func() interface{} { return new(UTPAuthenticator) },
+		"none":         func() interface{} { return new(NoOpAuthenticator) },
+		"srtp":         func() interface{} { return new(SRTPAuthenticator) },
+		"utp":          func() interface{} { return new(UTPAuthenticator) },
+		"wechat-video": func() interface{} { return new(WechatVideoAuthenticator) },
 	}, "type", "")
 
 	tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{

+ 29 - 30
transport/internet/headers/http/http.go

@@ -109,15 +109,15 @@ type HttpConn struct {
 	readBuffer    *buf.Buffer
 	oneTimeReader Reader
 	oneTimeWriter Writer
-	isServer      bool
+	errorWriter   Writer
 }
 
-func NewHttpConn(conn net.Conn, reader Reader, writer Writer, isServer bool) *HttpConn {
+func NewHttpConn(conn net.Conn, reader Reader, writer Writer, errorWriter Writer) *HttpConn {
 	return &HttpConn{
 		Conn:          conn,
 		oneTimeReader: reader,
 		oneTimeWriter: writer,
-		isServer:      isServer,
+		errorWriter:   errorWriter,
 	}
 }
 
@@ -157,33 +157,10 @@ func (v *HttpConn) Write(b []byte) (int, error) {
 
 // Close implements net.Conn.Close().
 func (v *HttpConn) Close() error {
-	if v.isServer && v.oneTimeWriter != nil {
+	if v.oneTimeWriter != nil && v.errorWriter != nil {
 		// Connection is being closed but header wasn't sent. This means the client request
 		// is probably not valid. Sending back a server error header in this case.
-		writer := formResponseHeader(&ResponseConfig{
-			Version: &Version{
-				Value: "1.1",
-			},
-			Status: &Status{
-				Code:   "500",
-				Reason: "Internal Server Error",
-			},
-			Header: []*Header{
-				{
-					Name:  "Connection",
-					Value: []string{"close"},
-				},
-				{
-					Name:  "Cache-Control",
-					Value: []string{"private"},
-				},
-				{
-					Name:  "Content-Length",
-					Value: []string{"0"},
-				},
-			},
-		})
-		writer.Write(v.Conn)
+		v.errorWriter.Write(v.Conn)
 	}
 
 	return v.Conn.Close()
@@ -248,14 +225,36 @@ func (v HttpAuthenticator) Client(conn net.Conn) net.Conn {
 	if v.config.Response != nil {
 		writer = v.GetClientWriter()
 	}
-	return NewHttpConn(conn, reader, writer, false)
+	return NewHttpConn(conn, reader, writer, new(NoOpWriter))
 }
 
 func (v HttpAuthenticator) Server(conn net.Conn) net.Conn {
 	if v.config.Request == nil && v.config.Response == nil {
 		return conn
 	}
-	return NewHttpConn(conn, new(HeaderReader), v.GetServerWriter(), true)
+	return NewHttpConn(conn, new(HeaderReader), v.GetServerWriter(), formResponseHeader(&ResponseConfig{
+		Version: &Version{
+			Value: "1.1",
+		},
+		Status: &Status{
+			Code:   "500",
+			Reason: "Internal Server Error",
+		},
+		Header: []*Header{
+			{
+				Name:  "Connection",
+				Value: []string{"close"},
+			},
+			{
+				Name:  "Cache-Control",
+				Value: []string{"private"},
+			},
+			{
+				Name:  "Content-Length",
+				Value: []string{"0"},
+			},
+		},
+	}))
 }
 
 type HttpAuthenticatorFactory struct{}

+ 47 - 0
transport/internet/headers/wechat/config.pb.go

@@ -0,0 +1,47 @@
+package wechat
+
+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 VideoConfig struct {
+}
+
+func (m *VideoConfig) Reset()                    { *m = VideoConfig{} }
+func (m *VideoConfig) String() string            { return proto.CompactTextString(m) }
+func (*VideoConfig) ProtoMessage()               {}
+func (*VideoConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func init() {
+	proto.RegisterType((*VideoConfig)(nil), "v2ray.core.transport.internet.headers.wechat.VideoConfig")
+}
+
+func init() {
+	proto.RegisterFile("v2ray.com/core/transport/internet/headers/wechat/config.proto", fileDescriptor0)
+}
+
+var fileDescriptor0 = []byte{
+	// 169 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x2d, 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, 0x48,
+	0x4d, 0x4c, 0x49, 0x2d, 0x2a, 0xd6, 0x2f, 0x4f, 0x4d, 0xce, 0x48, 0x2c, 0xd1, 0x4f, 0xce, 0xcf,
+	0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xd2, 0x81, 0x69, 0x2f, 0x4a, 0xd5,
+	0x83, 0x6b, 0xd5, 0x83, 0x69, 0xd5, 0x83, 0x6a, 0xd5, 0x83, 0x68, 0x55, 0xe2, 0xe5, 0xe2, 0x0e,
+	0xcb, 0x4c, 0x49, 0xcd, 0x77, 0x06, 0x1b, 0xe1, 0x54, 0xc6, 0x65, 0x90, 0x9c, 0x9f, 0xab, 0x47,
+	0x8a, 0x11, 0x4e, 0xdc, 0x10, 0xbd, 0x01, 0x20, 0xdb, 0xa3, 0xd8, 0x20, 0x82, 0xab, 0x98, 0x74,
+	0xc2, 0x8c, 0x82, 0x12, 0x2b, 0xf5, 0x9c, 0x41, 0x66, 0x84, 0xc0, 0xcd, 0xf0, 0x84, 0x99, 0xe1,
+	0x01, 0x35, 0x23, 0x1c, 0xac, 0x3c, 0x89, 0x0d, 0xec, 0x76, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff,
+	0xff, 0x9b, 0x6a, 0x0f, 0x19, 0xfc, 0x00, 0x00, 0x00,
+}

+ 10 - 0
transport/internet/headers/wechat/config.proto

@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.headers.wechat;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Headers.Wechat";
+option go_package = "wechat";
+option java_package = "com.v2ray.core.transport.internet.headers.wechat";
+option java_outer_classname = "ConfigProto";
+
+message VideoConfig {
+}

+ 36 - 0
transport/internet/headers/wechat/wechat.go

@@ -0,0 +1,36 @@
+package wechat
+
+import (
+	"v2ray.com/core/common"
+	"v2ray.com/core/common/dice"
+	"v2ray.com/core/common/serial"
+	"v2ray.com/core/transport/internet"
+)
+
+type VideoChat struct {
+	sn int
+}
+
+func (vc *VideoChat) Size() int {
+	return 13
+}
+
+func (vc *VideoChat) Write(b []byte) (int, error) {
+	vc.sn++
+	b = append(b[:0], 0xa1, 0x08)
+	b = serial.IntToBytes(vc.sn, b)
+	b = append(b, 0x10, 0x11, 0x18, 0x30, 0x22, 0x30)
+	return 13, nil
+}
+
+type VideoChatFactory struct{}
+
+func (VideoChatFactory) Create(rawSettings interface{}) internet.PacketHeader {
+	return &VideoChat{
+		sn: dice.Roll(65535),
+	}
+}
+
+func init() {
+	common.Must(internet.RegisterPacketHeader(serial.GetMessageType(new(VideoConfig)), VideoChatFactory{}))
+}

+ 20 - 0
transport/internet/headers/wechat/wechat_test.go

@@ -0,0 +1,20 @@
+package wechat_test
+
+import (
+	"testing"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/testing/assert"
+	. "v2ray.com/core/transport/internet/headers/wechat"
+)
+
+func TestUTPWrite(t *testing.T) {
+	assert := assert.On(t)
+
+	video := VideoChat{}
+
+	payload := buf.NewLocal(2048)
+	payload.AppendSupplier(video.Write)
+
+	assert.Int(payload.Len()).Equals(video.Size())
+}