mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-13 01:38:24 +03:00
https://github.com/XTLS/Xray-core/pull/6771#issuecomment-5752154561 https://github.com/XTLS/Xray-core/pull/6795#issuecomment-6095740152 https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6116258188
281 lines
7.1 KiB
Go
281 lines
7.1 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"net/netip"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/net"
|
|
)
|
|
|
|
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
|
|
type Stack interface {
|
|
Start() error
|
|
Close() error
|
|
}
|
|
|
|
// StackOptions for the stack implementation
|
|
type StackOptions struct {
|
|
Tun Tun
|
|
MTU uint32
|
|
IdleTimeout time.Duration
|
|
}
|
|
|
|
// flow is what the packets of one connection have in common, src is the side of the system
|
|
type flow struct {
|
|
src, dst netip.AddrPort
|
|
}
|
|
|
|
// stack turns the packets that the system sends to the tun into connections and back.
|
|
// It has only what the two need to talk through a tun, and a local link when the system passes on
|
|
// what others send, see tcpConn
|
|
type stack struct {
|
|
ctx context.Context
|
|
tun Tun
|
|
mtu int
|
|
// segmenter is the tun if it takes segments that are larger than its MTU
|
|
segmenter segmenter
|
|
// window is the window of every connection
|
|
window int32
|
|
// handle is given every new connection
|
|
handle func(conn net.Conn, destination net.Destination)
|
|
udp *udpConnectionHandler
|
|
|
|
mu sync.Mutex
|
|
conns map[flow]*tcpConn
|
|
closed bool
|
|
// opening is how many of conns wait for the answer to their SYN-ACK
|
|
opening atomic.Int32
|
|
}
|
|
|
|
// NewStack builds new ip stack
|
|
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
|
s := &stack{
|
|
ctx: ctx,
|
|
tun: options.Tun,
|
|
mtu: int(options.MTU),
|
|
window: tcpWindow,
|
|
handle: handler.HandleConnection,
|
|
conns: make(map[flow]*tcpConn),
|
|
}
|
|
if t, ok := options.Tun.(segmenter); ok && t.Segments() {
|
|
s.segmenter = t
|
|
s.window = tcpLargeWindow
|
|
}
|
|
s.udp = newUdpConnectionHandler(s.handle, s.writeUDP)
|
|
return s, nil
|
|
}
|
|
|
|
// Start is called by Handler to bring stack to life
|
|
func (s *stack) Start() error {
|
|
go s.read()
|
|
return nil
|
|
}
|
|
|
|
// Close is called by Handler to shut down the stack
|
|
func (s *stack) Close() error {
|
|
s.mu.Lock()
|
|
s.closed = true
|
|
conns := make([]*tcpConn, 0, len(s.conns))
|
|
for _, c := range s.conns {
|
|
conns = append(conns, c)
|
|
}
|
|
s.mu.Unlock()
|
|
for _, c := range conns {
|
|
c.mu.Lock()
|
|
if c.err == nil {
|
|
c.reset()
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// read takes the packets of the tun until it is closed
|
|
func (s *stack) read() {
|
|
// a tun that does not split segments gives them as large as a packet can be
|
|
b := make([]byte, packetRoom+1<<16)
|
|
for {
|
|
b, err := s.tun.ReadPacket(b)
|
|
if err != nil {
|
|
s.mu.Lock()
|
|
closed := s.closed
|
|
s.mu.Unlock()
|
|
if !closed {
|
|
errors.LogErrorInner(s.ctx, err, "[tun] failed to read a packet, no more are read")
|
|
}
|
|
return
|
|
}
|
|
p, ok := parsePacket(b)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch p.proto {
|
|
case protoTCP:
|
|
s.handleTCP(p)
|
|
case protoUDP:
|
|
s.handleUDP(p)
|
|
case protoICMPv4, protoICMPv6:
|
|
s.handleICMP(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *stack) handleTCP(p packet) {
|
|
t := p.payload
|
|
if len(t) < tcpHeaderSize {
|
|
return
|
|
}
|
|
size := int(t[12]>>4) * 4
|
|
if size < tcpHeaderSize || size > len(t) {
|
|
return
|
|
}
|
|
f := flow{
|
|
src: netip.AddrPortFrom(p.src, binary.BigEndian.Uint16(t)),
|
|
dst: netip.AddrPortFrom(p.dst, binary.BigEndian.Uint16(t[2:])),
|
|
}
|
|
seg := segment{
|
|
seq: binary.BigEndian.Uint32(t[4:]),
|
|
ack: binary.BigEndian.Uint32(t[8:]),
|
|
flags: t[13],
|
|
window: binary.BigEndian.Uint16(t[14:]),
|
|
options: t[tcpHeaderSize:size],
|
|
payload: t[size:],
|
|
}
|
|
|
|
s.mu.Lock()
|
|
c := s.conns[f]
|
|
s.mu.Unlock()
|
|
if c != nil && c.input(seg) {
|
|
return
|
|
}
|
|
if seg.flags&(tcpSyn|tcpAck|tcpRst) != tcpSyn {
|
|
if seg.flags&tcpRst == 0 {
|
|
s.refuse(f, seg)
|
|
}
|
|
return
|
|
}
|
|
|
|
if s.opening.Load() >= tcpOpening {
|
|
return
|
|
}
|
|
c = newTCPConn(s, f, seg)
|
|
s.mu.Lock()
|
|
if s.closed {
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
s.conns[f] = c
|
|
s.mu.Unlock()
|
|
s.opening.Add(1)
|
|
c.mu.Lock()
|
|
c.sendSynAck()
|
|
c.arm(tcpTick)
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// refuse answers a segment of a connection that is not known with a reset
|
|
func (s *stack) refuse(f flow, seg segment) {
|
|
var b [ipv6HeaderSize + tcpHeaderSize]byte
|
|
n := putIPHeader(b[:], protoTCP, f.dst.Addr(), f.src.Addr())
|
|
t := b[n : n+tcpHeaderSize]
|
|
binary.BigEndian.PutUint16(t, f.dst.Port())
|
|
binary.BigEndian.PutUint16(t[2:], f.src.Port())
|
|
t[12] = tcpHeaderSize / 4 << 4
|
|
if seg.flags&tcpAck != 0 {
|
|
binary.BigEndian.PutUint32(t[4:], seg.ack)
|
|
t[13] = tcpRst
|
|
} else {
|
|
ack := seg.seq + uint32(len(seg.payload))
|
|
if seg.flags&(tcpSyn|tcpFin) != 0 {
|
|
ack++
|
|
}
|
|
binary.BigEndian.PutUint32(t[8:], ack)
|
|
t[13] = tcpRst | tcpAck
|
|
}
|
|
setIPLength(b[:n], n+tcpHeaderSize)
|
|
binary.BigEndian.PutUint16(t[16:], transportChecksum(b[:n], protoTCP, t, nil))
|
|
_ = s.tun.WritePacket(b[:n+tcpHeaderSize], nil)
|
|
}
|
|
|
|
// remove forgets a connection. c.mu is held
|
|
func (s *stack) remove(c *tcpConn) {
|
|
if !c.established {
|
|
s.opening.Add(-1)
|
|
}
|
|
s.mu.Lock()
|
|
if s.conns[c.flow] == c {
|
|
delete(s.conns, c.flow)
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *stack) handleUDP(p packet) {
|
|
u := p.payload
|
|
if len(u) < udpHeaderSize {
|
|
return
|
|
}
|
|
size := int(binary.BigEndian.Uint16(u[4:]))
|
|
if size < udpHeaderSize || size > len(u) {
|
|
return
|
|
}
|
|
// the packet waits for its reader, longer than read keeps it
|
|
b := buf.NewWithSize(int32(size - udpHeaderSize))
|
|
b.Write(u[udpHeaderSize:size])
|
|
src := net.UDPDestination(net.IPAddress(p.src.AsSlice()), net.Port(binary.BigEndian.Uint16(u)))
|
|
dst := net.UDPDestination(net.IPAddress(p.dst.AsSlice()), net.Port(binary.BigEndian.Uint16(u[2:])))
|
|
s.udp.HandlePacket(src, dst, b)
|
|
}
|
|
|
|
// writeUDP writes a packet with the payload from src to dst to the tun
|
|
func (s *stack) writeUDP(payload []byte, src net.Destination, dst net.Destination) error {
|
|
srcIP, _ := netip.AddrFromSlice(src.Address.IP())
|
|
dstIP, _ := netip.AddrFromSlice(dst.Address.IP())
|
|
srcIP, dstIP = srcIP.Unmap(), dstIP.Unmap()
|
|
if !srcIP.IsValid() || srcIP.Is4() != dstIP.Is4() {
|
|
return errors.New("failed to write udp packet from ", src, " to ", dst)
|
|
}
|
|
var b [ipv6HeaderSize + udpHeaderSize]byte
|
|
n := putIPHeader(b[:], protoUDP, srcIP, dstIP)
|
|
u := b[n : n+udpHeaderSize]
|
|
binary.BigEndian.PutUint16(u, uint16(src.Port))
|
|
binary.BigEndian.PutUint16(u[2:], uint16(dst.Port))
|
|
binary.BigEndian.PutUint16(u[4:], uint16(udpHeaderSize+len(payload)))
|
|
setIPLength(b[:n], n+udpHeaderSize+len(payload))
|
|
check := transportChecksum(b[:n], protoUDP, u, payload)
|
|
if check == 0 {
|
|
// zero stands for no checksum
|
|
check = 0xffff
|
|
}
|
|
binary.BigEndian.PutUint16(u[6:], check)
|
|
return s.tun.WritePacket(b[:n+udpHeaderSize], payload)
|
|
}
|
|
|
|
// handleICMP answers an echo request as its destination would
|
|
func (s *stack) handleICMP(p packet) {
|
|
m := p.payload
|
|
request, reply := byte(8), byte(0)
|
|
if p.proto == protoICMPv6 {
|
|
request, reply = 128, 129
|
|
}
|
|
if len(m) < 8 || m[0] != request || m[1] != 0 {
|
|
return
|
|
}
|
|
var b [ipv6HeaderSize]byte
|
|
n := putIPHeader(b[:], p.proto, p.dst, p.src)
|
|
setIPLength(b[:n], n+len(m))
|
|
// the reply is the request with another type
|
|
m[0], m[2], m[3] = reply, 0, 0
|
|
if p.proto == protoICMPv6 {
|
|
binary.BigEndian.PutUint16(m[2:], transportChecksum(b[:n], protoICMPv6, m, nil))
|
|
} else {
|
|
binary.BigEndian.PutUint16(m[2:], checksum(sum(m, 0)))
|
|
}
|
|
_ = s.tun.WritePacket(b[:n], m)
|
|
}
|