/* SPDX-License-Identifier: MIT
*
* Phobos
*/
package tun2socks
import (
"context"
"errors"
"sync/atomic"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/wintun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const linkQueueDepth = 512
type device struct {
session *wintun.Session
endpoint *channel.Endpoint
cancel context.CancelFunc
done chan struct{}
reportedDrop atomic.Bool
}
func newDevice(session *wintun.Session, mtu uint32) *device {
return &device{
session: session,
endpoint: channel.New(linkQueueDepth, mtu, ""),
done: make(chan struct{}, 2),
}
}
func (d *device) start(logf func(string, ...any)) {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
go d.pumpInbound(ctx, logf)
go d.pumpOutbound(ctx, logf)
}
func (d *device) stop() {
if d.cancel != nil {
d.cancel()
}
d.endpoint.Close()
<-d.done
<-d.done
}
func (d *device) pumpInbound(ctx context.Context, logf func(string, ...any)) {
defer func() { d.done <- struct{}{} }()
event := d.session.ReadWaitEvent()
for {
if ctx.Err() != nil {
return
}
packet, err := d.session.ReceivePacket()
switch {
case err == nil:
d.inject(packet)
d.session.ReleaseReceivePacket(packet)
case errors.Is(err, wintun.ErrNoMorePackets):
if _, err := windows.WaitForSingleObject(event, 250); err != nil {
logf("tun2socks: wait failed: %v", err)
return
}
default:
if ctx.Err() == nil {
logf("tun2socks: adapter read failed: %v", err)
}
return
}
}
}
func (d *device) inject(packet []byte) {
if len(packet) < 1 {
return
}
var protocol tcpip.NetworkProtocolNumber
switch header.IPVersion(packet) {
case header.IPv4Version:
protocol = header.IPv4ProtocolNumber
case header.IPv6Version:
protocol = header.IPv6ProtocolNumber
default:
return
}
buf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(packet),
})
d.endpoint.InjectInbound(protocol, buf)
buf.DecRef()
}
func (d *device) pumpOutbound(ctx context.Context, logf func(string, ...any)) {
defer func() { d.done <- struct{}{} }()
for {
packet := d.endpoint.ReadContext(ctx)
if packet == nil {
return
}
view := packet.ToView()
size := view.Size()
out, err := d.session.AllocateSendPacket(size)
if err == nil {
view.Read(out)
d.session.SendPacket(out)
} else if !d.reportedDrop.Swap(true) {
logf("tun2socks: adapter send ring is full, dropping packets: %v", err)
}
view.Release()
packet.DecRef()
}
}