Xray-core/proxy/freedom/freedom.go

467 lines
12 KiB
Go
Raw Normal View History

2020-11-25 14:01:53 +03:00
package freedom
2020-12-04 04:36:16 +03:00
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
2020-11-25 14:01:53 +03:00
import (
"context"
"crypto/rand"
"encoding/binary"
"io"
"math/big"
2020-11-25 14:01:53 +03:00
"time"
2020-12-04 04:36:16 +03:00
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
2020-12-04 04:36:16 +03:00
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/retry"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/stats"
2020-12-04 04:36:16 +03:00
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
2020-11-25 14:01:53 +03:00
)
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
h := new(Handler)
if err := core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) error {
return h.Init(config.(*Config), pm, d)
}); err != nil {
return nil, err
}
return h, nil
}))
}
// Handler handles Freedom connections.
type Handler struct {
policyManager policy.Manager
dns dns.Client
config *Config
}
// Init initializes the Handler with necessary parameters.
func (h *Handler) Init(config *Config, pm policy.Manager, d dns.Client) error {
h.config = config
h.policyManager = pm
h.dns = d
return nil
}
func (h *Handler) policy() policy.Session {
p := h.policyManager.ForLevel(h.config.UserLevel)
if h.config.Timeout > 0 && h.config.UserLevel == 0 {
p.Timeouts.ConnectionIdle = time.Duration(h.config.Timeout) * time.Second
}
return p
}
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
var option dns.IPOption = dns.IPOption{
IPv4Enable: true,
IPv6Enable: true,
FakeEnable: false,
}
2020-11-25 14:01:53 +03:00
if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
option = dns.IPOption{
IPv4Enable: true,
IPv6Enable: false,
FakeEnable: false,
2020-11-25 14:01:53 +03:00
}
} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
option = dns.IPOption{
IPv4Enable: false,
IPv6Enable: true,
FakeEnable: false,
2020-11-25 14:01:53 +03:00
}
}
ips, err := h.dns.LookupIP(domain, option)
2020-11-25 14:01:53 +03:00
if err != nil {
newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
}
if len(ips) == 0 {
return nil
}
return net.IPAddress(ips[dice.Roll(len(ips))])
}
func isValidAddress(addr *net.IPOrDomain) bool {
if addr == nil {
return false
}
a := addr.AsAddress()
return a != net.AnyIP
}
// Process implements proxy.Outbound.
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbound := session.OutboundFromContext(ctx)
if outbound == nil || !outbound.Target.IsValid() {
return newError("target not specified.")
}
destination := outbound.Target
UDPOverride := net.UDPDestination(nil, 0)
2020-11-25 14:01:53 +03:00
if h.config.DestinationOverride != nil {
server := h.config.DestinationOverride.Server
if isValidAddress(server.Address) {
destination.Address = server.Address.AsAddress()
UDPOverride.Address = destination.Address
2020-11-25 14:01:53 +03:00
}
if server.Port != 0 {
destination.Port = net.Port(server.Port)
UDPOverride.Port = destination.Port
2020-11-25 14:01:53 +03:00
}
}
input := link.Reader
output := link.Writer
var conn stat.Connection
2020-11-25 14:01:53 +03:00
err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination
if h.config.useIP() && dialDest.Address.Family().IsDomain() {
ip := h.resolveIP(ctx, dialDest.Address.Domain(), dialer.Address())
if ip != nil {
dialDest = net.Destination{
Network: dialDest.Network,
Address: ip,
Port: dialDest.Port,
}
newError("dialing to ", dialDest).WriteToLog(session.ExportIDToError(ctx))
2020-11-25 14:01:53 +03:00
}
}
rawConn, err := dialer.Dial(ctx, dialDest)
if err != nil {
return err
}
conn = rawConn
return nil
})
if err != nil {
return newError("failed to open connection to ", destination).Base(err)
}
defer conn.Close()
newError("connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()).WriteToLog(session.ExportIDToError(ctx))
2020-11-25 14:01:53 +03:00
var newCtx context.Context
var newCancel context.CancelFunc
if session.TimeoutOnlyFromContext(ctx) {
newCtx, newCancel = context.WithCancel(context.Background())
}
2020-11-25 14:01:53 +03:00
plcy := h.policy()
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() {
cancel()
if newCancel != nil {
newCancel()
}
}, plcy.Timeouts.ConnectionIdle)
2020-11-25 14:01:53 +03:00
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
var writer buf.Writer
if destination.Network == net.Network_TCP {
if h.config.Fragment != nil {
if h.config.Fragment.StartPacket == 0 && h.config.Fragment.EndPacket == 1 {
newError("FRAGMENT", int(h.config.Fragment.MaxLength)).WriteToLog(session.ExportIDToError(ctx))
writer = buf.NewWriter(
&FragmentedClientHelloConn{
Conn: conn,
maxLength: int(h.config.Fragment.MaxLength),
minInterval: time.Duration(h.config.Fragment.MinInterval) * time.Millisecond,
maxInterval: time.Duration(h.config.Fragment.MaxInterval) * time.Millisecond,
})
} else {
writer = buf.NewWriter(
&FragmentWriter{
Writer: conn,
minLength: int(h.config.Fragment.MinLength),
maxLength: int(h.config.Fragment.MaxLength),
minInterval: time.Duration(h.config.Fragment.MinInterval) * time.Millisecond,
maxInterval: time.Duration(h.config.Fragment.MaxInterval) * time.Millisecond,
startPacket: int(h.config.Fragment.StartPacket),
endPacket: int(h.config.Fragment.EndPacket),
PacketCount: 0,
})
}
} else {
writer = buf.NewWriter(conn)
}
2020-11-25 14:01:53 +03:00
} else {
writer = NewPacketWriter(conn, h, ctx, UDPOverride)
2020-11-25 14:01:53 +03:00
}
if err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to process request").Base(err)
}
return nil
}
responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
var reader buf.Reader
if destination.Network == net.Network_TCP {
reader = buf.NewReader(conn)
} else {
reader = NewPacketReader(conn, UDPOverride)
2020-11-25 14:01:53 +03:00
}
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
return newError("failed to process response").Base(err)
}
return nil
}
if newCtx != nil {
ctx = newCtx
}
2020-11-25 14:01:53 +03:00
if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
iConn := conn
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
iConn = statConn.Connection
}
var counter stats.Counter
if statConn != nil {
counter = statConn.ReadCounter
}
if c, ok := iConn.(*internet.PacketConnWrapper); ok && UDPOverride.Address == nil && UDPOverride.Port == 0 {
return &PacketReader{
PacketConnWrapper: c,
Counter: counter,
}
}
return &buf.PacketReader{Reader: conn}
}
type PacketReader struct {
*internet.PacketConnWrapper
stats.Counter
}
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
b.Resize(0, buf.Size)
n, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
b.Resize(0, int32(n))
b.UDP = &net.Destination{
Address: net.IPAddress(d.(*net.UDPAddr).IP),
Port: net.Port(d.(*net.UDPAddr).Port),
Network: net.Network_UDP,
}
if r.Counter != nil {
r.Counter.Add(int64(n))
}
return buf.MultiBuffer{b}, nil
}
func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride net.Destination) buf.Writer {
iConn := conn
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
iConn = statConn.Connection
}
var counter stats.Counter
if statConn != nil {
counter = statConn.WriteCounter
}
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
return &PacketWriter{
PacketConnWrapper: c,
Counter: counter,
Handler: h,
Context: ctx,
UDPOverride: UDPOverride,
}
}
return &buf.SequentialWriter{Writer: conn}
}
type PacketWriter struct {
*internet.PacketConnWrapper
stats.Counter
*Handler
context.Context
UDPOverride net.Destination
}
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for {
mb2, b := buf.SplitFirst(mb)
mb = mb2
if b == nil {
break
}
var n int
var err error
if b.UDP != nil {
if w.UDPOverride.Address != nil {
b.UDP.Address = w.UDPOverride.Address
}
if w.UDPOverride.Port != 0 {
b.UDP.Port = w.UDPOverride.Port
}
if w.Handler.config.useIP() && b.UDP.Address.Family().IsDomain() {
ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil)
if ip != nil {
b.UDP.Address = ip
}
}
destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if destAddr == nil {
b.Release()
continue
}
n, err = w.PacketConnWrapper.WriteTo(b.Bytes(), destAddr)
} else {
n, err = w.PacketConnWrapper.Write(b.Bytes())
}
b.Release()
if err != nil {
buf.ReleaseMulti(mb)
return err
}
if w.Counter != nil {
w.Counter.Add(int64(n))
}
}
return nil
}
type FragmentWriter struct {
io.Writer
minLength int
maxLength int
minInterval time.Duration
maxInterval time.Duration
startPacket int
endPacket int
PacketCount int
}
func (w *FragmentWriter) Write(buf []byte) (int, error) {
w.PacketCount += 1
if (w.startPacket != 0 && (w.PacketCount < w.startPacket || w.PacketCount > w.endPacket)) || len(buf) <= w.minLength {
return w.Writer.Write(buf)
}
nTotal := 0
for {
randomBytesTo := int(randBetween(int64(w.minLength), int64(w.maxLength))) + nTotal
if randomBytesTo > len(buf) {
randomBytesTo = len(buf)
}
n, err := w.Writer.Write(buf[nTotal:randomBytesTo])
if err != nil {
return nTotal + n, err
}
nTotal += n
if nTotal >= len(buf) {
return nTotal, nil
}
randomInterval := randBetween(int64(w.minInterval), int64(w.maxInterval))
time.Sleep(time.Duration(randomInterval))
}
}
// stolen from github.com/xtls/xray-core/transport/internet/reality
func randBetween(left int64, right int64) int64 {
if left == right {
return left
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
return left + bigInt.Int64()
}
type FragmentedClientHelloConn struct {
net.Conn
PacketCount int
minLength int
maxLength int
minInterval time.Duration
maxInterval time.Duration
}
func (c *FragmentedClientHelloConn) Write(b []byte) (n int, err error) {
if len(b) >= 5 && b[0] == 22 && c.PacketCount == 0 {
n, err = sendFragmentedClientHello(c, b, c.minLength, c.maxLength)
if err == nil {
c.PacketCount++
return n, err
}
}
return c.Conn.Write(b)
}
func sendFragmentedClientHello(conn *FragmentedClientHelloConn, clientHello []byte, minFragmentSize, maxFragmentSize int) (n int, err error) {
if len(clientHello) < 5 || clientHello[0] != 22 {
return 0, errors.New("not a valid TLS ClientHello message")
}
clientHelloLen := (int(clientHello[3]) << 8) | int(clientHello[4])
clientHelloData := clientHello[5:]
for i := 0; i < clientHelloLen; {
fragmentEnd := i + int(randBetween(int64(minFragmentSize), int64(maxFragmentSize)))
if fragmentEnd > clientHelloLen {
fragmentEnd = clientHelloLen
}
fragment := clientHelloData[i:fragmentEnd]
i = fragmentEnd
err = writeFragmentedRecord(conn, 22, fragment, clientHello)
if err != nil {
return 0, err
}
}
return len(clientHello), nil
}
func writeFragmentedRecord(c *FragmentedClientHelloConn, contentType uint8, data []byte, clientHello []byte) error {
header := make([]byte, 5)
header[0] = byte(clientHello[0])
tlsVersion := (int(clientHello[1]) << 8) | int(clientHello[2])
binary.BigEndian.PutUint16(header[1:], uint16(tlsVersion))
binary.BigEndian.PutUint16(header[3:], uint16(len(data)))
_, err := c.Conn.Write(append(header, data...))
randomInterval := randBetween(int64(c.minInterval), int64(c.maxInterval))
time.Sleep(time.Duration(randomInterval))
return err
}