mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-12-15 09:34:00 +08:00
314 lines
8.0 KiB
Go
314 lines
8.0 KiB
Go
package dokodemo
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/log"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/protocol"
|
|
"github.com/xtls/xray-core/common/session"
|
|
"github.com/xtls/xray-core/common/utils"
|
|
"github.com/xtls/xray-core/core"
|
|
"github.com/xtls/xray-core/features/policy"
|
|
"github.com/xtls/xray-core/features/routing"
|
|
"github.com/xtls/xray-core/transport"
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
|
"github.com/xtls/xray-core/transport/internet/tls"
|
|
)
|
|
|
|
func init() {
|
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
|
d := new(DokodemoDoor)
|
|
err := core.RequireFeatures(ctx, func(pm policy.Manager) error {
|
|
return d.Init(config.(*Config), pm, session.SockoptFromContext(ctx))
|
|
})
|
|
return d, err
|
|
}))
|
|
}
|
|
|
|
type DokodemoDoor struct {
|
|
policyManager policy.Manager
|
|
config *Config
|
|
address net.Address
|
|
port net.Port
|
|
portMap map[string]string
|
|
sockopt *session.Sockopt
|
|
}
|
|
|
|
// Init initializes the DokodemoDoor instance with necessary parameters.
|
|
func (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error {
|
|
if len(config.Networks) == 0 {
|
|
return errors.New("no network specified")
|
|
}
|
|
d.config = config
|
|
d.address = config.GetPredefinedAddress()
|
|
d.port = net.Port(config.Port)
|
|
d.portMap = config.PortMap
|
|
d.policyManager = pm
|
|
d.sockopt = sockopt
|
|
|
|
return nil
|
|
}
|
|
|
|
// Network implements proxy.Inbound.
|
|
func (d *DokodemoDoor) Network() []net.Network {
|
|
return d.config.Networks
|
|
}
|
|
|
|
func (d *DokodemoDoor) policy() policy.Session {
|
|
config := d.config
|
|
p := d.policyManager.ForLevel(config.UserLevel)
|
|
return p
|
|
}
|
|
|
|
// Process implements proxy.Inbound.
|
|
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
|
errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr())
|
|
dest := net.Destination{
|
|
Network: network,
|
|
Address: d.address,
|
|
Port: d.port,
|
|
}
|
|
|
|
if !d.config.FollowRedirect {
|
|
host, port, err := net.SplitHostPort(conn.LocalAddr().String())
|
|
if dest.Address == nil {
|
|
if err != nil {
|
|
dest.Address = net.DomainAddress("localhost")
|
|
} else {
|
|
if strings.Contains(host, ".") {
|
|
dest.Address = net.LocalHostIP
|
|
} else {
|
|
dest.Address = net.LocalHostIPv6
|
|
}
|
|
}
|
|
}
|
|
if dest.Port == 0 {
|
|
dest.Port = net.Port(common.Must2(strconv.Atoi(port)))
|
|
}
|
|
if d.portMap != nil && d.portMap[port] != "" {
|
|
h, p, _ := net.SplitHostPort(d.portMap[port])
|
|
if len(h) > 0 {
|
|
dest.Address = net.ParseAddress(h)
|
|
}
|
|
if len(p) > 0 {
|
|
dest.Port = net.Port(common.Must2(strconv.Atoi(p)))
|
|
}
|
|
}
|
|
}
|
|
|
|
destinationOverridden := false
|
|
if d.config.FollowRedirect {
|
|
outbounds := session.OutboundsFromContext(ctx)
|
|
if len(outbounds) > 0 {
|
|
ob := outbounds[len(outbounds)-1]
|
|
if ob.Target.IsValid() {
|
|
dest = ob.Target
|
|
destinationOverridden = true
|
|
}
|
|
}
|
|
if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden {
|
|
if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
|
|
dest.Address = net.DomainAddress(serverName)
|
|
destinationOverridden = true
|
|
ctx = session.ContextWithMitmServerName(ctx, serverName)
|
|
}
|
|
if tlsConn.NegotiatedProtocol() != "h2" {
|
|
ctx = session.ContextWithMitmAlpn11(ctx, true)
|
|
}
|
|
}
|
|
}
|
|
if !dest.IsValid() || dest.Address == nil {
|
|
return errors.New("unable to get destination")
|
|
}
|
|
|
|
inbound := session.InboundFromContext(ctx)
|
|
inbound.Name = "dokodemo-door"
|
|
inbound.CanSpliceCopy = 1
|
|
inbound.User = &protocol.MemoryUser{
|
|
Level: d.config.UserLevel,
|
|
}
|
|
|
|
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
|
From: conn.RemoteAddr(),
|
|
To: dest,
|
|
Status: log.AccessAccepted,
|
|
Reason: "",
|
|
})
|
|
errors.LogInfo(ctx, "received request for ", conn.RemoteAddr())
|
|
|
|
var reader buf.Reader
|
|
if dest.Network == net.Network_TCP {
|
|
reader = buf.NewReader(conn)
|
|
} else {
|
|
reader = buf.NewPacketReader(conn)
|
|
}
|
|
|
|
var writer buf.Writer
|
|
if network == net.Network_TCP {
|
|
writer = buf.NewWriter(conn)
|
|
} else {
|
|
// if we are in TPROXY mode, use linux's udp forging functionality
|
|
if !destinationOverridden {
|
|
writer = &buf.SequentialWriter{Writer: conn}
|
|
} else {
|
|
back := conn.RemoteAddr().(*net.UDPAddr)
|
|
if !dest.Address.Family().IsIP() {
|
|
if len(back.IP) == 4 {
|
|
dest.Address = net.AnyIP
|
|
} else {
|
|
dest.Address = net.AnyIPv6
|
|
}
|
|
}
|
|
addr := &net.UDPAddr{
|
|
IP: dest.Address.IP(),
|
|
Port: int(dest.Port),
|
|
}
|
|
var mark int
|
|
if d.sockopt != nil {
|
|
mark = int(d.sockopt.Mark)
|
|
}
|
|
pConn, err := FakeUDP(addr, mark)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer = NewPacketWriter(ctx, pConn, &dest, mark, back)
|
|
defer writer.(*PacketWriter).Close() // close fake UDP conns
|
|
}
|
|
}
|
|
|
|
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
|
Reader: reader,
|
|
Writer: writer},
|
|
); err != nil {
|
|
return errors.New("failed to dispatch request").Base(err)
|
|
}
|
|
return nil // Unlike Dispatch(), DispatchLink() will not return until the outbound finishes Process()
|
|
}
|
|
|
|
func NewPacketWriter(ctx context.Context, conn net.PacketConn, d *net.Destination, mark int, back *net.UDPAddr) buf.Writer {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
writer := &PacketWriter{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
conn: conn,
|
|
conns: utils.NewTypedSyncMap[net.Destination, *PacketConnTimeWrapper](),
|
|
inactiveConns: make([]*PacketConnTimeWrapper, 0),
|
|
mark: mark,
|
|
back: back,
|
|
}
|
|
timedconn := &PacketConnTimeWrapper{
|
|
PacketConn: conn,
|
|
lastUsedTime: time.Now(),
|
|
isMainConn: true,
|
|
}
|
|
writer.conns.Store(*d, timedconn)
|
|
go writer.cleanInactiveConns()
|
|
return writer
|
|
}
|
|
|
|
type PacketWriter struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
conn net.PacketConn
|
|
conns *utils.TypedSyncMap[net.Destination, *PacketConnTimeWrapper]
|
|
inactiveConns []*PacketConnTimeWrapper
|
|
mark int
|
|
back *net.UDPAddr
|
|
}
|
|
|
|
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
for {
|
|
mb2, b := buf.SplitFirst(mb)
|
|
mb = mb2
|
|
if b == nil {
|
|
break
|
|
}
|
|
var err error
|
|
if b.UDP != nil && b.UDP.Address.Family().IsIP() {
|
|
conn, _ := w.conns.Load(*b.UDP)
|
|
if conn == nil {
|
|
fakeudpconn, err := FakeUDP(
|
|
&net.UDPAddr{
|
|
IP: b.UDP.Address.IP(),
|
|
Port: int(b.UDP.Port),
|
|
},
|
|
w.mark,
|
|
)
|
|
conn = &PacketConnTimeWrapper{
|
|
PacketConn: fakeudpconn,
|
|
lastUsedTime: time.Now(),
|
|
}
|
|
if err != nil {
|
|
errors.LogInfo(context.Background(), err.Error())
|
|
b.Release()
|
|
continue
|
|
}
|
|
w.conns.Store(*b.UDP, conn)
|
|
}
|
|
_, err = conn.WriteTo(b.Bytes(), w.back)
|
|
if err != nil {
|
|
errors.LogInfo(context.Background(), err.Error())
|
|
w.conns.Delete(*b.UDP)
|
|
conn.Close()
|
|
}
|
|
b.Release()
|
|
} else {
|
|
_, err = w.conn.WriteTo(b.Bytes(), w.back)
|
|
b.Release()
|
|
if err != nil {
|
|
buf.ReleaseMulti(mb)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *PacketWriter) Close() error {
|
|
w.cancel()
|
|
w.conns.Range(func(key net.Destination, conn *PacketConnTimeWrapper) bool {
|
|
common.CloseIfExists(conn)
|
|
return true
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (w *PacketWriter) cleanInactiveConns() {
|
|
ticker := time.NewTicker(60 * time.Second)
|
|
defer ticker.Stop()
|
|
select {
|
|
case <-ticker.C:
|
|
if len(w.inactiveConns) > 0 {
|
|
for _, conn := range w.inactiveConns {
|
|
common.CloseIfExists(conn)
|
|
}
|
|
}
|
|
w.conns.Range(func(key net.Destination, conn *PacketConnTimeWrapper) bool {
|
|
if conn != nil && !conn.isMainConn && time.Since(conn.lastUsedTime) > 120*time.Second {
|
|
w.conns.Delete(key)
|
|
}
|
|
w.inactiveConns = append(w.inactiveConns, conn)
|
|
return true
|
|
})
|
|
case <-w.ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
|
|
type PacketConnTimeWrapper struct {
|
|
net.PacketConn
|
|
lastUsedTime time.Time
|
|
isMainConn bool
|
|
}
|
|
|
|
func (c *PacketConnTimeWrapper) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
c.lastUsedTime = time.Now()
|
|
return c.PacketConn.WriteTo(b, addr)
|
|
} |