初版功能完成
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled

This commit is contained in:
qsc
2026-08-29 13:12:17 +08:00
commit 142e5dc7d6
217 changed files with 21313 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
// Package clientwg embeds wireguard-go around the single RemLink Wintun.
package clientwg
import (
"errors"
"os"
"sync"
"sync/atomic"
"golang.zx2c4.com/wireguard/tun"
)
var _ tun.Device = (*MuxTun)(nil)
// MuxTun is the deliberately thin Phase 1 wrapper around the real Wintun.
// Later phases add Read-side CIDR classification without changing this surface.
type MuxTun struct {
base tun.Device
router atomic.Pointer[PacketMux]
writeMu sync.Mutex
closeOnce sync.Once
closeErr error
}
// NewMuxTun wraps a live TUN device without changing its semantics.
func NewMuxTun(base tun.Device) *MuxTun {
return &MuxTun{base: base}
}
func (m *MuxTun) File() *os.File {
return m.base.File()
}
func (m *MuxTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
router := m.router.Load()
if router == nil {
return m.base.Read(bufs, sizes, offset)
}
if len(bufs) == 0 || len(sizes) < len(bufs) || offset < 0 {
return 0, errors.New("invalid MuxTun read buffers")
}
for {
count, readErr := m.base.Read(bufs, sizes, offset)
if count < 0 || count > len(bufs) {
return 0, errors.New("base TUN returned invalid batch count")
}
overlayCount := 0
for index := 0; index < count; index++ {
size := sizes[index]
if size < 0 || offset+size > len(bufs[index]) {
return 0, errors.New("base TUN returned invalid packet size")
}
packet := bufs[index][offset : offset+size]
class, sink := router.route(packet)
switch class {
case PacketOverlay:
if overlayCount != index {
if offset+size > len(bufs[overlayCount]) {
return 0, errors.New("destination batch buffer is too small")
}
copy(bufs[overlayCount][offset:offset+size], packet)
}
sizes[overlayCount] = size
overlayCount++
case PacketRemote:
if sink != nil {
owned := append([]byte(nil), packet...)
if !sink.Enqueue(owned) {
router.recordDrop(DropEvent{Reason: DropRemoteQueueFull})
}
}
}
}
if overlayCount > 0 || readErr != nil {
return overlayCount, readErr
}
// A batch containing only Remote/dropped packets is consumed here. Read
// again instead of returning 0,nil to wireguard-go and spinning it.
}
}
func (m *MuxTun) Write(bufs [][]byte, offset int) (int, error) {
m.writeMu.Lock()
defer m.writeMu.Unlock()
return m.base.Write(bufs, offset)
}
// SetPacketMux enables or replaces Engineer outbound classification. A nil
// value restores Phase 1 passthrough behavior.
func (m *MuxTun) SetPacketMux(router *PacketMux) { m.router.Store(router) }
// InjectInbound serializes Site Session replies with wireguard-go writes.
func (m *MuxTun) InjectInbound(packet []byte) error {
m.writeMu.Lock()
defer m.writeMu.Unlock()
_, err := m.base.Write([][]byte{packet}, 0)
return err
}
func (m *MuxTun) MTU() (int, error) {
return m.base.MTU()
}
func (m *MuxTun) Name() (string, error) {
return m.base.Name()
}
func (m *MuxTun) Events() <-chan tun.Event {
return m.base.Events()
}
func (m *MuxTun) Close() error {
m.closeOnce.Do(func() {
m.closeErr = m.base.Close()
})
return m.closeErr
}
func (m *MuxTun) BatchSize() int {
return m.base.BatchSize()
}