mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
chore: remove depend on lunixbochs/struc
Some checks are pending
Test / test (1.20, macos-13) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-13) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, ubuntu-latest) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-13) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, ubuntu-latest) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-13) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, ubuntu-latest) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-13) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, ubuntu-latest) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.25, macos-13) (push) Waiting to run
Test / test (1.25, macos-latest) (push) Waiting to run
Test / test (1.25, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.25, ubuntu-latest) (push) Waiting to run
Test / test (1.25, windows-latest) (push) Waiting to run
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
Some checks are pending
Test / test (1.20, macos-13) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-13) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, ubuntu-latest) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-13) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, ubuntu-latest) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-13) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, ubuntu-latest) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-13) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, ubuntu-latest) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.25, macos-13) (push) Waiting to run
Test / test (1.25, macos-latest) (push) Waiting to run
Test / test (1.25, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.25, ubuntu-latest) (push) Waiting to run
Test / test (1.25, windows-latest) (push) Waiting to run
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
This commit is contained in:
parent
76e40baebc
commit
48c1b1cdb2
1
go.mod
1
go.mod
@ -13,7 +13,6 @@ require (
|
|||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||||
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
|
||||||
github.com/mdlayher/netlink v1.7.2
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||||
github.com/metacubex/bart v0.20.5
|
github.com/metacubex/bart v0.20.5
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -84,8 +84,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -16,7 +15,6 @@ import (
|
|||||||
"github.com/metacubex/mihomo/transport/hysteria/transport"
|
"github.com/metacubex/mihomo/transport/hysteria/transport"
|
||||||
"github.com/metacubex/mihomo/transport/hysteria/utils"
|
"github.com/metacubex/mihomo/transport/hysteria/utils"
|
||||||
|
|
||||||
"github.com/lunixbochs/struc"
|
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
"github.com/metacubex/quic-go/congestion"
|
"github.com/metacubex/quic-go/congestion"
|
||||||
"github.com/metacubex/randv2"
|
"github.com/metacubex/randv2"
|
||||||
@ -104,31 +102,23 @@ func (c *Client) connectToServer(dialer utils.PacketDialer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) handleControlStream(qs *quic.Conn, stream *quic.Stream) (bool, string, error) {
|
func (c *Client) handleControlStream(qs *quic.Conn, stream *quic.Stream) (bool, string, error) {
|
||||||
// Send protocol version
|
|
||||||
_, err := stream.Write([]byte{protocolVersion})
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
// Send client hello
|
// Send client hello
|
||||||
err = struc.Pack(stream, &clientHello{
|
err := WriteClientHello(stream, ClientHello{
|
||||||
Rate: transmissionRate{
|
SendBPS: c.sendBPS,
|
||||||
SendBPS: c.sendBPS,
|
RecvBPS: c.recvBPS,
|
||||||
RecvBPS: c.recvBPS,
|
Auth: c.auth,
|
||||||
},
|
|
||||||
Auth: c.auth,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
// Receive server hello
|
// Receive server hello
|
||||||
var sh serverHello
|
sh, err := ReadServerHello(stream)
|
||||||
err = struc.Unpack(stream, &sh)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
// Set the congestion accordingly
|
// Set the congestion accordingly
|
||||||
if sh.OK && c.congestionFactory != nil {
|
if sh.OK && c.congestionFactory != nil {
|
||||||
qs.SetCongestionControl(c.congestionFactory(sh.Rate.RecvBPS))
|
qs.SetCongestionControl(c.congestionFactory(sh.RecvBPS))
|
||||||
}
|
}
|
||||||
return sh.OK, sh.Message, nil
|
return sh.OK, sh.Message, nil
|
||||||
}
|
}
|
||||||
@ -140,7 +130,7 @@ func (c *Client) handleMessage(qs *quic.Conn) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
var udpMsg udpMessage
|
var udpMsg udpMessage
|
||||||
err = struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
err = udpMsg.Unpack(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -200,7 +190,7 @@ func (c *Client) DialTCP(host string, port uint16, dialer utils.PacketDialer) (n
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Send request
|
// Send request
|
||||||
err = struc.Pack(stream, &clientRequest{
|
err = WriteClientRequest(stream, ClientRequest{
|
||||||
UDP: false,
|
UDP: false,
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
@ -213,8 +203,8 @@ func (c *Client) DialTCP(host string, port uint16, dialer utils.PacketDialer) (n
|
|||||||
// and defer the response handling to the first Read() call
|
// and defer the response handling to the first Read() call
|
||||||
if !c.fastOpen {
|
if !c.fastOpen {
|
||||||
// Read response
|
// Read response
|
||||||
var sr serverResponse
|
var sr *ServerResponse
|
||||||
err = struc.Unpack(stream, &sr)
|
sr, err = ReadServerResponse(stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stream.Close()
|
_ = stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -239,16 +229,16 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Send request
|
// Send request
|
||||||
err = struc.Pack(stream, &clientRequest{
|
err = WriteClientRequest(stream, ClientRequest{
|
||||||
UDP: true,
|
UDP: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stream.Close()
|
_ = stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Read response
|
// Read response
|
||||||
var sr serverResponse
|
var sr *ServerResponse
|
||||||
err = struc.Unpack(stream, &sr)
|
sr, err = ReadServerResponse(stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stream.Close()
|
_ = stream.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -306,8 +296,8 @@ type quicConn struct {
|
|||||||
|
|
||||||
func (w *quicConn) Read(b []byte) (n int, err error) {
|
func (w *quicConn) Read(b []byte) (n int, err error) {
|
||||||
if !w.Established {
|
if !w.Established {
|
||||||
var sr serverResponse
|
var sr *ServerResponse
|
||||||
err := struc.Unpack(w.Orig, &sr)
|
sr, err = ReadServerResponse(w.Orig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = w.Close()
|
_ = w.Close()
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -401,9 +391,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
|||||||
Data: p,
|
Data: p,
|
||||||
}
|
}
|
||||||
// try no frag first
|
// try no frag first
|
||||||
var msgBuf bytes.Buffer
|
err = c.Session.SendDatagram(msg.Pack())
|
||||||
_ = struc.Pack(&msgBuf, &msg)
|
|
||||||
err = c.Session.SendDatagram(msgBuf.Bytes())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errSize *quic.DatagramTooLargeError
|
var errSize *quic.DatagramTooLargeError
|
||||||
if errors.As(err, &errSize) {
|
if errors.As(err, &errSize) {
|
||||||
@ -411,9 +399,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
|||||||
msg.MsgID = uint16(randv2.IntN(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
msg.MsgID = uint16(randv2.IntN(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||||
fragMsgs := fragUDPMessage(msg, int(errSize.MaxDatagramPayloadSize))
|
fragMsgs := fragUDPMessage(msg, int(errSize.MaxDatagramPayloadSize))
|
||||||
for _, fragMsg := range fragMsgs {
|
for _, fragMsg := range fragMsgs {
|
||||||
msgBuf.Reset()
|
err = c.Session.SendDatagram(fragMsg.Pack())
|
||||||
_ = struc.Pack(&msgBuf, &fragMsg)
|
|
||||||
err = c.Session.SendDatagram(msgBuf.Bytes())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@ func fragUDPMessage(m udpMessage, maxSize int) []udpMessage {
|
|||||||
frag := m
|
frag := m
|
||||||
frag.FragID = fragID
|
frag.FragID = fragID
|
||||||
frag.FragCount = fragCount
|
frag.FragCount = fragCount
|
||||||
frag.DataLen = uint16(payloadSize)
|
|
||||||
frag.Data = fullPayload[off : off+payloadSize]
|
frag.Data = fullPayload[off : off+payloadSize]
|
||||||
frags = append(frags, frag)
|
frags = append(frags, frag)
|
||||||
off += payloadSize
|
off += payloadSize
|
||||||
@ -56,7 +55,6 @@ func (d *defragger) Feed(m udpMessage) *udpMessage {
|
|||||||
for _, frag := range d.frags {
|
for _, frag := range d.frags {
|
||||||
data = append(data, frag.Data...)
|
data = append(data, frag.Data...)
|
||||||
}
|
}
|
||||||
m.DataLen = uint16(len(data))
|
|
||||||
m.Data = data
|
m.Data = data
|
||||||
m.FragID = 0
|
m.FragID = 0
|
||||||
m.FragCount = 1
|
m.FragCount = 1
|
||||||
|
|||||||
@ -20,13 +20,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
100,
|
100,
|
||||||
@ -34,13 +32,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
[]udpMessage{
|
[]udpMessage{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -50,13 +46,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
22,
|
22,
|
||||||
@ -64,24 +58,20 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
[]udpMessage{
|
[]udpMessage{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 4,
|
|
||||||
Data: []byte("hell"),
|
Data: []byte("hell"),
|
||||||
},
|
},
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 1,
|
FragID: 1,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 1,
|
|
||||||
Data: []byte("o"),
|
Data: []byte("o"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -91,13 +81,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 20,
|
|
||||||
Data: []byte("wow wow wow lol lmao"),
|
Data: []byte("wow wow wow lol lmao"),
|
||||||
},
|
},
|
||||||
23,
|
23,
|
||||||
@ -105,46 +93,38 @@ func Test_fragUDPMessage(t *testing.T) {
|
|||||||
[]udpMessage{
|
[]udpMessage{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 4,
|
FragCount: 4,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("wow w"),
|
Data: []byte("wow w"),
|
||||||
},
|
},
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 1,
|
FragID: 1,
|
||||||
FragCount: 4,
|
FragCount: 4,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("ow wo"),
|
Data: []byte("ow wo"),
|
||||||
},
|
},
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 2,
|
FragID: 2,
|
||||||
FragCount: 4,
|
FragCount: 4,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("w lol"),
|
Data: []byte("w lol"),
|
||||||
},
|
},
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 3,
|
FragID: 3,
|
||||||
FragCount: 4,
|
FragCount: 4,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte(" lmao"),
|
Data: []byte(" lmao"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -174,25 +154,21 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&udpMessage{
|
&udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 123,
|
MsgID: 123,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -201,13 +177,11 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 666,
|
MsgID: 666,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 3,
|
FragCount: 3,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -218,13 +192,11 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 666,
|
MsgID: 666,
|
||||||
FragID: 1,
|
FragID: 1,
|
||||||
FragCount: 3,
|
FragCount: 3,
|
||||||
DataLen: 8,
|
|
||||||
Data: []byte(" shitty "),
|
Data: []byte(" shitty "),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -235,25 +207,21 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 666,
|
MsgID: 666,
|
||||||
FragID: 2,
|
FragID: 2,
|
||||||
FragCount: 3,
|
FragCount: 3,
|
||||||
DataLen: 7,
|
|
||||||
Data: []byte("world!!"),
|
Data: []byte("world!!"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&udpMessage{
|
&udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 666,
|
MsgID: 666,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 20,
|
|
||||||
Data: []byte("hello shitty world!!"),
|
Data: []byte("hello shitty world!!"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -262,13 +230,11 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 777,
|
MsgID: 777,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -279,13 +245,11 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 778,
|
MsgID: 778,
|
||||||
FragID: 1,
|
FragID: 1,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte(" moto"),
|
Data: []byte(" moto"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -296,13 +260,11 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 777,
|
MsgID: 777,
|
||||||
FragID: 1,
|
FragID: 1,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte(" moto"),
|
Data: []byte(" moto"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -313,25 +275,21 @@ func Test_defragger_Feed(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
udpMessage{
|
udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 777,
|
MsgID: 777,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 2,
|
FragCount: 2,
|
||||||
DataLen: 5,
|
|
||||||
Data: []byte("hello"),
|
Data: []byte("hello"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&udpMessage{
|
&udpMessage{
|
||||||
SessionID: 123,
|
SessionID: 123,
|
||||||
HostLen: 4,
|
|
||||||
Host: "test",
|
Host: "test",
|
||||||
Port: 123,
|
Port: 123,
|
||||||
MsgID: 777,
|
MsgID: 777,
|
||||||
FragID: 0,
|
FragID: 0,
|
||||||
FragCount: 1,
|
FragCount: 1,
|
||||||
DataLen: 10,
|
|
||||||
Data: []byte("hello moto"),
|
Data: []byte("hello moto"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,60 +1,241 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
protocolVersion = uint8(3)
|
protocolVersion = uint8(3)
|
||||||
protocolVersionV2 = uint8(2)
|
protocolTimeout = 10 * time.Second
|
||||||
protocolTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
closeErrorCodeGeneric = 0
|
closeErrorCodeGeneric = 0
|
||||||
closeErrorCodeProtocol = 1
|
closeErrorCodeProtocol = 1
|
||||||
closeErrorCodeAuth = 2
|
closeErrorCodeAuth = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type transmissionRate struct {
|
type ClientHello struct {
|
||||||
SendBPS uint64
|
SendBPS uint64
|
||||||
RecvBPS uint64
|
RecvBPS uint64
|
||||||
}
|
|
||||||
|
|
||||||
type clientHello struct {
|
|
||||||
Rate transmissionRate
|
|
||||||
AuthLen uint16 `struc:"sizeof=Auth"`
|
|
||||||
Auth []byte
|
Auth []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverHello struct {
|
func WriteClientHello(stream io.Writer, hello ClientHello) error {
|
||||||
OK bool
|
var requestLen int
|
||||||
Rate transmissionRate
|
requestLen += 1 // version
|
||||||
MessageLen uint16 `struc:"sizeof=Message"`
|
requestLen += 8 // sendBPS
|
||||||
Message string
|
requestLen += 8 // recvBPS
|
||||||
|
requestLen += 2 // auth len
|
||||||
|
requestLen += len(hello.Auth)
|
||||||
|
request := make([]byte, requestLen)
|
||||||
|
request[0] = protocolVersion
|
||||||
|
binary.BigEndian.PutUint64(request[1:9], hello.SendBPS)
|
||||||
|
binary.BigEndian.PutUint64(request[9:17], hello.RecvBPS)
|
||||||
|
binary.BigEndian.PutUint16(request[17:19], uint16(len(hello.Auth)))
|
||||||
|
copy(request[19:], hello.Auth)
|
||||||
|
_, err := stream.Write(request)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientRequest struct {
|
func ReadClientHello(stream io.Reader) (*ClientHello, error) {
|
||||||
UDP bool
|
var responseLen int
|
||||||
HostLen uint16 `struc:"sizeof=Host"`
|
responseLen += 1 // ok
|
||||||
Host string
|
responseLen += 8 // sendBPS
|
||||||
Port uint16
|
responseLen += 8 // recvBPS
|
||||||
|
responseLen += 2 // auth len
|
||||||
|
response := make([]byte, responseLen)
|
||||||
|
_, err := io.ReadFull(stream, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[0] != protocolVersion {
|
||||||
|
return nil, errors.New("unsupported client version")
|
||||||
|
}
|
||||||
|
var clientHello ClientHello
|
||||||
|
clientHello.SendBPS = binary.BigEndian.Uint64(response[1:9])
|
||||||
|
clientHello.RecvBPS = binary.BigEndian.Uint64(response[9:17])
|
||||||
|
authLen := binary.BigEndian.Uint16(response[17:19])
|
||||||
|
|
||||||
|
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
|
||||||
|
return nil, errors.New("invalid rate from client")
|
||||||
|
}
|
||||||
|
|
||||||
|
authBytes := make([]byte, authLen)
|
||||||
|
_, err = io.ReadFull(stream, authBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientHello.Auth = authBytes
|
||||||
|
return &clientHello, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverResponse struct {
|
type ServerHello struct {
|
||||||
|
OK bool
|
||||||
|
SendBPS uint64
|
||||||
|
RecvBPS uint64
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadServerHello(stream io.Reader) (*ServerHello, error) {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 8 // sendBPS
|
||||||
|
responseLen += 8 // recvBPS
|
||||||
|
responseLen += 2 // message len
|
||||||
|
response := make([]byte, responseLen)
|
||||||
|
_, err := io.ReadFull(stream, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var serverHello ServerHello
|
||||||
|
serverHello.OK = response[0] == 1
|
||||||
|
serverHello.SendBPS = binary.BigEndian.Uint64(response[1:9])
|
||||||
|
serverHello.RecvBPS = binary.BigEndian.Uint64(response[9:17])
|
||||||
|
messageLen := binary.BigEndian.Uint16(response[17:19])
|
||||||
|
if messageLen == 0 {
|
||||||
|
return &serverHello, nil
|
||||||
|
}
|
||||||
|
message := make([]byte, messageLen)
|
||||||
|
_, err = io.ReadFull(stream, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverHello.Message = string(message)
|
||||||
|
return &serverHello, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteServerHello(stream io.Writer, hello ServerHello) error {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 8 // sendBPS
|
||||||
|
responseLen += 8 // recvBPS
|
||||||
|
responseLen += 2 // message len
|
||||||
|
responseLen += len(hello.Message)
|
||||||
|
response := make([]byte, responseLen)
|
||||||
|
if hello.OK {
|
||||||
|
response[0] = 1
|
||||||
|
} else {
|
||||||
|
response[0] = 0
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint64(response[1:9], hello.SendBPS)
|
||||||
|
binary.BigEndian.PutUint64(response[9:17], hello.RecvBPS)
|
||||||
|
binary.BigEndian.PutUint16(response[17:19], uint16(len(hello.Message)))
|
||||||
|
copy(response[19:], hello.Message)
|
||||||
|
_, err := stream.Write(response)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRequest struct {
|
||||||
|
UDP bool
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadClientRequest(stream io.Reader) (*ClientRequest, error) {
|
||||||
|
var clientRequest ClientRequest
|
||||||
|
err := binary.Read(stream, binary.BigEndian, &clientRequest.UDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var hostLen uint16
|
||||||
|
err = binary.Read(stream, binary.BigEndian, &hostLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host := make([]byte, hostLen)
|
||||||
|
_, err = io.ReadFull(stream, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientRequest.Host = string(host)
|
||||||
|
err = binary.Read(stream, binary.BigEndian, &clientRequest.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &clientRequest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteClientRequest(stream io.Writer, request ClientRequest) error {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 1 // udp
|
||||||
|
requestLen += 2 // host len
|
||||||
|
requestLen += len(request.Host)
|
||||||
|
requestLen += 2 // port
|
||||||
|
buffer := make([]byte, requestLen)
|
||||||
|
if request.UDP {
|
||||||
|
buffer[0] = 1
|
||||||
|
} else {
|
||||||
|
buffer[0] = 0
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16(buffer[1:3], uint16(len(request.Host)))
|
||||||
|
n := copy(buffer[3:], request.Host)
|
||||||
|
binary.BigEndian.PutUint16(buffer[3+n:3+n+2], request.Port)
|
||||||
|
_, err := stream.Write(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerResponse struct {
|
||||||
OK bool
|
OK bool
|
||||||
UDPSessionID uint32
|
UDPSessionID uint32
|
||||||
MessageLen uint16 `struc:"sizeof=Message"`
|
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadServerResponse(stream io.Reader) (*ServerResponse, error) {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 4 // udp session id
|
||||||
|
responseLen += 2 // message len
|
||||||
|
response := make([]byte, responseLen)
|
||||||
|
_, err := io.ReadFull(stream, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var serverResponse ServerResponse
|
||||||
|
serverResponse.OK = response[0] == 1
|
||||||
|
serverResponse.UDPSessionID = binary.BigEndian.Uint32(response[1:5])
|
||||||
|
messageLen := binary.BigEndian.Uint16(response[5:7])
|
||||||
|
if messageLen == 0 {
|
||||||
|
return &serverResponse, nil
|
||||||
|
}
|
||||||
|
message := make([]byte, messageLen)
|
||||||
|
_, err = io.ReadFull(stream, message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serverResponse.Message = string(message)
|
||||||
|
return &serverResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteServerResponse(stream io.Writer, response ServerResponse) error {
|
||||||
|
var responseLen int
|
||||||
|
responseLen += 1 // ok
|
||||||
|
responseLen += 4 // udp session id
|
||||||
|
responseLen += 2 // message len
|
||||||
|
responseLen += len(response.Message)
|
||||||
|
buffer := make([]byte, responseLen)
|
||||||
|
if response.OK {
|
||||||
|
buffer[0] = 1
|
||||||
|
} else {
|
||||||
|
buffer[0] = 0
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(buffer[1:5], response.UDPSessionID)
|
||||||
|
binary.BigEndian.PutUint16(buffer[5:7], uint16(len(response.Message)))
|
||||||
|
copy(buffer[7:], response.Message)
|
||||||
|
_, err := stream.Write(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type udpMessage struct {
|
type udpMessage struct {
|
||||||
SessionID uint32
|
SessionID uint32
|
||||||
HostLen uint16 `struc:"sizeof=Host"`
|
|
||||||
Host string
|
Host string
|
||||||
Port uint16
|
Port uint16
|
||||||
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
|
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
|
||||||
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
|
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
|
||||||
FragCount uint8 // must be 1 when not fragmented
|
FragCount uint8 // must be 1 when not fragmented
|
||||||
DataLen uint16 `struc:"sizeof=Data"`
|
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,11 +247,62 @@ func (m udpMessage) Size() int {
|
|||||||
return m.HeaderSize() + len(m.Data)
|
return m.HeaderSize() + len(m.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpMessageV2 struct {
|
func (m udpMessage) Pack() []byte {
|
||||||
SessionID uint32
|
data := make([]byte, m.Size())
|
||||||
HostLen uint16 `struc:"sizeof=Host"`
|
buffer := bytes.NewBuffer(data)
|
||||||
Host string
|
_ = binary.Write(buffer, binary.BigEndian, m.SessionID)
|
||||||
Port uint16
|
_ = binary.Write(buffer, binary.BigEndian, uint16(len(m.Host)))
|
||||||
DataLen uint16 `struc:"sizeof=Data"`
|
buffer.WriteString(m.Host)
|
||||||
Data []byte
|
_ = binary.Write(buffer, binary.BigEndian, m.Port)
|
||||||
|
_ = binary.Write(buffer, binary.BigEndian, m.MsgID)
|
||||||
|
_ = binary.Write(buffer, binary.BigEndian, m.FragID)
|
||||||
|
_ = binary.Write(buffer, binary.BigEndian, m.FragCount)
|
||||||
|
_ = binary.Write(buffer, binary.BigEndian, uint16(len(m.Data)))
|
||||||
|
buffer.Write(m.Data)
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *udpMessage) Unpack(data []byte) error {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &m.SessionID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var hostLen uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &hostLen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hostBytes := make([]byte, hostLen)
|
||||||
|
_, err = io.ReadFull(reader, hostBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Host = string(hostBytes)
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &m.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &m.MsgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &m.FragID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &m.FragCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var dataLen uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &dataLen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reader.Len() != int(dataLen) {
|
||||||
|
return errors.New("invalid data length")
|
||||||
|
}
|
||||||
|
m.Data = data[len(data)-reader.Len():]
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user