Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,61 @@ wrapguard --config=~/wg0.conf -- curl https://icanhazip.com
# Route incoming connections through WireGuard
wrapguard --config=~/wg0.conf -- node -e 'http.createServer().listen(8080)'

# Use an exit node (route all traffic through a specific peer)
wrapguard --config=~/wg0.conf --exit-node=10.150.0.3 -- curl https://icanhazip.com

# Route specific subnets through different peers
wrapguard --config=~/wg0.conf \
--route=192.168.0.0/16:10.150.0.3 \
--route=172.16.0.0/12:10.150.0.4 \
-- curl https://internal.corp.com

# With debug logging to console
wrapguard --config=~/wg0.conf --log-level=debug -- curl https://icanhazip.com

# With logging to file
wrapguard --config=~/wg0.conf --log-level=info --log-file=/tmp/wrapguard.log -- curl https://icanhazip.com
```

## Routing

WrapGuard supports policy-based routing to direct traffic through specific WireGuard peers.

### Exit Node

Use the `--exit-node` option to route all traffic through a specific peer (like a traditional VPN):

```bash
wrapguard --config=~/wg0.conf --exit-node=10.150.0.3 -- curl https://example.com
```

### Policy-Based Routing

Use the `--route` option to route specific subnets through different peers:

```bash
# Route corporate traffic through one peer, internet through another
wrapguard --config=~/wg0.conf \
--route=192.168.0.0/16:10.150.0.3 \
--route=0.0.0.0/0:10.150.0.4 \
-- ssh internal.corp.com
```

### Configuration File Routing

You can also define routes in your WireGuard configuration file:

```ini
[Peer]
PublicKey = ...
AllowedIPs = 10.150.0.0/24
# Route all traffic through this peer
Route = 0.0.0.0/0
# Or route specific subnets
Route = 192.168.0.0/16
Route = 172.16.0.0/12:tcp:443
```

## Logging

WrapGuard provides structured JSON logging with configurable levels and output destinations.
Expand Down
81 changes: 81 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type PeerConfig struct {
Endpoint string
AllowedIPs []string
PersistentKeepalive int
RoutingPolicies []RoutingPolicy // New field for policy-based routing
}

type WireGuardConfig struct {
Expand Down Expand Up @@ -167,6 +168,14 @@ func parsePeerField(peer *PeerConfig, key, value string) error {
return fmt.Errorf("invalid persistent keepalive: %w", err)
}
peer.PersistentKeepalive = keepalive
case "route":
// Parse routing policy with auto-incrementing priority
priority := len(peer.RoutingPolicies)
policy, err := ParseRoutingPolicy(value, priority)
if err != nil {
return fmt.Errorf("invalid routing policy: %w", err)
}
peer.RoutingPolicies = append(peer.RoutingPolicies, *policy)
}
return nil
}
Expand Down Expand Up @@ -283,3 +292,75 @@ func resolveEndpoint(endpoint string) (string, error) {

return net.JoinHostPort(resolvedIP.String(), port), nil
}

// ApplyCLIRoutes applies routing policies from CLI arguments to the configuration
func ApplyCLIRoutes(config *WireGuardConfig, exitNode string, routes []string) error {
// Handle exit node (shorthand for routing all traffic through a peer)
if exitNode != "" {
routes = append([]string{fmt.Sprintf("0.0.0.0/0:%s", exitNode)}, routes...)
}

// Process each route
for _, route := range routes {
parts := strings.Split(route, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid route format '%s', expected CIDR:peerIP", route)
}

cidr := strings.TrimSpace(parts[0])
peerIP := strings.TrimSpace(parts[1])

// Validate CIDR
if _, err := netip.ParsePrefix(cidr); err != nil {
return fmt.Errorf("invalid CIDR in route '%s': %w", route, err)
}

// Find the peer with the matching IP
peerFound := false
for i := range config.Peers {
peer := &config.Peers[i]

// Check if this peer can route to the specified IP
for _, allowedIP := range peer.AllowedIPs {
prefix, err := netip.ParsePrefix(allowedIP)
if err != nil {
continue
}

// Check if the peer IP is within this peer's allowed IPs
addr, err := netip.ParseAddr(peerIP)
if err != nil {
continue
}

if prefix.Contains(addr) {
// Add routing policy to this peer
priority := len(peer.RoutingPolicies)
policy := RoutingPolicy{
DestinationCIDR: cidr,
Protocol: "any",
PortRange: PortRange{Start: 1, End: 65535},
Priority: priority,
}
peer.RoutingPolicies = append(peer.RoutingPolicies, policy)
peerFound = true

if logger != nil {
logger.Infof("Added route %s via peer %s", cidr, peerIP)
}
break
}
}

if peerFound {
break
}
}

if !peerFound {
return fmt.Errorf("no peer found that can route to %s", peerIP)
}
}

return nil
}
23 changes: 23 additions & 0 deletions example-usage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

# Example usage of WrapGuard with routing options

echo "Example 1: Using exit node to route all traffic through a specific peer"
echo "wrapguard --config=wg0.conf --exit-node=10.150.0.3 -- curl https://icanhazip.com"
echo ""

echo "Example 2: Routing specific subnets through different peers"
echo "wrapguard --config=wg0.conf \\"
echo " --route=192.168.0.0/16:10.150.0.3 \\"
echo " --route=172.16.0.0/12:10.150.0.4 \\"
echo " -- ssh internal.corp.com"
echo ""

echo "Example 3: Combining exit node with specific routes"
echo "wrapguard --config=wg0.conf \\"
echo " --exit-node=10.150.0.5 \\"
echo " --route=10.0.0.0/8:10.150.0.3 \\"
echo " -- curl https://example.com"
echo ""

echo "Note: The peer IPs (like 10.150.0.3) must be within the AllowedIPs range of the corresponding peer in your config."
14 changes: 7 additions & 7 deletions example-wg0.conf
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[Interface]
PrivateKey = YOUR_PRIVATE_KEY_HERE
Address = 10.0.0.2/24
DNS = 8.8.8.8, 8.8.4.4
PrivateKey = eDsYEfddDm8jE8sUBnfG9GZm0mqTYGJhxbsOjzKvBUo=
Address = 10.150.0.2/24

[Peer]
PublicKey = YOUR_PEER_PUBLIC_KEY_HERE
Endpoint = your-server.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
PublicKey = sJwKzKorIGo/ZHAPDnmM5dk0ZmQlkf4aNtRVK6eYInU=
PresharedKey = ve5d5GUSnojL/5mrn7srhnRjhyrVWsTBSPwfpEIT4DA=
Endpoint = 127.0.0.1:51820
AllowedIPs = 10.150.0.0/24
PersistentKeepalive = 25
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func printUsage() {

help += "\033[33mOPTIONS:\033[0m\n"
help += " --config=<path> Path to WireGuard configuration file\n"
help += " --exit-node=<ip> Route all traffic through specified peer IP\n"
help += " --route=<policy> Add routing policy (CIDR:peerIP)\n"
help += " --log-level=<level> Set log level (error, warn, info, debug)\n"
help += " --log-file=<path> Set file to write logs to (default: terminal)\n"
help += " --help Show this help message\n"
Expand Down Expand Up @@ -77,11 +79,18 @@ func main() {
var showVersion bool
var logLevelStr string
var logFile string
var exitNode string
var routes []string
flag.StringVar(&configPath, "config", "", "Path to WireGuard configuration file")
flag.BoolVar(&showHelp, "help", false, "Show help message")
flag.BoolVar(&showVersion, "version", false, "Show version information")
flag.StringVar(&logLevelStr, "log-level", "info", "Set log level (error, warn, info, debug)")
flag.StringVar(&logFile, "log-file", "", "Set file to write logs to (default: terminal)")
flag.StringVar(&exitNode, "exit-node", "", "Route all traffic through specified peer IP (e.g., 10.0.0.3)")
flag.Func("route", "Add routing policy (format: CIDR:peerIP, e.g., 192.168.1.0/24:10.0.0.3)", func(value string) error {
routes = append(routes, value)
return nil
})
flag.Usage = printUsage
flag.Parse()

Expand Down Expand Up @@ -137,6 +146,14 @@ func main() {
os.Exit(1)
}

// Apply CLI routing options
if exitNode != "" || len(routes) > 0 {
if err := ApplyCLIRoutes(config, exitNode, routes); err != nil {
logger.Errorf("Failed to apply routing options: %v", err)
os.Exit(1)
}
}

// Create IPC server for communication with LD_PRELOAD library
ipcServer, err := NewIPCServer()
if err != nil {
Expand Down
Loading
Loading