|
| 1 | +# WireGuard |
| 2 | + |
| 3 | +WireGuard is an outbound protocol that sends proxied traffic through a userspace WireGuard tunnel. (v5.48.0+) |
| 4 | + |
| 5 | +The current V2Ray implementation is outbound only. It creates a virtual network stack inside V2Ray, then sends TCP and UDP traffic through the configured WireGuard peer. |
| 6 | + |
| 7 | +:::warning |
| 8 | +In the current implementation: |
| 9 | + |
| 10 | +* `settings.listenOnSystemNetwork` must be `true`. |
| 11 | +* `settings.wgDevice` is required. |
| 12 | +* `settings.stack` is required. |
| 13 | +* `settings.wgDevice.peers[].endpoint` should be a literal `IP:port` or `[IPv6]:port`. |
| 14 | +::: |
| 15 | + |
| 16 | +## WireGuard Outbound |
| 17 | + |
| 18 | +* Name: `wireguard` |
| 19 | +* Type: Outbound Protocol |
| 20 | +* ID: `outbound.wireguard` |
| 21 | + |
| 22 | +### Structure |
| 23 | + |
| 24 | +```json |
| 25 | +{ |
| 26 | + "wgDevice": { |
| 27 | + "privateKey": "", |
| 28 | + "mtu": 0, |
| 29 | + "peers": [ |
| 30 | + { |
| 31 | + "publicKey": "", |
| 32 | + "presharedKey": "", |
| 33 | + "allowedIps": [], |
| 34 | + "endpoint": "", |
| 35 | + "persistentKeepaliveInterval": 0 |
| 36 | + } |
| 37 | + ] |
| 38 | + }, |
| 39 | + "stack": { |
| 40 | + "mtu": 0, |
| 41 | + "ips": [], |
| 42 | + "routes": [], |
| 43 | + "preferIpv6ForUdp": false, |
| 44 | + "dualStackUdp": false |
| 45 | + }, |
| 46 | + "listenOnSystemNetwork": true, |
| 47 | + "domainStrategy": "USE_IP" |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +### Fields |
| 52 | + |
| 53 | +> `wgDevice`: [DeviceObject](#deviceobject) |
| 54 | +
|
| 55 | +WireGuard device settings. This field is required. |
| 56 | + |
| 57 | +> `stack`: [StackObject](#stackobject) |
| 58 | +
|
| 59 | +Virtual network stack settings. This field is required for WireGuard outbound. |
| 60 | + |
| 61 | +> `listenOnSystemNetwork`: boolean |
| 62 | +
|
| 63 | +Create the underlying UDP socket on the system network. In the current implementation this must be `true`. |
| 64 | + |
| 65 | +> `domainStrategy`: `"USE_IP"` | `"USE_IP4"` | `"USE_IP6"` |
| 66 | +
|
| 67 | +Controls how destination domains are resolved in this outbound's TCP dial path. |
| 68 | + |
| 69 | +This field is separate from the top-level outbound `domainStrategy` in [OutboundObject](../outbound.md). The WireGuard settings use protobuf enum names such as `"USE_IP"`, not `"UseIP"`. |
| 70 | + |
| 71 | +Use `"USE_IP"`, `"USE_IP4"`, or `"USE_IP6"` when the target is a domain name, because the gVisor dialer used by this outbound needs an IP address before dialing. |
| 72 | + |
| 73 | +This field is not used by the current UDP path. If a UDP destination is still a domain name when it reaches this outbound, it is not supported. |
| 74 | + |
| 75 | +## TCP Happy Eyeballs |
| 76 | + |
| 77 | +For TCP connections, when this outbound resolves a domain name to multiple IP addresses, it uses a Happy Eyeballs style racing dialer. |
| 78 | + |
| 79 | +The current implementation works as follows: |
| 80 | + |
| 81 | +* It is only used on the TCP path. |
| 82 | +* It is only used when the WireGuard outbound itself resolves the destination domain through `settings.domainStrategy`. |
| 83 | +* A separate dial attempt is created for each resolved IP address. |
| 84 | +* The first successful TCP connection wins. |
| 85 | +* If both IPv4 and IPv6 addresses are present, the current implementation prefers IPv6. IPv6 attempts start immediately, and IPv4 attempts start 300 ms later. |
| 86 | +* If all resolved addresses are from the same family, there is no head start delay and all attempts start immediately. |
| 87 | +* Extra successful connections that lose the race are closed. |
| 88 | + |
| 89 | +The outer outbound `domainStrategy` in [OutboundObject](../outbound.md) runs before the WireGuard proxy code. If that outer layer already resolves the destination to a single IP address, the WireGuard outbound receives an IP directly and this Happy Eyeballs logic is skipped. |
| 90 | + |
| 91 | +## DeviceObject |
| 92 | + |
| 93 | +### Structure |
| 94 | + |
| 95 | +```json |
| 96 | +{ |
| 97 | + "privateKey": "", |
| 98 | + "mtu": 0, |
| 99 | + "peers": [] |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### Fields |
| 104 | + |
| 105 | +> `privateKey`: string |
| 106 | +
|
| 107 | +The local WireGuard private key, encoded as a base64 string. This is the same format used by standard WireGuard tools such as `wg genkey`. |
| 108 | + |
| 109 | +For a usable client configuration, this field should be set. |
| 110 | + |
| 111 | +> `mtu`: number |
| 112 | +
|
| 113 | +MTU for the WireGuard device. In most cases it should match `stack.mtu`. |
| 114 | + |
| 115 | +> `peers`: \[ [PeerObject](#peerobject) \] |
| 116 | +
|
| 117 | +Remote WireGuard peers. |
| 118 | + |
| 119 | +For a usable client configuration, define at least one peer. |
| 120 | + |
| 121 | +## PeerObject |
| 122 | + |
| 123 | +### Structure |
| 124 | + |
| 125 | +```json |
| 126 | +{ |
| 127 | + "publicKey": "", |
| 128 | + "presharedKey": "", |
| 129 | + "allowedIps": [], |
| 130 | + "endpoint": "", |
| 131 | + "persistentKeepaliveInterval": 0 |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +### Fields |
| 136 | + |
| 137 | +> `publicKey`: string |
| 138 | +
|
| 139 | +The peer public key, encoded as a base64 string. |
| 140 | + |
| 141 | +If this field is empty, the peer entry is ignored by the current implementation. |
| 142 | + |
| 143 | +> `presharedKey`: string |
| 144 | +
|
| 145 | +Optional preshared key, encoded as a base64 string. |
| 146 | + |
| 147 | +> `allowedIps`: \[ string \] |
| 148 | +
|
| 149 | +The CIDR ranges routed to this peer in the WireGuard device configuration. |
| 150 | + |
| 151 | +This field is especially important when there are multiple peers. The WireGuard device uses the destination address of each packet to decide which peer should receive it, based on these CIDR ranges. |
| 152 | + |
| 153 | +For a single-peer full-tunnel client, this is typically `["0.0.0.0/0", "::/0"]`. For split routing or multi-peer setups, assign only the subnets that should go to that specific peer. |
| 154 | + |
| 155 | +> `endpoint`: string |
| 156 | +
|
| 157 | +The peer endpoint in `IP:port` form, or `[IPv6]:port` for IPv6. |
| 158 | + |
| 159 | +For a normal client configuration, this field should be set. |
| 160 | + |
| 161 | +> `persistentKeepaliveInterval`: number |
| 162 | +
|
| 163 | +Persistent keepalive interval in seconds. A value such as `25` is commonly used when the client is behind NAT. |
| 164 | + |
| 165 | +## StackObject |
| 166 | + |
| 167 | +The `stack` object configures the virtual TCP/IP stack that V2Ray uses inside the WireGuard tunnel. |
| 168 | + |
| 169 | +### Structure |
| 170 | + |
| 171 | +```json |
| 172 | +{ |
| 173 | + "mtu": 0, |
| 174 | + "ips": [ |
| 175 | + { |
| 176 | + "ipAddr": "", |
| 177 | + "prefix": 0 |
| 178 | + } |
| 179 | + ], |
| 180 | + "routes": [ |
| 181 | + { |
| 182 | + "ipAddr": "", |
| 183 | + "prefix": 0 |
| 184 | + } |
| 185 | + ], |
| 186 | + "preferIpv6ForUdp": false, |
| 187 | + "dualStackUdp": false, |
| 188 | + "socketSettings": {}, |
| 189 | + "enablePromiscuousMode": false, |
| 190 | + "enableSpoofing": false, |
| 191 | + "tcpListener": [] |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +### Fields |
| 196 | + |
| 197 | +> `mtu`: number |
| 198 | +
|
| 199 | +MTU for the virtual stack. In most cases it should match `wgDevice.mtu`. |
| 200 | + |
| 201 | +> `ips`: \[ [CIDRObject](#cidrobject) \] |
| 202 | +
|
| 203 | +The local tunnel IP addresses assigned to this client. |
| 204 | + |
| 205 | +These are the addresses that exist on the virtual interface inside V2Ray. In a normal client configuration, use the addresses assigned by the WireGuard server or peer. |
| 206 | + |
| 207 | +> `routes`: \[ [CIDRObject](#cidrobject) \] |
| 208 | +
|
| 209 | +Routes installed into the virtual stack. |
| 210 | + |
| 211 | +For a full-tunnel client, use default routes such as `0.0.0.0/0` and `::/0`. For a split-tunnel client, list only the subnets that should go through WireGuard. |
| 212 | + |
| 213 | +> `preferIpv6ForUdp`: boolean |
| 214 | +
|
| 215 | +Prefer IPv6 when both IPv4 and IPv6 are available for UDP. |
| 216 | + |
| 217 | +> `dualStackUdp`: boolean |
| 218 | +
|
| 219 | +Enable dual-stack UDP handling inside the virtual stack. |
| 220 | + |
| 221 | +> `socketSettings`: object |
| 222 | +
|
| 223 | +Optional socket settings for the virtual stack. |
| 224 | + |
| 225 | +In the current implementation, only `rxBufSize` and `txBufSize` are applied. |
| 226 | + |
| 227 | +> `enablePromiscuousMode`: boolean |
| 228 | +
|
| 229 | +Enable promiscuous mode on the virtual NIC inside the gVisor stack. |
| 230 | + |
| 231 | +This is passed directly to the stack NIC with `SetPromiscuousMode`. It is an advanced option and is usually not needed for a normal client setup. |
| 232 | + |
| 233 | +> `enableSpoofing`: boolean |
| 234 | +
|
| 235 | +Enable IP spoofing on the virtual NIC inside the gVisor stack. |
| 236 | + |
| 237 | +This is passed directly to the stack NIC with `SetSpoofing`. It is an advanced option and is usually not needed for a normal client setup. |
| 238 | + |
| 239 | +> `tcpListener`: \[ [TCPListenerObject](#tcplistenerobject) \] |
| 240 | +
|
| 241 | +Advanced option for exposing TCP listeners on the virtual stack. |
| 242 | + |
| 243 | +Each listener opens a TCP port on the gVisor stack. Accepted connections are then forwarded to the outbound specified by that listener's `tag`, using the listener's local stack address and port as the forwarded destination. |
| 244 | + |
| 245 | +This is mainly useful for specialized internal routing or virtual network topologies. It is usually not needed for a normal client setup. |
| 246 | + |
| 247 | +## TCPListenerObject |
| 248 | + |
| 249 | +### Structure |
| 250 | + |
| 251 | +```json |
| 252 | +{ |
| 253 | + "address": { |
| 254 | + "ipAddr": "" |
| 255 | + }, |
| 256 | + "port": 0, |
| 257 | + "tag": "" |
| 258 | +} |
| 259 | +``` |
| 260 | + |
| 261 | +### Fields |
| 262 | + |
| 263 | +> `address`: [ListenerAddressObject](#listeneraddressobject) |
| 264 | +
|
| 265 | +The local stack address to listen on. |
| 266 | + |
| 267 | +The current implementation uses only the `ipAddr` value of this object when creating the listener. |
| 268 | + |
| 269 | +> `port`: number |
| 270 | +
|
| 271 | +The TCP port to listen on inside the virtual stack. |
| 272 | + |
| 273 | +> `tag`: string |
| 274 | +
|
| 275 | +The outbound tag used to forward accepted connections. |
| 276 | + |
| 277 | +## ListenerAddressObject |
| 278 | + |
| 279 | +### Structure |
| 280 | + |
| 281 | +```json |
| 282 | +{ |
| 283 | + "ipAddr": "" |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +### Fields |
| 288 | + |
| 289 | +> `ipAddr`: string |
| 290 | +
|
| 291 | +The local IP address to listen on inside the virtual stack. |
| 292 | + |
| 293 | +This object is used by `tcpListener.address`. Unlike [CIDRObject](#cidrobject), it does not use a prefix length. |
| 294 | + |
| 295 | +## CIDRObject |
| 296 | + |
| 297 | +### Structure |
| 298 | + |
| 299 | +```json |
| 300 | +{ |
| 301 | + "ipAddr": "", |
| 302 | + "prefix": 0 |
| 303 | +} |
| 304 | +``` |
| 305 | + |
| 306 | +### Fields |
| 307 | + |
| 308 | +> `ipAddr`: string |
| 309 | +
|
| 310 | +IP address of the CIDR block. |
| 311 | + |
| 312 | +> `prefix`: number |
| 313 | +
|
| 314 | +Prefix length of the CIDR block. |
| 315 | + |
| 316 | +## Example |
| 317 | + |
| 318 | +The following example sends all traffic through a single WireGuard peer. |
| 319 | + |
| 320 | +```json |
| 321 | +{ |
| 322 | + "outbounds": [ |
| 323 | + { |
| 324 | + "protocol": "wireguard", |
| 325 | + "tag": "wg-out", |
| 326 | + "settings": { |
| 327 | + "wgDevice": { |
| 328 | + "privateKey": "wYZ9AK9e418AUhqoXiGSSn+G5XdZzKyEGwLRpXUjHtM=", // replace with your private key |
| 329 | + "mtu": 1420, |
| 330 | + "peers": [ |
| 331 | + { |
| 332 | + "publicKey": "wRaPC0mJWDNkD49zr+OH8zuHoavmQV3fDt57xMzy9uk=", // replace with the peer public key |
| 333 | + "presharedKey": "IoGHxUuE93FlkYp8S08vgy9zLxVocHeHcgo4L38TvSk=", // optional |
| 334 | + "endpoint": "198.51.100.10:51820", |
| 335 | + "allowedIps": ["0.0.0.0/0", "::/0"], |
| 336 | + "persistentKeepaliveInterval": 25 |
| 337 | + } |
| 338 | + ] |
| 339 | + }, |
| 340 | + "stack": { |
| 341 | + "mtu": 1420, |
| 342 | + "ips": [ |
| 343 | + {"ipAddr": "10.64.0.2", "prefix": 32}, |
| 344 | + {"ipAddr": "fd00::2", "prefix": 128} |
| 345 | + ], |
| 346 | + "routes": [ |
| 347 | + {"ipAddr": "0.0.0.0", "prefix": 0}, |
| 348 | + {"ipAddr": "::", "prefix": 0} |
| 349 | + ], |
| 350 | + "dualStackUdp": true |
| 351 | + }, |
| 352 | + "listenOnSystemNetwork": true, |
| 353 | + "domainStrategy": "USE_IP" |
| 354 | + } |
| 355 | + } |
| 356 | + ] |
| 357 | +} |
| 358 | +``` |
| 359 | + |
| 360 | +## Notes |
| 361 | + |
| 362 | +* `wgDevice.peers[].allowedIps` describes what the peer owns inside WireGuard. |
| 363 | +* `stack.routes` describes what traffic V2Ray should send into the WireGuard tunnel. |
| 364 | +* In a simple client configuration these two sets usually match, but they are not the same field and should not be confused. |
| 365 | +* If you only use IPv4, remove the IPv6 addresses and routes from both `allowedIps` and `stack`. |
| 366 | +* `settings.domainStrategy` is used by this outbound's TCP path. For UDP destinations that may still be domain names, use the outer outbound `domainStrategy` with values such as `"UseIP"`, `"UseIP4"`, or `"UseIP6"`. |
0 commit comments