mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2024-04-21 12:21:34 +00:00
177 lines
3.8 KiB
Go
177 lines
3.8 KiB
Go
package trojan
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/p4gefau1t/trojan-go/api"
|
|
"github.com/p4gefau1t/trojan-go/common"
|
|
"github.com/p4gefau1t/trojan-go/config"
|
|
"github.com/p4gefau1t/trojan-go/log"
|
|
"github.com/p4gefau1t/trojan-go/statistic"
|
|
"github.com/p4gefau1t/trojan-go/statistic/memory"
|
|
"github.com/p4gefau1t/trojan-go/tunnel"
|
|
"github.com/p4gefau1t/trojan-go/tunnel/mux"
|
|
)
|
|
|
|
const (
|
|
MaxPacketSize = 1024 * 8
|
|
)
|
|
|
|
const (
|
|
Connect tunnel.Command = 1
|
|
Associate tunnel.Command = 3
|
|
Mux tunnel.Command = 0x7f
|
|
)
|
|
|
|
type OutboundConn struct {
|
|
metadata *tunnel.Metadata
|
|
sent uint64
|
|
recv uint64
|
|
user statistic.User
|
|
headerWritten bool
|
|
net.Conn
|
|
}
|
|
|
|
func (c *OutboundConn) Metadata() *tunnel.Metadata {
|
|
return c.metadata
|
|
}
|
|
|
|
func (c *OutboundConn) WriteHeader(payload []byte) error {
|
|
if !c.headerWritten {
|
|
hash := c.user.Hash()
|
|
buf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize))
|
|
crlf := []byte{0x0d, 0x0a}
|
|
buf.Write([]byte(hash))
|
|
buf.Write(crlf)
|
|
c.metadata.WriteTo(buf)
|
|
buf.Write(crlf)
|
|
if payload != nil {
|
|
buf.Write(payload)
|
|
}
|
|
_, err := c.Conn.Write(buf.Bytes())
|
|
c.headerWritten = true
|
|
return err
|
|
}
|
|
return common.NewError("trojan header has been written")
|
|
}
|
|
|
|
func (c *OutboundConn) Write(p []byte) (int, error) {
|
|
if !c.headerWritten {
|
|
err := c.WriteHeader(p)
|
|
if err != nil {
|
|
return 0, common.NewError("trojan failed to flush header with payload").Base(err)
|
|
}
|
|
return len(p), nil
|
|
}
|
|
n, err := c.Conn.Write(p)
|
|
c.user.AddTraffic(n, 0)
|
|
c.sent += uint64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (c *OutboundConn) Read(p []byte) (int, error) {
|
|
n, err := c.Conn.Read(p)
|
|
c.user.AddTraffic(0, n)
|
|
c.recv += uint64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (c *OutboundConn) Close() error {
|
|
log.Info("connection to", c.metadata, "closed", "sent:", common.HumanFriendlyTraffic(c.sent), "recv:", common.HumanFriendlyTraffic(c.recv))
|
|
return c.Conn.Close()
|
|
}
|
|
|
|
type Client struct {
|
|
underlay tunnel.Client
|
|
user statistic.User
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func (c *Client) Close() error {
|
|
c.cancel()
|
|
return c.underlay.Close()
|
|
}
|
|
|
|
func (c *Client) DialConn(addr *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) {
|
|
conn, err := c.underlay.DialConn(addr, &Tunnel{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newConn := &OutboundConn{
|
|
Conn: conn,
|
|
user: c.user,
|
|
metadata: &tunnel.Metadata{
|
|
Command: Connect,
|
|
Address: addr,
|
|
},
|
|
}
|
|
if _, ok := overlay.(*mux.Tunnel); ok {
|
|
newConn.metadata.Command = Mux
|
|
}
|
|
|
|
go func(newConn *OutboundConn) {
|
|
// if the trojan header is still buffered after 100 ms, the client may expect data from the server
|
|
// so we flush the trojan header
|
|
time.Sleep(time.Millisecond * 100)
|
|
newConn.WriteHeader(nil)
|
|
}(newConn)
|
|
return newConn, nil
|
|
}
|
|
|
|
func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {
|
|
fakeAddr := &tunnel.Address{
|
|
DomainName: "UDP_CONN",
|
|
AddressType: tunnel.DomainName,
|
|
}
|
|
conn, err := c.underlay.DialConn(fakeAddr, &Tunnel{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &PacketConn{
|
|
Conn: &OutboundConn{
|
|
Conn: conn,
|
|
user: c.user,
|
|
metadata: &tunnel.Metadata{
|
|
Command: Associate,
|
|
Address: fakeAddr,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func NewClient(ctx context.Context, client tunnel.Client) (*Client, error) {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
auth, err := statistic.NewAuthenticator(ctx, memory.Name)
|
|
if err != nil {
|
|
cancel()
|
|
return nil, err
|
|
}
|
|
|
|
cfg := config.FromContext(ctx, Name).(*Config)
|
|
if cfg.API.Enabled {
|
|
go api.RunService(ctx, Name+"_CLIENT", auth)
|
|
}
|
|
|
|
var user statistic.User
|
|
for _, u := range auth.ListUsers() {
|
|
user = u
|
|
break
|
|
}
|
|
if user == nil {
|
|
cancel()
|
|
return nil, common.NewError("no valid user found")
|
|
}
|
|
|
|
log.Debug("trojan client created")
|
|
return &Client{
|
|
underlay: client,
|
|
ctx: ctx,
|
|
user: user,
|
|
cancel: cancel,
|
|
}, nil
|
|
}
|