github.com/sagernet/sing-box@v1.9.0-rc.20/outbound/wireguard.go (about)

     1  //go:build with_wireguard
     2  
     3  package outbound
     4  
     5  import (
     6  	"context"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"net"
    11  	"net/netip"
    12  	"strings"
    13  
    14  	"github.com/sagernet/sing-box/adapter"
    15  	"github.com/sagernet/sing-box/common/dialer"
    16  	C "github.com/sagernet/sing-box/constant"
    17  	"github.com/sagernet/sing-box/log"
    18  	"github.com/sagernet/sing-box/option"
    19  	"github.com/sagernet/sing-box/transport/wireguard"
    20  	"github.com/sagernet/sing-dns"
    21  	"github.com/sagernet/sing-tun"
    22  	E "github.com/sagernet/sing/common/exceptions"
    23  	M "github.com/sagernet/sing/common/metadata"
    24  	N "github.com/sagernet/sing/common/network"
    25  	"github.com/sagernet/sing/common/x/list"
    26  	"github.com/sagernet/sing/service"
    27  	"github.com/sagernet/sing/service/pause"
    28  	"github.com/sagernet/wireguard-go/conn"
    29  	"github.com/sagernet/wireguard-go/device"
    30  )
    31  
    32  var (
    33  	_ adapter.Outbound                = (*WireGuard)(nil)
    34  	_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
    35  )
    36  
    37  type WireGuard struct {
    38  	myOutboundAdapter
    39  	ctx           context.Context
    40  	workers       int
    41  	peers         []wireguard.PeerConfig
    42  	useStdNetBind bool
    43  	listener      N.Dialer
    44  	ipcConf       string
    45  
    46  	pauseManager  pause.Manager
    47  	pauseCallback *list.Element[pause.Callback]
    48  	bind          conn.Bind
    49  	device        *device.Device
    50  	tunDevice     wireguard.Device
    51  }
    52  
    53  func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) {
    54  	outbound := &WireGuard{
    55  		myOutboundAdapter: myOutboundAdapter{
    56  			protocol:     C.TypeWireGuard,
    57  			network:      options.Network.Build(),
    58  			router:       router,
    59  			logger:       logger,
    60  			tag:          tag,
    61  			dependencies: withDialerDependency(options.DialerOptions),
    62  		},
    63  		ctx:          ctx,
    64  		workers:      options.Workers,
    65  		pauseManager: service.FromContext[pause.Manager](ctx),
    66  	}
    67  	peers, err := wireguard.ParsePeers(options)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	outbound.peers = peers
    72  	if len(options.LocalAddress) == 0 {
    73  		return nil, E.New("missing local address")
    74  	}
    75  	if options.GSO {
    76  		if options.GSO && options.Detour != "" {
    77  			return nil, E.New("gso is conflict with detour")
    78  		}
    79  		options.IsWireGuardListener = true
    80  		outbound.useStdNetBind = true
    81  	}
    82  	listener, err := dialer.New(router, options.DialerOptions)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	outbound.listener = listener
    87  	var privateKey string
    88  	{
    89  		bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)
    90  		if err != nil {
    91  			return nil, E.Cause(err, "decode private key")
    92  		}
    93  		privateKey = hex.EncodeToString(bytes)
    94  	}
    95  	outbound.ipcConf = "private_key=" + privateKey
    96  	mtu := options.MTU
    97  	if mtu == 0 {
    98  		mtu = 1408
    99  	}
   100  	var wireTunDevice wireguard.Device
   101  	if !options.SystemInterface && tun.WithGVisor {
   102  		wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu)
   103  	} else {
   104  		wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu, options.GSO)
   105  	}
   106  	if err != nil {
   107  		return nil, E.Cause(err, "create WireGuard device")
   108  	}
   109  	outbound.tunDevice = wireTunDevice
   110  	return outbound, nil
   111  }
   112  
   113  func (w *WireGuard) Start() error {
   114  	err := wireguard.ResolvePeers(w.ctx, w.router, w.peers)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	var bind conn.Bind
   119  	if w.useStdNetBind {
   120  		bind = conn.NewStdNetBind(w.listener.(dialer.WireGuardListener))
   121  	} else {
   122  		var (
   123  			isConnect   bool
   124  			connectAddr netip.AddrPort
   125  			reserved    [3]uint8
   126  		)
   127  		peerLen := len(w.peers)
   128  		if peerLen == 1 {
   129  			isConnect = true
   130  			connectAddr = w.peers[0].Endpoint
   131  			reserved = w.peers[0].Reserved
   132  		}
   133  		bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved)
   134  	}
   135  	wgDevice := device.NewDevice(w.tunDevice, bind, &device.Logger{
   136  		Verbosef: func(format string, args ...interface{}) {
   137  			w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
   138  		},
   139  		Errorf: func(format string, args ...interface{}) {
   140  			w.logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
   141  		},
   142  	}, w.workers)
   143  	ipcConf := w.ipcConf
   144  	for _, peer := range w.peers {
   145  		ipcConf += peer.GenerateIpcLines()
   146  	}
   147  	err = wgDevice.IpcSet(ipcConf)
   148  	if err != nil {
   149  		return E.Cause(err, "setup wireguard: \n", ipcConf)
   150  	}
   151  	w.device = wgDevice
   152  	w.pauseCallback = w.pauseManager.RegisterCallback(w.onPauseUpdated)
   153  	return w.tunDevice.Start()
   154  }
   155  
   156  func (w *WireGuard) Close() error {
   157  	if w.device != nil {
   158  		w.device.Close()
   159  	}
   160  	if w.pauseCallback != nil {
   161  		w.pauseManager.UnregisterCallback(w.pauseCallback)
   162  	}
   163  	w.tunDevice.Close()
   164  	return nil
   165  }
   166  
   167  func (w *WireGuard) InterfaceUpdated() {
   168  	w.device.BindUpdate()
   169  	return
   170  }
   171  
   172  func (w *WireGuard) onPauseUpdated(event int) {
   173  	switch event {
   174  	case pause.EventDevicePaused:
   175  		w.device.Down()
   176  	case pause.EventDeviceWake:
   177  		w.device.Up()
   178  	}
   179  }
   180  
   181  func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
   182  	switch network {
   183  	case N.NetworkTCP:
   184  		w.logger.InfoContext(ctx, "outbound connection to ", destination)
   185  	case N.NetworkUDP:
   186  		w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
   187  	}
   188  	if destination.IsFqdn() {
   189  		destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  		return N.DialSerial(ctx, w.tunDevice, network, destination, destinationAddresses)
   194  	}
   195  	return w.tunDevice.DialContext(ctx, network, destination)
   196  }
   197  
   198  func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
   199  	w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
   200  	if destination.IsFqdn() {
   201  		destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		packetConn, _, err := N.ListenSerial(ctx, w.tunDevice, destination, destinationAddresses)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		return packetConn, err
   210  	}
   211  	return w.tunDevice.ListenPacket(ctx, destination)
   212  }
   213  
   214  func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
   215  	return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
   216  }
   217  
   218  func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
   219  	return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
   220  }