Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruakij committed Apr 2, 2023
2 parents c234ca5 + df17081 commit b4bdb21
Show file tree
Hide file tree
Showing 6 changed files with 991 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,91 @@ Converts and adds IPv4- to IPv6-Adresses and AllowedIP-Entries for wireguard-int

<br>

<!-- TOC -->
- [1. Overview](#1-overview)
- [2. Install](#2-install)
- [2.1. From Binary](#21-from-binary)
- [2.2. From sources](#22-from-sources)
- [3. Setup](#3-setup)
- [3.1. Environment](#31-environment)
- [3.2. Examples](#32-examples)
- [4. License](#4-license)
<!-- /TOC -->

<br>

# 1. Overview

The program will convert IPv4-only wireguard-interfaces to IPv6. It converts and adds the Address of the Interface and AllowedIPs-Entries with optional filters.

IPv6-Adresses are generated based on the IPv4-Adress.

Beware: This program needs `NET_ADMIN` privileges for setting Adresses and to access the wireguard-daemon.

<br>

# 2. Install

## 2.1. From Binary

1. Download the appripriate binary for your system from the Release-page (or build from sources)
2. Save at an appropriate location e.g. `/usr/bin/local/wg-ipv6-converter`
3. Make executeable: `chmod +x /usr/bin/local/wg-ipv6-converter`

<br>

## 2.2. From sources

Clone the repository and compile using `go build ./cmd/app`

<br>

# 3. Setup
## 3.1. Environment

Variable|Description|Default
-|-|-
`INTERFACE`* | Wireguard-Interface Name |
`IPV6_FORMAT` | Format to use for converting v4 to v6 <br> The CIDR-Mask gets translated using 128 - 24 - Mask <br> e.g. `10.0.100.5/16` -> `fc12::0a00:6405/96` | `fc12::%02x%02x:%02x%02x/%d`
`RECHECK_INTERVAL` | Interval in seconds to recheck AllowedIPs entries in case something changed | 300

*\* Required*

<br>

## 3.2. Examples

### 3.2.1. Netbird

Netbird is at the moment only IPv4-compatible, with this program running where necessary, some basic IPv6-setup can be archieved.

```bash
INTERFACE="wt0" ./wg-ipv6-converter
```
Or using a systemd-service based on the example:
```bash
[Unit]
Description=WireGuard IPv6 converter for netbird
BindsTo=netbird.service
After=netbird.service

[Service]
Type=simple
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/wg-ipv6-converter
Restart=always
RestartSec=30

Environment="INTERFACE=wt0"
Environment="RECHECK_INTERVAL=60"

[Install]
WantedBy=multi-user.target
```

<br>

# 4. License

This project is licenced under GPLv3.
See [LICENSE](LICENSE) for more details.
17 changes: 17 additions & 0 deletions cmd/app/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"log"
"os"
)

type Log struct {
Info log.Logger
Warn log.Logger
Error log.Logger
}
var logger Log = Log{
Info: *log.New(os.Stdout, "[INFO]\t", log.Ltime|log.Lshortfile),
Warn: *log.New(os.Stderr, "[WARN]\t", log.Ltime|log.Lshortfile),
Error: *log.New(os.Stderr, "[ERROR]\t", log.Ltime|log.Lshortfile),
}
159 changes: 159 additions & 0 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package main

import (
"fmt"
"net"
"os"
"strconv"
"time"

envChecks "git.ruekov.eu/ruakij/routingtabletowg/lib/environmentchecks"
"git.ruekov.eu/ruakij/routingtabletowg/lib/wgchecks/netchecks"

"github.com/vishvananda/netlink"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)

var envRequired = []string{
"INTERFACE",
}
var envDefaults = map[string]string{
"IPV6_FORMAT": "fc12::%02x%02x:%02x%02x/%d",
"FILTER_PREFIX": "100.100",
"RECHECK_INTERVAL": "300",
}

func main() {
// Environment-vars
err := envChecks.HandleRequired(envRequired)
if(err != nil){
logger.Error.Fatal(err)
}
envChecks.HandleDefaults(envDefaults)

// Get the network interface object
iface := os.Getenv("INTERFACE")
netInterface, err := netlink.LinkByName(iface)
if err != nil {
logger.Error.Fatal(err)
}

ipv6Format := os.Getenv("IPV6_FORMAT")
ipv6TestStr := *convertIPv4ToIPv6(&ipv6Format, &net.IPNet{IP: net.IPv4(1,1,1,1), Mask: net.CIDRMask(24, net.IPv4len)})
_, err = netlink.ParseIPNet(ipv6TestStr)
if err != nil {
logger.Error.Fatalf("IPV6_FORMAT is invalid: %s", err)
}

filterPrefix := os.Getenv("FILTER_PREFIX")

checkIntervalStr := os.Getenv("RECHECK_INTERVAL")
checkIntervalSec, err := strconv.Atoi(checkIntervalStr)
if err != nil {
logger.Error.Fatalf("Couldn't read RECHECK_INTERVAL '%s': %s", checkIntervalStr, err)
}
checkInterval := time.Second * time.Duration(checkIntervalSec)

// Get the IPv4 address of the interface
addrs, err := netlink.AddrList(netInterface, netlink.FAMILY_V4)
if err != nil {
logger.Error.Fatal(err)
}
if(len(addrs) == 0){
logger.Error.Fatal("Interface doesnt have IPv4-Adresses")
}

// Add the IPv6 address to the interface
ipv6Str := *convertIPv4ToIPv6(&ipv6Format, addrs[0].IPNet)
ipv6, err := netlink.ParseAddr(ipv6Str)
if err != nil {
logger.Error.Fatal(err)
}
logger.Info.Printf("Adding converted %s -> %s to interface", addrs[0].IPNet.String(), ipv6Str)
err = netlink.AddrAdd(netInterface, ipv6)
if err != nil {
switch {
case os.IsExist(err):
logger.Warn.Println("Address is already set on interface")
default:
logger.Error.Fatalf("Failed to set address on interface: %v", err)
}
}

// Create a WireGuard client
client, err := wgctrl.New()
if err != nil {
logger.Error.Fatal(err)
}
defer client.Close()

// Loop indefinitely
for {
// Get the WireGuard peers on the interface
wgDevice, err := client.Device(iface)
if err != nil {
logger.Error.Fatalf("getting WireGuard device from interface '%s' failed: %s", iface, err)
}

var wgConfig wgtypes.Config
wgConfig.Peers = make([]wgtypes.PeerConfig, 0, len(wgDevice.Peers))

for _, peer := range wgDevice.Peers {
// Create slice for 1 expected addition
var addAllowedIPs = make([]net.IPNet, 0, 1)

// Loop through the allowed-ips and add the ones starting with 100.100
for _, allowedIP := range peer.AllowedIPs {
if allowedIP.String()[:len(filterPrefix)] == filterPrefix {
// Convert the IPv4 allowed-ip to an IPv6 address
ipv6Str := *convertIPv4ToIPv6(&ipv6Format, &allowedIP)
logger.Info.Printf("AllowedIP %s -> %s to peer %s", allowedIP.String(), ipv6Str, peer.PublicKey)
ipv6, err := netlink.ParseIPNet(ipv6Str)
if err != nil {
logger.Warn.Printf("Couldnt parse IPv6 address %s of peer %s: %s", ipv6Str, peer.PublicKey, err)
continue
}

// Check if already set
if i, _ := netchecks.IPNetIndexByIPNet(&peer.AllowedIPs, ipv6); i != -1 {
continue
}

// Add the IPv6 allowed-ip to the peer
addAllowedIPs = append(addAllowedIPs, *ipv6)
}
}

if(len(addAllowedIPs) > 0){
// Create peer-config
peerConfig := wgtypes.PeerConfig{
PublicKey: peer.PublicKey,
AllowedIPs: append(peer.AllowedIPs, addAllowedIPs...),
}

// Add entry
wgConfig.Peers = append(wgConfig.Peers, peerConfig)
}
}

if(len(wgConfig.Peers) == 0){
logger.Info.Println("No changes, skipping")
} else {
err = client.ConfigureDevice(iface, wgConfig)
if(err != nil){
logger.Error.Fatalf("Error configuring wg-device '%s': %s", iface, err)
}
}

// Sleep for x seconds before running the loop again
time.Sleep(checkInterval)
}
}

func convertIPv4ToIPv6(ipv6Format *string, ipv4 *net.IPNet) (*string) {
CIDR, _ := ipv4.Mask.Size()
// Run format
ipv6Str := fmt.Sprintf(*ipv6Format, (*ipv4).IP[0], (*ipv4).IP[1], (*ipv4).IP[2], (*ipv4).IP[3], net.IPv6len*8-(net.IPv4len*8-CIDR))
return &ipv6Str
}
22 changes: 22 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module git.ruekov.eu/wg-ipv6-converter

go 1.20

require golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde

require github.com/vishvananda/netns v0.0.4 // indirect

require (
git.ruekov.eu/ruakij/routingtabletowg v0.0.0-20230331142222-dcc0b6607b52
github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/genetlink v1.3.1 // indirect
github.com/mdlayher/netlink v1.7.1 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/vishvananda/netlink v1.1.0
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
git.ruekov.eu/ruakij/routingtabletowg v0.0.0-20230331142222-dcc0b6607b52 h1:W7nF5Qxua2PDMoogjVXceWSEeboOfd8/HvpIY7HjMbM=
git.ruekov.eu/ruakij/routingtabletowg v0.0.0-20230331142222-dcc0b6607b52/go.mod h1:wYEQNasQeg+oOxXqFBxavBjZfX5hY5qoGrV4K6sRaiI=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/genetlink v1.3.1 h1:roBiPnual+eqtRkKX2Jb8UQN5ZPWnhDCGj/wR6Jlz2w=
github.com/mdlayher/genetlink v1.3.1/go.mod h1:uaIPxkWmGk753VVIzDtROxQ8+T+dkHqOI0vB1NA9S/Q=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=

0 comments on commit b4bdb21

Please sign in to comment.