Chore: split component to transport
This commit is contained in:
18
transport/ssr/protocol/auth_aes128_md5.go
Normal file
18
transport/ssr/protocol/auth_aes128_md5.go
Normal file
@ -0,0 +1,18 @@
|
||||
package protocol
|
||||
|
||||
import "github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_md5", newAuthAES128MD5, 9)
|
||||
}
|
||||
|
||||
func newAuthAES128MD5(b *Base) Protocol {
|
||||
a := &authAES128{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum},
|
||||
userData: &userData{},
|
||||
}
|
||||
a.initUserData()
|
||||
return a
|
||||
}
|
275
transport/ssr/protocol/auth_aes128_sha1.go
Normal file
275
transport/ssr/protocol/auth_aes128_sha1.go
Normal file
@ -0,0 +1,275 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
)
|
||||
|
||||
type hmacMethod func(key, data []byte) []byte
|
||||
type hashDigestMethod func([]byte) []byte
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_sha1", newAuthAES128SHA1, 9)
|
||||
}
|
||||
|
||||
type authAES128Function struct {
|
||||
salt string
|
||||
hmac hmacMethod
|
||||
hashDigest hashDigestMethod
|
||||
}
|
||||
|
||||
type authAES128 struct {
|
||||
*Base
|
||||
*authData
|
||||
*authAES128Function
|
||||
*userData
|
||||
iv []byte
|
||||
hasSentHeader bool
|
||||
rawTrans bool
|
||||
packID uint32
|
||||
recvID uint32
|
||||
}
|
||||
|
||||
func newAuthAES128SHA1(b *Base) Protocol {
|
||||
a := &authAES128{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum},
|
||||
userData: &userData{},
|
||||
}
|
||||
a.initUserData()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authAES128) initUserData() {
|
||||
params := strings.Split(a.Param, ":")
|
||||
if len(params) > 1 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
} else {
|
||||
log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt)
|
||||
}
|
||||
}
|
||||
if len(a.userKey) == 0 {
|
||||
a.userKey = a.Key
|
||||
rand.Read(a.userID[:])
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||
p := &authAES128{
|
||||
Base: a.Base,
|
||||
authData: a.next(),
|
||||
authAES128Function: a.authAES128Function,
|
||||
userData: a.userData,
|
||||
packID: 1,
|
||||
recvID: 1,
|
||||
}
|
||||
p.iv = iv
|
||||
return &Conn{Conn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authAES128) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
p := &authAES128{
|
||||
Base: a.Base,
|
||||
authAES128Function: a.authAES128Function,
|
||||
userData: a.userData,
|
||||
}
|
||||
return &PacketConn{PacketConn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authAES128) Decode(dst, src *bytes.Buffer) error {
|
||||
if a.rawTrans {
|
||||
dst.ReadFrom(src)
|
||||
return nil
|
||||
}
|
||||
for src.Len() > 4 {
|
||||
macKey := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.userKey)
|
||||
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID)
|
||||
if !bytes.Equal(a.hmac(macKey, src.Bytes()[:2])[:2], src.Bytes()[2:4]) {
|
||||
src.Reset()
|
||||
return errAuthAES128MACError
|
||||
}
|
||||
|
||||
length := int(binary.LittleEndian.Uint16(src.Bytes()[:2]))
|
||||
if length >= 8192 || length < 7 {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthAES128LengthError
|
||||
}
|
||||
if length > src.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
if !bytes.Equal(a.hmac(macKey, src.Bytes()[:length-4])[:4], src.Bytes()[length-4:length]) {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthAES128ChksumError
|
||||
}
|
||||
|
||||
a.recvID++
|
||||
|
||||
pos := int(src.Bytes()[4])
|
||||
if pos < 255 {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.LittleEndian.Uint16(src.Bytes()[5:7])) + 4
|
||||
}
|
||||
dst.Write(src.Bytes()[pos : length-4])
|
||||
src.Next(length)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error {
|
||||
fullDataLength := len(b)
|
||||
if !a.hasSentHeader {
|
||||
dataLength := getDataLength(b)
|
||||
a.packAuthData(buf, b[:dataLength])
|
||||
b = b[dataLength:]
|
||||
a.hasSentHeader = true
|
||||
}
|
||||
for len(b) > 8100 {
|
||||
a.packData(buf, b[:8100], fullDataLength)
|
||||
b = b[8100:]
|
||||
}
|
||||
if len(b) > 0 {
|
||||
a.packData(buf, b, fullDataLength)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
|
||||
if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) {
|
||||
return nil, errAuthAES128ChksumError
|
||||
}
|
||||
return b[:len(b)-4], nil
|
||||
}
|
||||
|
||||
func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||
buf.Write(b)
|
||||
buf.Write(a.userID[:])
|
||||
buf.Write(a.hmac(a.userKey, buf.Bytes())[:4])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) {
|
||||
dataLength := len(data)
|
||||
randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength)
|
||||
/*
|
||||
2: uint16 LittleEndian packedDataLength
|
||||
2: hmac of packedDataLength
|
||||
3: maxRandDataLengthPrefix (min:1)
|
||||
4: hmac of packedData except the last 4 bytes
|
||||
*/
|
||||
packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4
|
||||
if randDataLength < 128 {
|
||||
packedDataLength -= 2
|
||||
}
|
||||
|
||||
macKey := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.userKey)
|
||||
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID)
|
||||
a.packID++
|
||||
|
||||
binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength))
|
||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2])
|
||||
a.packRandData(poolBuf, randDataLength)
|
||||
poolBuf.Write(data)
|
||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4])
|
||||
}
|
||||
|
||||
func trapezoidRandom(max int, d float64) int {
|
||||
base := rand.Float64()
|
||||
if d-0 > 1e-6 {
|
||||
a := 1 - d
|
||||
base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d)
|
||||
}
|
||||
return int(base * float64(max))
|
||||
}
|
||||
|
||||
func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int {
|
||||
if fullDataLength >= 32*1024-a.Overhead {
|
||||
return 0
|
||||
}
|
||||
// 1460: tcp_mss
|
||||
revLength := 1460 - dataLength - 9
|
||||
if revLength == 0 {
|
||||
return 0
|
||||
}
|
||||
if revLength < 0 {
|
||||
if revLength > -1460 {
|
||||
return trapezoidRandom(revLength+1460, -0.3)
|
||||
}
|
||||
return rand.Intn(32)
|
||||
}
|
||||
if dataLength > 900 {
|
||||
return rand.Intn(revLength)
|
||||
}
|
||||
return trapezoidRandom(revLength, -0.3)
|
||||
}
|
||||
|
||||
func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
dataLength := len(data)
|
||||
randDataLength := a.getRandDataLengthForPackAuthData(dataLength)
|
||||
/*
|
||||
7: checkHead(1) and hmac of checkHead(6)
|
||||
4: userID
|
||||
16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2)
|
||||
4: hmac of userID and encrypted data
|
||||
4: hmac of packedAuthData except the last 4 bytes
|
||||
*/
|
||||
packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4
|
||||
|
||||
macKey := pool.Get(len(a.iv) + len(a.Key))
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.iv)
|
||||
copy(macKey[len(a.iv):], a.Key)
|
||||
|
||||
poolBuf.WriteByte(byte(rand.Intn(256)))
|
||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6])
|
||||
poolBuf.Write(a.userID[:])
|
||||
err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt)
|
||||
if err != nil {
|
||||
poolBuf.Reset()
|
||||
return
|
||||
}
|
||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4])
|
||||
tools.AppendRandBytes(poolBuf, randDataLength)
|
||||
poolBuf.Write(data)
|
||||
poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4])
|
||||
}
|
||||
|
||||
func (a *authAES128) getRandDataLengthForPackAuthData(size int) int {
|
||||
if size > 400 {
|
||||
return rand.Intn(512)
|
||||
}
|
||||
return rand.Intn(1024)
|
||||
}
|
||||
|
||||
func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) {
|
||||
if size < 128 {
|
||||
poolBuf.WriteByte(byte(size + 1))
|
||||
tools.AppendRandBytes(poolBuf, size)
|
||||
return
|
||||
}
|
||||
poolBuf.WriteByte(255)
|
||||
binary.Write(poolBuf, binary.LittleEndian, uint16(size+3))
|
||||
tools.AppendRandBytes(poolBuf, size)
|
||||
}
|
310
transport/ssr/protocol/auth_chain_a.go
Normal file
310
transport/ssr/protocol/auth_chain_a.go
Normal file
@ -0,0 +1,310 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_chain_a", newAuthChainA, 4)
|
||||
}
|
||||
|
||||
type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int
|
||||
|
||||
type authChainA struct {
|
||||
*Base
|
||||
*authData
|
||||
*userData
|
||||
iv []byte
|
||||
salt string
|
||||
hasSentHeader bool
|
||||
rawTrans bool
|
||||
lastClientHash []byte
|
||||
lastServerHash []byte
|
||||
encrypter cipher.Stream
|
||||
decrypter cipher.Stream
|
||||
randomClient tools.XorShift128Plus
|
||||
randomServer tools.XorShift128Plus
|
||||
randDataLength randDataLengthMethod
|
||||
packID uint32
|
||||
recvID uint32
|
||||
}
|
||||
|
||||
func newAuthChainA(b *Base) Protocol {
|
||||
a := &authChainA{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
userData: &userData{},
|
||||
salt: "auth_chain_a",
|
||||
}
|
||||
a.initUserData()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authChainA) initUserData() {
|
||||
params := strings.Split(a.Param, ":")
|
||||
if len(params) > 1 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||
binary.LittleEndian.PutUint32(a.userID[:], uint32(userID))
|
||||
a.userKey = []byte(params[1])
|
||||
} else {
|
||||
log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt)
|
||||
}
|
||||
}
|
||||
if len(a.userKey) == 0 {
|
||||
a.userKey = a.Key
|
||||
rand.Read(a.userID[:])
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||
p := &authChainA{
|
||||
Base: a.Base,
|
||||
authData: a.next(),
|
||||
userData: a.userData,
|
||||
salt: a.salt,
|
||||
packID: 1,
|
||||
recvID: 1,
|
||||
}
|
||||
p.iv = iv
|
||||
p.randDataLength = p.getRandLength
|
||||
return &Conn{Conn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
p := &authChainA{
|
||||
Base: a.Base,
|
||||
salt: a.salt,
|
||||
userData: a.userData,
|
||||
}
|
||||
return &PacketConn{PacketConn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authChainA) Decode(dst, src *bytes.Buffer) error {
|
||||
if a.rawTrans {
|
||||
dst.ReadFrom(src)
|
||||
return nil
|
||||
}
|
||||
for src.Len() > 4 {
|
||||
macKey := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.userKey)
|
||||
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID)
|
||||
|
||||
dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16]))
|
||||
randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer)
|
||||
length := dataLength + randDataLength
|
||||
|
||||
if length >= 4096 {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthChainLengthError
|
||||
}
|
||||
|
||||
if 4+length > src.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2])
|
||||
if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthChainChksumError
|
||||
}
|
||||
a.lastServerHash = serverHash
|
||||
|
||||
pos := 2
|
||||
if dataLength > 0 && randDataLength > 0 {
|
||||
pos += getRandStartPos(randDataLength, &a.randomServer)
|
||||
}
|
||||
wantedData := src.Bytes()[pos : pos+dataLength]
|
||||
a.decrypter.XORKeyStream(wantedData, wantedData)
|
||||
if a.recvID == 1 {
|
||||
dst.Write(wantedData[2:])
|
||||
} else {
|
||||
dst.Write(wantedData)
|
||||
}
|
||||
a.recvID++
|
||||
src.Next(length + 4)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error {
|
||||
if !a.hasSentHeader {
|
||||
dataLength := getDataLength(b)
|
||||
a.packAuthData(buf, b[:dataLength])
|
||||
b = b[dataLength:]
|
||||
a.hasSentHeader = true
|
||||
}
|
||||
for len(b) > 2800 {
|
||||
a.packData(buf, b[:2800])
|
||||
b = b[2800:]
|
||||
}
|
||||
if len(b) > 0 {
|
||||
a.packData(buf, b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authChainA) DecodePacket(b []byte) ([]byte, error) {
|
||||
if len(b) < 9 {
|
||||
return nil, errAuthChainLengthError
|
||||
}
|
||||
if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) {
|
||||
return nil, errAuthChainChksumError
|
||||
}
|
||||
md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1])
|
||||
|
||||
randDataLength := udpGetRandLength(md5Data, &a.randomServer)
|
||||
|
||||
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
|
||||
rc4Cipher, err := rc4.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wantedData := b[:len(b)-8-randDataLength]
|
||||
rc4Cipher.XORKeyStream(wantedData, wantedData)
|
||||
return wantedData, nil
|
||||
}
|
||||
|
||||
func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||
authData := pool.Get(3)
|
||||
defer pool.Put(authData)
|
||||
rand.Read(authData)
|
||||
|
||||
md5Data := tools.HmacMD5(a.Key, authData)
|
||||
|
||||
randDataLength := udpGetRandLength(md5Data, &a.randomClient)
|
||||
|
||||
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16)
|
||||
rc4Cipher, err := rc4.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rc4Cipher.XORKeyStream(b, b)
|
||||
|
||||
buf.Write(b)
|
||||
tools.AppendRandBytes(buf, randDataLength)
|
||||
buf.Write(authData)
|
||||
binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4]))
|
||||
buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||
/*
|
||||
dataLength := len(data)
|
||||
12: checkHead(4) and hmac of checkHead(8)
|
||||
4: uint32 LittleEndian uid (uid = userID ^ last client hash)
|
||||
16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2)
|
||||
4: last server hash(4)
|
||||
packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength
|
||||
*/
|
||||
|
||||
macKey := pool.Get(len(a.iv) + len(a.Key))
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.iv)
|
||||
copy(macKey[len(a.iv):], a.Key)
|
||||
|
||||
// check head
|
||||
tools.AppendRandBytes(poolBuf, 4)
|
||||
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes())
|
||||
a.initRC4Cipher()
|
||||
poolBuf.Write(a.lastClientHash[:8])
|
||||
// uid
|
||||
binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12]))
|
||||
// encrypted data
|
||||
err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt)
|
||||
if err != nil {
|
||||
poolBuf.Reset()
|
||||
return
|
||||
}
|
||||
// last server hash
|
||||
a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:])
|
||||
poolBuf.Write(a.lastServerHash[:4])
|
||||
// packed data
|
||||
a.packData(poolBuf, data)
|
||||
}
|
||||
|
||||
func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) {
|
||||
a.encrypter.XORKeyStream(data, data)
|
||||
|
||||
macKey := pool.Get(len(a.userKey) + 4)
|
||||
defer pool.Put(macKey)
|
||||
copy(macKey, a.userKey)
|
||||
binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID)
|
||||
a.packID++
|
||||
|
||||
length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16])
|
||||
|
||||
originalLength := poolBuf.Len()
|
||||
binary.Write(poolBuf, binary.LittleEndian, length)
|
||||
a.putMixedRandDataAndData(poolBuf, data)
|
||||
a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:])
|
||||
poolBuf.Write(a.lastClientHash[:2])
|
||||
}
|
||||
|
||||
func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) {
|
||||
randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient)
|
||||
if len(data) == 0 {
|
||||
tools.AppendRandBytes(poolBuf, randDataLength)
|
||||
return
|
||||
}
|
||||
if randDataLength > 0 {
|
||||
startPos := getRandStartPos(randDataLength, &a.randomClient)
|
||||
tools.AppendRandBytes(poolBuf, startPos)
|
||||
poolBuf.Write(data)
|
||||
tools.AppendRandBytes(poolBuf, randDataLength-startPos)
|
||||
return
|
||||
}
|
||||
poolBuf.Write(data)
|
||||
}
|
||||
|
||||
func getRandStartPos(length int, random *tools.XorShift128Plus) int {
|
||||
if length == 0 {
|
||||
return 0
|
||||
}
|
||||
return int(random.Next()%8589934609) % length
|
||||
}
|
||||
|
||||
func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int {
|
||||
if length > 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinAndLength(lastHash, length)
|
||||
if length > 1300 {
|
||||
return int(random.Next() % 31)
|
||||
}
|
||||
if length > 900 {
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
if length > 400 {
|
||||
return int(random.Next() % 521)
|
||||
}
|
||||
return int(random.Next() % 1021)
|
||||
}
|
||||
|
||||
func (a *authChainA) initRC4Cipher() {
|
||||
key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16)
|
||||
a.encrypter, _ = rc4.NewCipher(key)
|
||||
a.decrypter, _ = rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int {
|
||||
random.InitFromBin(lastHash)
|
||||
return int(random.Next() % 127)
|
||||
}
|
97
transport/ssr/protocol/auth_chain_b.go
Normal file
97
transport/ssr/protocol/auth_chain_b.go
Normal file
@ -0,0 +1,97 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_chain_b", newAuthChainB, 4)
|
||||
}
|
||||
|
||||
type authChainB struct {
|
||||
*authChainA
|
||||
dataSizeList []int
|
||||
dataSizeList2 []int
|
||||
}
|
||||
|
||||
func newAuthChainB(b *Base) Protocol {
|
||||
a := &authChainB{
|
||||
authChainA: &authChainA{
|
||||
Base: b,
|
||||
authData: &authData{},
|
||||
userData: &userData{},
|
||||
salt: "auth_chain_b",
|
||||
},
|
||||
}
|
||||
a.initUserData()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||
p := &authChainB{
|
||||
authChainA: &authChainA{
|
||||
Base: a.Base,
|
||||
authData: a.next(),
|
||||
userData: a.userData,
|
||||
salt: a.salt,
|
||||
packID: 1,
|
||||
recvID: 1,
|
||||
},
|
||||
}
|
||||
p.iv = iv
|
||||
p.randDataLength = p.getRandLength
|
||||
p.initDataSize()
|
||||
return &Conn{Conn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authChainB) initDataSize() {
|
||||
a.dataSizeList = a.dataSizeList[:0]
|
||||
a.dataSizeList2 = a.dataSizeList2[:0]
|
||||
|
||||
a.randomServer.InitFromBin(a.Key)
|
||||
length := a.randomServer.Next()%8 + 4
|
||||
for ; length > 0; length-- {
|
||||
a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440))
|
||||
}
|
||||
sort.Ints(a.dataSizeList)
|
||||
|
||||
length = a.randomServer.Next()%16 + 8
|
||||
for ; length > 0; length-- {
|
||||
a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440))
|
||||
}
|
||||
sort.Ints(a.dataSizeList2)
|
||||
}
|
||||
|
||||
func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int {
|
||||
if length >= 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinAndLength(lashHash, length)
|
||||
pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead })
|
||||
finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList)))
|
||||
if finalPos < len(a.dataSizeList) {
|
||||
return a.dataSizeList[finalPos] - length - a.Overhead
|
||||
}
|
||||
|
||||
pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead })
|
||||
finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2)))
|
||||
if finalPos < len(a.dataSizeList2) {
|
||||
return a.dataSizeList2[finalPos] - length - a.Overhead
|
||||
}
|
||||
if finalPos < pos+len(a.dataSizeList2)-1 {
|
||||
return 0
|
||||
}
|
||||
if length > 1300 {
|
||||
return int(random.Next() % 31)
|
||||
}
|
||||
if length > 900 {
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
if length > 400 {
|
||||
return int(random.Next() % 521)
|
||||
}
|
||||
return int(random.Next() % 1021)
|
||||
}
|
182
transport/ssr/protocol/auth_sha1_v4.go
Normal file
182
transport/ssr/protocol/auth_sha1_v4.go
Normal file
@ -0,0 +1,182 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"hash/adler32"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_sha1_v4", newAuthSHA1V4, 7)
|
||||
}
|
||||
|
||||
type authSHA1V4 struct {
|
||||
*Base
|
||||
*authData
|
||||
iv []byte
|
||||
hasSentHeader bool
|
||||
rawTrans bool
|
||||
}
|
||||
|
||||
func newAuthSHA1V4(b *Base) Protocol {
|
||||
return &authSHA1V4{Base: b, authData: &authData{}}
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn {
|
||||
p := &authSHA1V4{Base: a.Base, authData: a.next()}
|
||||
p.iv = iv
|
||||
return &Conn{Conn: c, Protocol: p}
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
return c
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error {
|
||||
if a.rawTrans {
|
||||
dst.ReadFrom(src)
|
||||
return nil
|
||||
}
|
||||
for src.Len() > 4 {
|
||||
if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) {
|
||||
src.Reset()
|
||||
return errAuthSHA1V4CRC32Error
|
||||
}
|
||||
|
||||
length := int(binary.BigEndian.Uint16(src.Bytes()[:2]))
|
||||
if length >= 8192 || length < 7 {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthSHA1V4LengthError
|
||||
}
|
||||
if length > src.Len() {
|
||||
break
|
||||
}
|
||||
|
||||
if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) {
|
||||
a.rawTrans = true
|
||||
src.Reset()
|
||||
return errAuthSHA1V4Adler32Error
|
||||
}
|
||||
|
||||
pos := int(src.Bytes()[4])
|
||||
if pos < 255 {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4
|
||||
}
|
||||
dst.Write(src.Bytes()[pos : length-4])
|
||||
src.Next(length)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error {
|
||||
if !a.hasSentHeader {
|
||||
dataLength := getDataLength(b)
|
||||
|
||||
a.packAuthData(buf, b[:dataLength])
|
||||
b = b[dataLength:]
|
||||
|
||||
a.hasSentHeader = true
|
||||
}
|
||||
for len(b) > 8100 {
|
||||
a.packData(buf, b[:8100])
|
||||
b = b[8100:]
|
||||
}
|
||||
if len(b) > 0 {
|
||||
a.packData(buf, b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil }
|
||||
|
||||
func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||
buf.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) {
|
||||
dataLength := len(data)
|
||||
randDataLength := a.getRandDataLength(dataLength)
|
||||
/*
|
||||
2: uint16 BigEndian packedDataLength
|
||||
2: uint16 LittleEndian crc32Data & 0xffff
|
||||
3: maxRandDataLengthPrefix (min:1)
|
||||
4: adler32Data
|
||||
*/
|
||||
packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4
|
||||
if randDataLength < 128 {
|
||||
packedDataLength -= 2
|
||||
}
|
||||
|
||||
binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength))
|
||||
binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff))
|
||||
a.packRandData(poolBuf, randDataLength)
|
||||
poolBuf.Write(data)
|
||||
binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:]))
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
||||
dataLength := len(data)
|
||||
randDataLength := a.getRandDataLength(12 + dataLength)
|
||||
/*
|
||||
2: uint16 BigEndian packedAuthDataLength
|
||||
4: uint32 LittleEndian crc32Data
|
||||
3: maxRandDataLengthPrefix (min: 1)
|
||||
12: authDataLength
|
||||
10: hmacSHA1DataLength
|
||||
*/
|
||||
packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10
|
||||
if randDataLength < 128 {
|
||||
packedAuthDataLength -= 2
|
||||
}
|
||||
|
||||
salt := []byte("auth_sha1_v4")
|
||||
crcData := pool.Get(len(salt) + len(a.Key) + 2)
|
||||
defer pool.Put(crcData)
|
||||
binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength))
|
||||
copy(crcData[2:], salt)
|
||||
copy(crcData[2+len(salt):], a.Key)
|
||||
|
||||
key := pool.Get(len(a.iv) + len(a.Key))
|
||||
defer pool.Put(key)
|
||||
copy(key, a.iv)
|
||||
copy(key[len(a.iv):], a.Key)
|
||||
|
||||
poolBuf.Write(crcData[:2])
|
||||
binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData))
|
||||
a.packRandData(poolBuf, randDataLength)
|
||||
a.putAuthData(poolBuf)
|
||||
poolBuf.Write(data)
|
||||
poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10])
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) {
|
||||
if size < 128 {
|
||||
poolBuf.WriteByte(byte(size + 1))
|
||||
tools.AppendRandBytes(poolBuf, size)
|
||||
return
|
||||
}
|
||||
poolBuf.WriteByte(255)
|
||||
binary.Write(poolBuf, binary.BigEndian, uint16(size+3))
|
||||
tools.AppendRandBytes(poolBuf, size)
|
||||
}
|
||||
|
||||
func (a *authSHA1V4) getRandDataLength(size int) int {
|
||||
if size > 1200 {
|
||||
return 0
|
||||
}
|
||||
if size > 400 {
|
||||
return rand.Intn(256)
|
||||
}
|
||||
return rand.Intn(512)
|
||||
}
|
78
transport/ssr/protocol/base.go
Normal file
78
transport/ssr/protocol/base.go
Normal file
@ -0,0 +1,78 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
Key []byte
|
||||
Overhead int
|
||||
Param string
|
||||
}
|
||||
|
||||
type userData struct {
|
||||
userKey []byte
|
||||
userID [4]byte
|
||||
}
|
||||
|
||||
type authData struct {
|
||||
clientID [4]byte
|
||||
connectionID uint32
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (a *authData) next() *authData {
|
||||
r := &authData{}
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
if a.connectionID > 0xff000000 || a.connectionID == 0 {
|
||||
rand.Read(a.clientID[:])
|
||||
a.connectionID = rand.Uint32() & 0xffffff
|
||||
}
|
||||
a.connectionID++
|
||||
copy(r.clientID[:], a.clientID[:])
|
||||
r.connectionID = a.connectionID
|
||||
return r
|
||||
}
|
||||
|
||||
func (a *authData) putAuthData(buf *bytes.Buffer) {
|
||||
binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix()))
|
||||
buf.Write(a.clientID[:])
|
||||
binary.Write(buf, binary.LittleEndian, a.connectionID)
|
||||
}
|
||||
|
||||
func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error {
|
||||
encrypt := pool.Get(16)
|
||||
defer pool.Put(encrypt)
|
||||
binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix()))
|
||||
copy(encrypt[4:], a.clientID[:])
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID)
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0]))
|
||||
binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1]))
|
||||
|
||||
cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16)
|
||||
block, err := aes.NewCipher(cipherKey)
|
||||
if err != nil {
|
||||
log.Warnln("New cipher error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
iv := bytes.Repeat([]byte{0}, 16)
|
||||
cbcCipher := cipher.NewCBCEncrypter(block, iv)
|
||||
|
||||
cbcCipher.CryptBlocks(encrypt, encrypt)
|
||||
|
||||
b.Write(encrypt)
|
||||
return nil
|
||||
}
|
33
transport/ssr/protocol/origin.go
Normal file
33
transport/ssr/protocol/origin.go
Normal file
@ -0,0 +1,33 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
)
|
||||
|
||||
type origin struct{}
|
||||
|
||||
func init() { register("origin", newOrigin, 0) }
|
||||
|
||||
func newOrigin(b *Base) Protocol { return &origin{} }
|
||||
|
||||
func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c }
|
||||
|
||||
func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c }
|
||||
|
||||
func (o *origin) Decode(dst, src *bytes.Buffer) error {
|
||||
dst.ReadFrom(src)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *origin) Encode(buf *bytes.Buffer, b []byte) error {
|
||||
buf.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil }
|
||||
|
||||
func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error {
|
||||
buf.Write(b)
|
||||
return nil
|
||||
}
|
38
transport/ssr/protocol/packet.go
Normal file
38
transport/ssr/protocol/packet.go
Normal file
@ -0,0 +1,38 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
Protocol
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
buf := tools.BufPool.Get().(*bytes.Buffer)
|
||||
defer tools.BufPool.Put(buf)
|
||||
defer buf.Reset()
|
||||
err := c.EncodePacket(buf, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf.Bytes(), addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
decoded, err := c.DecodePacket(b[:n])
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, decoded)
|
||||
return len(decoded), addr, nil
|
||||
}
|
76
transport/ssr/protocol/protocol.go
Normal file
76
transport/ssr/protocol/protocol.go
Normal file
@ -0,0 +1,76 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32")
|
||||
errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length")
|
||||
errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32")
|
||||
errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac")
|
||||
errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length")
|
||||
errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum")
|
||||
errAuthChainLengthError = errors.New("auth_chain decode data wrong length")
|
||||
errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum")
|
||||
)
|
||||
|
||||
type Protocol interface {
|
||||
StreamConn(net.Conn, []byte) net.Conn
|
||||
PacketConn(net.PacketConn) net.PacketConn
|
||||
Decode(dst, src *bytes.Buffer) error
|
||||
Encode(buf *bytes.Buffer, b []byte) error
|
||||
DecodePacket([]byte) ([]byte, error)
|
||||
EncodePacket(buf *bytes.Buffer, b []byte) error
|
||||
}
|
||||
|
||||
type protocolCreator func(b *Base) Protocol
|
||||
|
||||
var protocolList = make(map[string]struct {
|
||||
overhead int
|
||||
new protocolCreator
|
||||
})
|
||||
|
||||
func register(name string, c protocolCreator, o int) {
|
||||
protocolList[name] = struct {
|
||||
overhead int
|
||||
new protocolCreator
|
||||
}{overhead: o, new: c}
|
||||
}
|
||||
|
||||
func PickProtocol(name string, b *Base) (Protocol, error) {
|
||||
if choice, ok := protocolList[name]; ok {
|
||||
b.Overhead += choice.overhead
|
||||
return choice.new(b), nil
|
||||
}
|
||||
return nil, fmt.Errorf("protocol %s not supported", name)
|
||||
}
|
||||
|
||||
func getHeadSize(b []byte, defaultValue int) int {
|
||||
if len(b) < 2 {
|
||||
return defaultValue
|
||||
}
|
||||
headType := b[0] & 7
|
||||
switch headType {
|
||||
case 1:
|
||||
return 7
|
||||
case 4:
|
||||
return 19
|
||||
case 3:
|
||||
return 4 + int(b[1])
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getDataLength(b []byte) int {
|
||||
bLength := len(b)
|
||||
dataLength := getHeadSize(b, 30) + rand.Intn(32)
|
||||
if bLength < dataLength {
|
||||
return bLength
|
||||
}
|
||||
return dataLength
|
||||
}
|
52
transport/ssr/protocol/stream.go
Normal file
52
transport/ssr/protocol/stream.go
Normal file
@ -0,0 +1,52 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Protocol
|
||||
decoded bytes.Buffer
|
||||
underDecoded bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
if c.decoded.Len() > 0 {
|
||||
return c.decoded.Read(b)
|
||||
}
|
||||
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
defer pool.Put(buf)
|
||||
n, err := c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.underDecoded.Write(buf[:n])
|
||||
err = c.Decode(&c.decoded, &c.underDecoded)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ = c.decoded.Read(b)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
bLength := len(b)
|
||||
buf := tools.BufPool.Get().(*bytes.Buffer)
|
||||
defer tools.BufPool.Put(buf)
|
||||
defer buf.Reset()
|
||||
err := c.Encode(buf, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.Conn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bLength, nil
|
||||
}
|
Reference in New Issue
Block a user