diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index f82d120e..f136a20a 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -41,19 +41,26 @@ type WireGuard struct { type WireGuardOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Ip string `proxy:"ip,omitempty"` - Ipv6 string `proxy:"ipv6,omitempty"` - PrivateKey string `proxy:"private-key"` - PublicKey string `proxy:"public-key"` - PreSharedKey string `proxy:"pre-shared-key,omitempty"` - Reserved []uint8 `proxy:"reserved,omitempty"` - Workers int `proxy:"workers,omitempty"` - MTU int `proxy:"mtu,omitempty"` - UDP bool `proxy:"udp,omitempty"` - PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` + WireGuardPeerOption + Name string `proxy:"name"` + PrivateKey string `proxy:"private-key"` + Workers int `proxy:"workers,omitempty"` + MTU int `proxy:"mtu,omitempty"` + UDP bool `proxy:"udp,omitempty"` + PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` + + Peers []WireGuardPeerOption `proxy:"peers,omitempty"` +} + +type WireGuardPeerOption struct { + Server string `proxy:"server"` + Port int `proxy:"port"` + Ip string `proxy:"ip,omitempty"` + Ipv6 string `proxy:"ipv6,omitempty"` + PublicKey string `proxy:"public-key,omitempty"` + PreSharedKey string `proxy:"pre-shared-key,omitempty"` + Reserved []uint8 `proxy:"reserved,omitempty"` + AllowedIPs []string `proxy:"allowed_ips,omitempty"` } type wgSingDialer struct { @@ -80,32 +87,11 @@ func (d wgNetDialer) DialContext(ctx context.Context, network, address string) ( return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap()) } -func NewWireGuard(option WireGuardOption) (*WireGuard, error) { - outbound := &WireGuard{ - Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.WireGuard, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), - }, - dialer: &wgSingDialer{dialer: dialer.NewDialer()}, - } - runtime.SetFinalizer(outbound, closeWireGuard) +func (option WireGuardPeerOption) Addr() M.Socksaddr { + return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)) +} - var reserved [3]uint8 - if len(option.Reserved) > 0 { - if len(option.Reserved) != 3 { - return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved)) - } - reserved[0] = uint8(option.Reserved[0]) - reserved[1] = uint8(option.Reserved[1]) - reserved[2] = uint8(option.Reserved[2]) - } - peerAddr := M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)) - outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, true, peerAddr, reserved) +func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) { localPrefixes := make([]netip.Prefix, 0, 2) if len(option.Ip) > 0 { if !strings.Contains(option.Ip, "/") { @@ -130,7 +116,46 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { if len(localPrefixes) == 0 { return nil, E.New("missing local address") } - var privateKey, peerPublicKey, preSharedKey string + return localPrefixes, nil +} + +func NewWireGuard(option WireGuardOption) (*WireGuard, error) { + outbound := &WireGuard{ + Base: &Base{ + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.WireGuard, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + dialer: &wgSingDialer{dialer: dialer.NewDialer()}, + } + runtime.SetFinalizer(outbound, closeWireGuard) + + var reserved [3]uint8 + if len(option.Reserved) > 0 { + if len(option.Reserved) != 3 { + return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved)) + } + copy(reserved[:], option.Reserved) + } + var isConnect bool + var connectAddr M.Socksaddr + if len(option.Peers) < 2 { + isConnect = true + if len(option.Peers) == 1 { + connectAddr = option.Peers[0].Addr() + } else { + connectAddr = option.Addr() + } + } + outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, isConnect, connectAddr, reserved) + + var localPrefixes []netip.Prefix + + var privateKey string { bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey) if err != nil { @@ -138,40 +163,92 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { } privateKey = hex.EncodeToString(bytes) } - { - bytes, err := base64.StdEncoding.DecodeString(option.PublicKey) - if err != nil { - return nil, E.Cause(err, "decode peer public key") - } - peerPublicKey = hex.EncodeToString(bytes) - } - if option.PreSharedKey != "" { - bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey) - if err != nil { - return nil, E.Cause(err, "decode pre shared key") - } - preSharedKey = hex.EncodeToString(bytes) - } ipcConf := "private_key=" + privateKey - ipcConf += "\npublic_key=" + peerPublicKey - ipcConf += "\nendpoint=" + peerAddr.String() - if preSharedKey != "" { - ipcConf += "\npreshared_key=" + preSharedKey - } - var has4, has6 bool - for _, address := range localPrefixes { - if address.Addr().Is4() { - has4 = true - } else { - has6 = true + if peersLen := len(option.Peers); peersLen > 0 { + localPrefixes = make([]netip.Prefix, 0, peersLen*2) + for i, peer := range option.Peers { + var peerPublicKey, preSharedKey string + { + bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey) + if err != nil { + return nil, E.Cause(err, "decode public key for peer ", i) + } + peerPublicKey = hex.EncodeToString(bytes) + } + if peer.PreSharedKey != "" { + bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey) + if err != nil { + return nil, E.Cause(err, "decode pre shared key for peer ", i) + } + preSharedKey = hex.EncodeToString(bytes) + } + destination := peer.Addr() + ipcConf += "\npublic_key=" + peerPublicKey + ipcConf += "\nendpoint=" + destination.String() + if preSharedKey != "" { + ipcConf += "\npreshared_key=" + preSharedKey + } + if len(peer.AllowedIPs) == 0 { + return nil, E.New("missing allowed_ips for peer ", i) + } + for _, allowedIP := range peer.AllowedIPs { + ipcConf += "\nallowed_ip=" + allowedIP + } + if len(peer.Reserved) > 0 { + if len(peer.Reserved) != 3 { + return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved)) + } + copy(reserved[:], option.Reserved) + outbound.bind.SetReservedForEndpoint(destination, reserved) + } + prefixes, err := peer.Prefixes() + if err != nil { + return nil, err + } + localPrefixes = append(localPrefixes, prefixes...) + } + } else { + var peerPublicKey, preSharedKey string + { + bytes, err := base64.StdEncoding.DecodeString(option.PublicKey) + if err != nil { + return nil, E.Cause(err, "decode peer public key") + } + peerPublicKey = hex.EncodeToString(bytes) + } + if option.PreSharedKey != "" { + bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey) + if err != nil { + return nil, E.Cause(err, "decode pre shared key") + } + preSharedKey = hex.EncodeToString(bytes) + } + ipcConf += "\npublic_key=" + peerPublicKey + ipcConf += "\nendpoint=" + connectAddr.String() + if preSharedKey != "" { + ipcConf += "\npreshared_key=" + preSharedKey + } + var err error + localPrefixes, err = option.Prefixes() + if err != nil { + return nil, err + } + var has4, has6 bool + for _, address := range localPrefixes { + if address.Addr().Is4() { + has4 = true + } else { + has6 = true + } + } + if has4 { + ipcConf += "\nallowed_ip=0.0.0.0/0" + } + if has6 { + ipcConf += "\nallowed_ip=::/0" } } - if has4 { - ipcConf += "\nallowed_ip=0.0.0.0/0" - } - if has6 { - ipcConf += "\nallowed_ip=::/0" - } + if option.PersistentKeepalive != 0 { ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) } @@ -179,6 +256,9 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { if mtu == 0 { mtu = 1408 } + if len(localPrefixes) == 0 { + return nil, E.New("missing local address") + } var err error outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) if err != nil { diff --git a/docs/config.yaml b/docs/config.yaml index c2acc1d4..32665aaf 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -620,12 +620,23 @@ proxies: # socks5 port: 2480 ip: 172.16.0.2 ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5 - private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU= public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= + # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= + private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU= udp: true reserved: "U4An" # 数组格式也是合法的 # reserved: [209,98,59] + # 如果peers不为空,该段落中的allowed_ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # peers: + # - server: 162.159.192.1 + # port: 2480 + # ip: 172.16.0.2 + # ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5 + # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= + # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= + # allowed_ips: ['0.0.0.0/0'] + # reserved: [209,98,59] # tuic - name: tuic