Files
XTLS_Xray-core/proxy/tun/stack.go
T

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)
}