1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-09-27 22:36:12 -04:00
This commit is contained in:
Darien Raymond 2017-01-04 11:10:52 +01:00
commit 723207158f
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
10 changed files with 464 additions and 211 deletions

View File

@ -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
proxy/socks/protocol.go Normal file
View File

@ -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
}

View File

@ -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)
request, err := session.Handshake(reader, connection)
if err != nil {
log.Access(clientAddr, "", log.AccessRejected, err)
log.Info("Socks|Server: Failed to read request: ", err)
return
}
if request.Command == proto.RequestCommandTCP {
dest := request.Destination()
session := &proxy.SessionInfo{
Source: clientAddr,
Destination: dest,
Inbound: v.meta,
}
log.Info("Socks|Server: TCP Connect request to ", dest)
log.Access(clientAddr, dest, log.AccessAccepted, "")
v.transport(reader, connection, session)
return
}
if request.Command == proto.RequestCommandUDP {
v.handleUDP()
return
}
}
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)
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)
}
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
}
log.Warning("Socks: Unsupported socks command ", request.Command)
return ErrUnsupportedSocksCommand
}
response := protocol.NewSocks5Response()
response.Error = protocol.ErrorSuccess
// 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
}
reader.SetBuffered(false)
writer.SetBuffered(false)
dest := request.Destination()
session := &proxy.SessionInfo{
Source: clientAddr,
Destination: dest,
Inbound: v.meta,
}
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()

View File

@ -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"`

View File

@ -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{

View File

@ -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{}

View File

@ -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,
}

View File

@ -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 {
}

View File

@ -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{}))
}

View File

@ -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())
}