add vtun listener and handler

This commit is contained in:
ginuerzh
2025-07-23 21:30:21 +08:00
parent afcf4a5252
commit acae52ab89
20 changed files with 1157 additions and 85 deletions
+4 -4
View File
@@ -27,7 +27,7 @@ type tunListener struct {
addr net.Addr
cqueue chan net.Conn
closed chan struct{}
logger logger.Logger
log logger.Logger
md metadata
options listener.Options
routes []*router.Route
@@ -39,7 +39,7 @@ func NewListener(opts ...listener.Option) listener.Listener {
opt(&options)
}
return &tunListener{
logger: options.Logger,
log: options.Logger,
options: options,
}
}
@@ -83,7 +83,7 @@ func (l *tunListener) listenLoop() {
}
addrs, _ := itf.Addrs()
l.logger.Infof("name: %s, net: %s, mtu: %d, addrs: %s",
l.log.Infof("name: %s, net: %s, mtu: %d, addrs: %s",
itf.Name, ip, itf.MTU, addrs)
var c net.Conn
@@ -112,7 +112,7 @@ func (l *tunListener) listenLoop() {
return nil
}()
if err != nil {
l.logger.Error(err)
l.log.Error(err)
cancel()
}
+43 -35
View File
@@ -7,53 +7,51 @@ import (
"golang.zx2c4.com/wireguard/tun"
)
const (
tunOffsetBytes = 16
MaxMessageSize = math.MaxUint16
)
type tunDevice struct {
dev tun.Device
elems [][MaxMessageSize]byte
rbufs [][]byte
wbuf [tunOffsetBytes + MaxMessageSize]byte
dev tun.Device
packets int
sizes []int
rbufs [][]byte
wbufs [][]byte
wbuf []byte
}
func (d *tunDevice) Read(p []byte) (n int, err error) {
if len(d.rbufs) > 0 {
n = copy(p, d.rbufs[0])
d.rbufs = d.rbufs[1:]
return
}
if d.packets > 0 {
for i, size := range d.sizes {
if size > 0 {
n = copy(p, d.rbufs[i][:size])
sizes := make([]int, len(d.elems))
bufs := make([][]byte, len(d.elems))
for i := range d.elems {
bufs[i] = d.elems[i][:]
}
nn, err := d.dev.Read(bufs, sizes, 0)
if err != nil {
return
}
for i := 0; i < nn; i++ {
if sizes[i] <= 0 {
continue
d.sizes[i] = 0
d.packets--
return
}
}
d.rbufs = append(d.rbufs, bufs[i][:sizes[i]])
}
n = copy(p, d.rbufs[0])
d.rbufs = d.rbufs[1:]
d.rbufs[0] = p
packets, err := d.dev.Read(d.rbufs, d.sizes, readOffset)
if err != nil && err != tun.ErrTooManySegments {
return
}
n = d.sizes[0]
d.sizes[0] = 0
d.packets = packets - 1
return
}
func (d *tunDevice) Write(p []byte) (n int, err error) {
buf := d.wbuf[:]
n = copy(buf[tunOffsetBytes:], p)
_, err = d.dev.Write([][]byte{buf[:tunOffsetBytes+n]}, tunOffsetBytes)
if writeOffset > 0 {
copy(d.wbuf[writeOffset:], p)
d.wbufs[0] = d.wbuf[:writeOffset+len(p)]
} else {
d.wbufs[0] = p
}
_, err = d.dev.Write(d.wbufs, writeOffset)
n = len(p)
return
}
@@ -67,9 +65,19 @@ func (l *tunListener) createTunDevice() (dev io.ReadWriteCloser, name string, er
return
}
batchSize := 16
rbufs := make([][]byte, batchSize)
for i := 1; i < len(rbufs); i++ {
rbufs[i] = make([]byte, math.MaxUint16)
}
dev = &tunDevice{
dev: ifce,
elems: make([][MaxMessageSize]byte, ifce.BatchSize()),
sizes: make([]int, batchSize),
rbufs: rbufs,
wbufs: make([][]byte, 1),
wbuf: make([]byte, math.MaxUint16+writeOffset),
}
name, err = ifce.Name()
+2
View File
@@ -10,6 +10,8 @@ import (
const (
defaultTunName = "utun"
readOffset = 4
writeOffset = 4
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
+7 -2
View File
@@ -10,6 +10,11 @@ import (
"github.com/vishvananda/netlink"
)
const (
readOffset = 0
writeOffset = 16
)
func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) {
dev, name, err = l.createTunDevice()
if err != nil {
@@ -30,7 +35,7 @@ func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.I
if err = netlink.AddrAdd(link, &netlink.Addr{
IPNet: &net,
}); err != nil {
l.logger.Error(err)
l.log.Error(err)
continue
}
}
@@ -52,7 +57,7 @@ func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.I
}
if len(dnsServers) > 0 {
cmd := fmt.Sprintf("resolvectl dns %s %s", name, strings.Join(dnsServers, " "))
l.logger.Debug(cmd)
l.log.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
+2
View File
@@ -12,6 +12,8 @@ import (
const (
defaultTunName = "tun0"
readOffset = 4
writeOffset = 4
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
+2
View File
@@ -10,6 +10,8 @@ import (
const (
defaultTunName = "wintun"
readOffset = 0
writeOffset = 0
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
+51
View File
@@ -0,0 +1,51 @@
package tun
import (
"context"
"errors"
"io"
"net"
"time"
)
type conn struct {
ifce io.ReadWriteCloser
laddr net.Addr
raddr net.Addr
cancel context.CancelFunc
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.ifce.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.ifce.Write(b)
}
func (c *conn) LocalAddr() net.Addr {
return c.laddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "tun", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) Close() (err error) {
if c.cancel != nil {
c.cancel()
}
return c.ifce.Close()
}
+151
View File
@@ -0,0 +1,151 @@
package tun
import (
"context"
"net"
"time"
"github.com/go-gost/core/limiter"
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/core/router"
traffic_limiter "github.com/go-gost/x/limiter/traffic"
limiter_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
stats "github.com/go-gost/x/observer/stats/wrapper"
"github.com/go-gost/x/registry"
)
func init() {
registry.ListenerRegistry().Register("vtun", NewListener)
}
type tunListener struct {
addr net.Addr
cqueue chan net.Conn
closed chan struct{}
log logger.Logger
md metadata
options listener.Options
routes []*router.Route
}
func NewListener(opts ...listener.Option) listener.Listener {
options := listener.Options{}
for _, opt := range opts {
opt(&options)
}
return &tunListener{
log: options.Logger,
options: options,
}
}
func (l *tunListener) Init(md mdata.Metadata) (err error) {
if err = l.parseMetadata(md); err != nil {
return
}
l.addr = &Addr{Name: l.options.Addr}
l.cqueue = make(chan net.Conn)
l.closed = make(chan struct{})
go l.listenLoop()
return
}
func (l *tunListener) listenLoop() {
for {
ctx, cancel := context.WithCancel(context.Background())
err := func() error {
ifce, name, ip, err := l.createTun()
if err != nil {
if ifce != nil {
ifce.Close()
}
return err
}
itf, err := net.InterfaceByName(name)
if err != nil {
return err
}
addrs, _ := itf.Addrs()
l.log.Infof("name: %s, net: %s, mtu: %d, addrs: %s",
itf.Name, ip, itf.MTU, addrs)
var c net.Conn
c = &conn{
ifce: ifce,
laddr: l.addr,
raddr: &net.IPAddr{IP: ip},
cancel: cancel,
}
c = metrics.WrapConn(l.options.Service, c)
c = stats.WrapConn(c, l.options.Stats)
c = limiter_wrapper.WrapConn(
c,
l.options.TrafficLimiter,
traffic_limiter.ServiceLimitKey,
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
limiter.NetworkOption(c.LocalAddr().Network()),
)
l.cqueue <- c
return nil
}()
if err != nil {
l.log.Error(err)
cancel()
}
select {
case <-ctx.Done():
case <-l.closed:
return
}
time.Sleep(time.Second)
}
}
func (l *tunListener) Accept() (net.Conn, error) {
select {
case conn := <-l.cqueue:
return conn, nil
case <-l.closed:
}
return nil, listener.ErrClosed
}
func (l *tunListener) Addr() net.Addr {
return l.addr
}
func (l *tunListener) Close() error {
select {
case <-l.closed:
return net.ErrClosed
default:
close(l.closed)
}
return nil
}
type Addr struct {
Name string
}
func (a *Addr) Network() string {
return "tun"
}
func (a *Addr) String() string {
return a.Name
}
+93
View File
@@ -0,0 +1,93 @@
package tun
import (
"net"
"strings"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/core/router"
tun_util "github.com/go-gost/x/internal/util/tun"
mdutil "github.com/go-gost/x/metadata/util"
xrouter "github.com/go-gost/x/router"
)
const (
defaultMTU = 1420
)
type metadata struct {
config *tun_util.Config
}
func (l *tunListener) parseMetadata(md mdata.Metadata) (err error) {
config := &tun_util.Config{
Name: mdutil.GetString(md, "name", "tun.name"),
Peer: mdutil.GetString(md, "peer", "tun.peer"),
MTU: mdutil.GetInt(md, "mtu", "tun.mtu"),
}
if config.MTU <= 0 {
config.MTU = defaultMTU
}
if gw := mdutil.GetString(md, "gw", "tun.gw"); gw != "" {
config.Gateway = net.ParseIP(gw)
}
for _, s := range strings.Split(mdutil.GetString(md, "net", "tun.net"), ",") {
if s = strings.TrimSpace(s); s == "" {
continue
}
ip, ipNet, err := net.ParseCIDR(s)
if err != nil {
continue
}
config.Net = append(config.Net, net.IPNet{
IP: ip,
Mask: ipNet.Mask,
})
}
for _, s := range strings.Split(mdutil.GetString(md, "route", "tun.route"), ",") {
gw := ""
if config.Gateway != nil {
gw = config.Gateway.String()
}
if route := xrouter.ParseRoute(strings.TrimSpace(s), gw); route != nil {
l.routes = append(l.routes, route)
}
}
for _, s := range mdutil.GetStrings(md, "routes", "tun.routes") {
ss := strings.SplitN(s, " ", 2)
if len(ss) == 2 {
_, ipNet, _ := net.ParseCIDR(strings.TrimSpace(ss[0]))
if ipNet == nil {
continue
}
gw := net.ParseIP(ss[1])
if gw == nil {
gw = config.Gateway
}
gateway := ""
if gw != nil {
gateway = gw.String()
}
l.routes = append(l.routes, &router.Route{
Net: ipNet,
Dst: ipNet.String(),
Gateway: gateway,
})
}
}
for _, v := range strings.Split(mdutil.GetString(md, "dns", "tun.dns"), ",") {
if ip := net.ParseIP(strings.TrimSpace(v)); ip != nil {
config.DNS = append(config.DNS, ip)
}
}
l.md.config = config
return
}
+91
View File
@@ -0,0 +1,91 @@
package tun
import (
"io"
"math"
"github.com/go-gost/core/logger"
"golang.zx2c4.com/wireguard/tun"
)
type tunDevice struct {
dev tun.Device
packets int
sizes []int
rbufs [][]byte
wbufs [][]byte
wbuf []byte
log logger.Logger
}
func (d *tunDevice) Read(p []byte) (n int, err error) {
if d.packets > 0 {
for i, size := range d.sizes {
if size > 0 {
n = copy(p, d.rbufs[i][:size])
d.sizes[i] = 0
d.packets--
return
}
}
}
d.rbufs[0] = p
packets, err := d.dev.Read(d.rbufs, d.sizes, readOffset)
if err != nil && err != tun.ErrTooManySegments {
return
}
n = d.sizes[0]
d.sizes[0] = 0
d.packets = packets - 1
// d.log.Debugf("read tun: (%d), % x", n, p[:n])
return
}
func (d *tunDevice) Write(p []byte) (n int, err error) {
if writeOffset > 0 {
copy(d.wbuf[writeOffset:], p)
d.wbufs[0] = d.wbuf[:writeOffset+len(p)]
} else {
d.wbufs[0] = p
}
_, err = d.dev.Write(d.wbufs, writeOffset)
n = len(p)
return
}
func (d *tunDevice) Close() error {
return d.dev.Close()
}
func (l *tunListener) createTunDevice() (dev io.ReadWriteCloser, name string, err error) {
ifce, err := tun.CreateTUN(l.md.config.Name, l.md.config.MTU)
if err != nil {
return
}
batchSize := 16
rbufs := make([][]byte, batchSize)
for i := 1; i < len(rbufs); i++ {
rbufs[i] = make([]byte, math.MaxUint16)
}
dev = &tunDevice{
dev: ifce,
sizes: make([]int, batchSize),
rbufs: rbufs,
wbufs: make([][]byte, 1),
wbuf: make([]byte, math.MaxUint16+writeOffset),
log: l.log,
}
name, err = ifce.Name()
return
}
+58
View File
@@ -0,0 +1,58 @@
package tun
import (
"fmt"
"io"
"net"
"os/exec"
"strings"
)
const (
defaultTunName = "utun"
readOffset = 4
writeOffset = 4
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
if l.md.config.Name == "" {
l.md.config.Name = defaultTunName
}
ifce, name, err = l.createTunDevice()
if err != nil {
return
}
peer := l.md.config.Peer
if peer == "" {
peer = ip.String()
}
if len(l.md.config.Net) > 0 {
cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up",
name, l.md.config.Net[0].String(), l.md.config.Peer, l.md.config.MTU)
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if err = exec.Command(args[0], args[1:]...).Run(); err != nil {
return
}
ip = l.md.config.Net[0].IP
}
if err = l.addRoutes(name); err != nil {
return
}
return
}
func (l *tunListener) addRoutes(ifName string) error {
for _, route := range l.routes {
cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName)
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if err := exec.Command(args[0], args[1:]...).Run(); err != nil {
return err
}
}
return nil
}
+90
View File
@@ -0,0 +1,90 @@
package tun
import (
"fmt"
"io"
"net"
"os/exec"
"strings"
"github.com/vishvananda/netlink"
)
const (
readOffset = 0
writeOffset = 16
)
func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) {
dev, name, err = l.createTunDevice()
if err != nil {
return
}
ifce, err := net.InterfaceByName(name)
if err != nil {
return
}
link, err := netlink.LinkByName(name)
if err != nil {
return
}
for _, net := range l.md.config.Net {
if err = netlink.AddrAdd(link, &netlink.Addr{
IPNet: &net,
}); err != nil {
l.log.Error(err)
continue
}
}
if len(l.md.config.Net) > 0 {
ip = l.md.config.Net[0].IP
}
if err = netlink.LinkSetUp(link); err != nil {
return
}
if err = l.addRoutes(ifce); err != nil {
return
}
var dnsServers []string
for _, dns := range l.md.config.DNS {
dnsServers = append(dnsServers, dns.String())
}
if len(dnsServers) > 0 {
cmd := fmt.Sprintf("resolvectl dns %s %s", name, strings.Join(dnsServers, " "))
l.log.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
}
}
return
}
func (l *tunListener) addRoutes(ifce *net.Interface) error {
for _, route := range l.routes {
if route.Net == nil {
continue
}
r := netlink.Route{
Dst: route.Net,
Gw: net.ParseIP(route.Gateway),
}
if r.Gw == nil {
r.LinkIndex = ifce.Index
}
if err := netlink.RouteReplace(&r); err != nil {
return fmt.Errorf("add route %v %v: %v", r.Dst, r.Gw, err)
}
}
return nil
}
+58
View File
@@ -0,0 +1,58 @@
//go:build !linux && !windows && !darwin
package tun
import (
"fmt"
"io"
"net"
"os/exec"
"strings"
)
const (
defaultTunName = "tun0"
readOffset = 4
writeOffset = 4
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
if l.md.config.Name == "" {
l.md.config.Name = defaultTunName
}
ifce, name, err = l.createTunDevice()
if err != nil {
return
}
if len(l.md.config.Net) > 0 {
cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up",
name, l.md.config.Net[0].String(), l.md.config.MTU)
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
}
ip = l.md.config.Net[0].IP
}
if err = l.addRoutes(name); err != nil {
return
}
return
}
func (l *tunListener) addRoutes(ifName string) error {
for _, route := range l.routes {
cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName)
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
return fmt.Errorf("%s: %v", cmd, er)
}
}
return nil
}
+87
View File
@@ -0,0 +1,87 @@
package tun
import (
"fmt"
"io"
"net"
"os/exec"
"strings"
)
const (
defaultTunName = "wintun"
readOffset = 0
writeOffset = 0
)
func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net.IP, err error) {
if l.md.config.Name == "" {
l.md.config.Name = defaultTunName
}
ifce, name, err = l.createTunDevice()
if err != nil {
return
}
if len(l.md.config.Net) > 0 {
ipNet := l.md.config.Net[0]
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+
"source=static addr=%s mask=%s gateway=none",
name, ipNet.IP.String(), ipMask(ipNet.Mask))
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
}
ip = ipNet.IP
}
if err = l.addRoutes(name, l.md.config.Gateway); err != nil {
return
}
for _, dns := range l.md.config.DNS {
cmd := fmt.Sprintf("netsh interface ip add dnsservers name=%s address=%s validate=no", name, dns.String())
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
}
}
return
}
func (l *tunListener) addRoutes(ifName string, gw net.IP) error {
for _, route := range l.routes {
l.deleteRoute(ifName, route.Net.String())
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active",
route.Net.String(), ifName)
if gw != nil {
cmd += " nexthop=" + gw.String()
}
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
return fmt.Errorf("%s: %v", cmd, er)
}
}
return nil
}
func (l *tunListener) deleteRoute(ifName string, route string) error {
cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=%s store=active",
route, ifName)
l.logger.Debug(cmd)
args := strings.Split(cmd, " ")
return exec.Command(args[0], args[1:]...).Run()
}
func ipMask(mask net.IPMask) string {
return fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
}