1
0
forked from extern/smegmesh

Implemented the forwarding of packets between meshes

This commit is contained in:
Tim Beatham 2023-10-25 18:34:38 +01:00
parent 180f5e226c
commit c205be6748
14 changed files with 241 additions and 33 deletions

View File

@ -5,6 +5,7 @@ import (
"log" "log"
ipcRpc "net/rpc" ipcRpc "net/rpc"
"os" "os"
"strings"
"time" "time"
"github.com/akamensky/argparse" "github.com/akamensky/argparse"
@ -80,6 +81,10 @@ func getMesh(client *ipcRpc.Client, meshId string) {
fmt.Println("WireGuard Endpoint: " + node.WgEndpoint) fmt.Println("WireGuard Endpoint: " + node.WgEndpoint)
fmt.Println("Wg IP: " + node.WgHost) fmt.Println("Wg IP: " + node.WgHost)
fmt.Println(fmt.Sprintf("Timestamp: %s", time.Unix(node.Timestamp, 0).String())) fmt.Println(fmt.Sprintf("Timestamp: %s", time.Unix(node.Timestamp, 0).String()))
advertiseRoutes := strings.Join(node.Routes, ",")
fmt.Printf("Routes: %s\n", advertiseRoutes)
fmt.Println("---") fmt.Println("---")
} }
} }

View File

@ -1,7 +1,5 @@
certificatePath: "../../cert/cert.pem" certificatePath: "../../cert/cert.pem"
privateKeyPath: "../../cert/key.pem" privateKeyPath: "../../cert/key.pem"
skipCertVerification: true skipCertVerification: true
ifName: "wgmesh"
wgPort: 51820
gRPCPort: "8080" gRPCPort: "8080"
secret: "abc123" advertiseRoutes: true

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/automerge/automerge-go" "github.com/automerge/automerge-go"
"github.com/tim-beatham/wgmesh/pkg/conf"
logging "github.com/tim-beatham/wgmesh/pkg/log" logging "github.com/tim-beatham/wgmesh/pkg/log"
"github.com/tim-beatham/wgmesh/pkg/wg" "github.com/tim-beatham/wgmesh/pkg/wg"
"golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl"
@ -22,6 +23,7 @@ type CrdtNodeManager struct {
Client *wgctrl.Client Client *wgctrl.Client
doc *automerge.Doc doc *automerge.Doc
LastHash automerge.ChangeHash LastHash automerge.ChangeHash
conf *conf.WgMeshConfiguration
} }
const maxFails = 5 const maxFails = 5
@ -30,6 +32,8 @@ func (c *CrdtNodeManager) AddNode(crdt MeshNodeCrdt) {
crdt.FailedMap = automerge.NewMap() crdt.FailedMap = automerge.NewMap()
crdt.Timestamp = time.Now().Unix() crdt.Timestamp = time.Now().Unix()
c.doc.Path("nodes").Map().Set(crdt.HostEndpoint, crdt) c.doc.Path("nodes").Map().Set(crdt.HostEndpoint, crdt)
nodeVal, _ := c.doc.Path("nodes").Map().Get(crdt.HostEndpoint)
nodeVal.Map().Set("routes", automerge.NewMap())
} }
func (c *CrdtNodeManager) ApplyWg() error { func (c *CrdtNodeManager) ApplyWg() error {
@ -66,13 +70,14 @@ func (c *CrdtNodeManager) Save() []byte {
} }
// NewCrdtNodeManager: Create a new crdt node manager // NewCrdtNodeManager: Create a new crdt node manager
func NewCrdtNodeManager(meshId, hostId, devName string, port int, client *wgctrl.Client) (*CrdtNodeManager, error) { func NewCrdtNodeManager(meshId, hostId, devName string, port int, conf conf.WgMeshConfiguration, client *wgctrl.Client) (*CrdtNodeManager, error) {
var manager CrdtNodeManager var manager CrdtNodeManager
manager.MeshId = meshId manager.MeshId = meshId
manager.doc = automerge.New() manager.doc = automerge.New()
manager.IfName = devName manager.IfName = devName
manager.Client = client manager.Client = client
manager.NodeId = hostId manager.NodeId = hostId
manager.conf = &conf
err := wg.CreateWgInterface(client, devName, port) err := wg.CreateWgInterface(client, devName, port)
@ -105,6 +110,11 @@ func (m *CrdtNodeManager) convertMeshNode(node MeshNodeCrdt) (*wgtypes.PeerConfi
allowedIps[0] = *ipnet allowedIps[0] = *ipnet
for route, _ := range node.Routes {
_, ipnet, _ := net.ParseCIDR(route)
allowedIps = append(allowedIps, *ipnet)
}
peerConfig := wgtypes.PeerConfig{ peerConfig := wgtypes.PeerConfig{
PublicKey: peerPublic, PublicKey: peerPublic,
Remove: m.HasFailed(node.HostEndpoint), Remove: m.HasFailed(node.HostEndpoint),
@ -290,6 +300,31 @@ func (m *CrdtNodeManager) updateWgConf(devName string, nodes map[string]MeshNode
return nil return nil
} }
// AddRoutes: adds routes to the specific nodeId
func (m *CrdtNodeManager) AddRoutes(routes ...string) error {
nodeVal, err := m.doc.Path("nodes").Map().Get(m.NodeId)
if err != nil {
return err
}
routeMap, err := nodeVal.Map().Get("routes")
if err != nil {
return err
}
for _, route := range routes {
err = routeMap.Map().Set(route, struct{}{})
if err != nil {
return err
}
}
return nil
}
func (m *CrdtNodeManager) GetSyncer() *AutomergeSync { func (m *CrdtNodeManager) GetSyncer() *AutomergeSync {
return NewAutomergeSync(m) return NewAutomergeSync(m)
} }

View File

@ -2,15 +2,18 @@ package crdt
import "github.com/automerge/automerge-go" import "github.com/automerge/automerge-go"
// MeshNodeCrdt: Represents a CRDT for a mesh nodes
type MeshNodeCrdt struct { type MeshNodeCrdt struct {
HostEndpoint string `automerge:"hostEndpoint"` HostEndpoint string `automerge:"hostEndpoint"`
WgEndpoint string `automerge:"wgEndpoint"` WgEndpoint string `automerge:"wgEndpoint"`
PublicKey string `automerge:"publicKey"` PublicKey string `automerge:"publicKey"`
WgHost string `automerge:"wgHost"` WgHost string `automerge:"wgHost"`
Timestamp int64 `automerge:"timestamp"` Timestamp int64 `automerge:"timestamp"`
FailedMap *automerge.Map `automerge:"failedMap"` FailedMap *automerge.Map `automerge:"failedMap"`
Routes map[string]interface{} `automerge:"routes"`
} }
// MeshCrdt: Represents the mesh network as a whole
type MeshCrdt struct { type MeshCrdt struct {
Nodes map[string]MeshNodeCrdt `automerge:"nodes"` Nodes map[string]MeshNodeCrdt `automerge:"nodes"`
} }

View File

@ -12,10 +12,8 @@ type WgMeshConfiguration struct {
CertificatePath string `yaml:"certificatePath"` CertificatePath string `yaml:"certificatePath"`
PrivateKeyPath string `yaml:"privateKeyPath"` PrivateKeyPath string `yaml:"privateKeyPath"`
SkipCertVerification bool `yaml:"skipCertVerification"` SkipCertVerification bool `yaml:"skipCertVerification"`
IfName string `yaml:"ifName"`
WgPort int `yaml:"wgPort"`
GrpcPort string `yaml:"gRPCPort"` GrpcPort string `yaml:"gRPCPort"`
Secret string `yaml:"secret"` AdvertiseRoutes bool `yaml:"advertiseRoutes"`
} }
func ParseConfiguration(filePath string) (*WgMeshConfiguration, error) { func ParseConfiguration(filePath string) (*WgMeshConfiguration, error) {

View File

@ -18,6 +18,7 @@ type MeshNode struct {
WgHost string WgHost string
Failed bool Failed bool
Timestamp int64 Timestamp int64
Routes []string
} }
type Mesh struct { type Mesh struct {

View File

@ -13,14 +13,13 @@ import (
) )
const ( const (
ModifierLength = 16 ModifierLength = 16
ZeroLength = 9 ZeroLength = 9
hash2Length = 57 hash2Length = 57
hash1Length = 58 hash1Length = 58
Hash2Prefix = 14 Hash2Prefix = 14
Hash1Prefix = 8 Hash1Prefix = 8
InterfaceIdLen = 8 InterfaceIdLen = 8
SubnetPrefixLen = 8
) )
/* /*
@ -28,14 +27,14 @@ const (
*/ */
type CgaParameters struct { type CgaParameters struct {
Modifier [ModifierLength]byte Modifier [ModifierLength]byte
SubnetPrefix [SubnetPrefixLen]byte SubnetPrefix [2 * InterfaceIdLen]byte
CollisionCount uint8 CollisionCount uint8
PublicKey wgtypes.Key PublicKey wgtypes.Key
interfaceId [2 * InterfaceIdLen]byte interfaceId [2 * InterfaceIdLen]byte
flag byte flag byte
} }
func NewCga(key wgtypes.Key, subnetPrefix [SubnetPrefixLen]byte) (*CgaParameters, error) { func NewCga(key wgtypes.Key, subnetPrefix [2 * InterfaceIdLen]byte) (*CgaParameters, error) {
var params CgaParameters var params CgaParameters
_, err := rand.Read(params.Modifier[:]) _, err := rand.Read(params.Modifier[:])

View File

@ -2,6 +2,7 @@ package ip
import ( import (
"crypto/sha1" "crypto/sha1"
"fmt"
"net" "net"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@ -9,8 +10,8 @@ import (
type ULABuilder struct{} type ULABuilder struct{}
func getULAPrefix(meshId string) [8]byte { func getMeshPrefix(meshId string) [16]byte {
var ulaPrefix [8]byte var ulaPrefix [16]byte
ulaPrefix[0] = 0xfd ulaPrefix[0] = 0xfd
@ -24,8 +25,22 @@ func getULAPrefix(meshId string) [8]byte {
return ulaPrefix return ulaPrefix
} }
func (u *ULABuilder) GetIPNet(meshId string) (*net.IPNet, error) {
meshBytes := getMeshPrefix(meshId)
var meshIP net.IP = meshBytes[:]
ip := fmt.Sprintf("%s/%d", meshIP.String(), 64)
_, net, err := net.ParseCIDR(ip)
if err != nil {
return nil, err
}
return net, nil
}
func (u *ULABuilder) GetIP(key wgtypes.Key, meshId string) (net.IP, error) { func (u *ULABuilder) GetIP(key wgtypes.Key, meshId string) (net.IP, error) {
ulaPrefix := getULAPrefix(meshId) ulaPrefix := getMeshPrefix(meshId)
c, err := NewCga(key, ulaPrefix) c, err := NewCga(key, ulaPrefix)

View File

@ -25,3 +25,15 @@ func MapValuesWithExclude[K comparable, V any](m map[K]V, exclude map[K]struct{}
return values return values
} }
func MapKeys[K comparable, V any](m map[K]V) []K {
values := make([]K, len(m))
i := 0
for k, _ := range m {
values[i] = k
i++
}
return values
}

View File

@ -44,7 +44,7 @@ func (c *MeshDOTConverter) Generate(meshId string) (string, error) {
} }
for _, node2 := range nodes[i+1:] { for _, node2 := range nodes[i+1:] {
if node1 == node2 || mesh.HasFailed(node2.HostEndpoint) { if node1.WgEndpoint == node2.WgEndpoint || mesh.HasFailed(node2.HostEndpoint) {
continue continue
} }

View File

@ -14,6 +14,7 @@ import (
type MeshManger struct { type MeshManger struct {
Meshes map[string]*crdt.CrdtNodeManager Meshes map[string]*crdt.CrdtNodeManager
RouteManager RouteManager
Client *wgctrl.Client Client *wgctrl.Client
HostEndpoint string HostEndpoint string
conf *conf.WgMeshConfiguration conf *conf.WgMeshConfiguration
@ -32,19 +33,20 @@ func (m *MeshManger) CreateMesh(devName string, port int) (string, error) {
return "", err return "", err
} }
nodeManager, err := crdt.NewCrdtNodeManager(key.String(), m.HostEndpoint, devName, port, m.Client) nodeManager, err := crdt.NewCrdtNodeManager(key.String(), m.HostEndpoint, devName, port, *m.conf, m.Client)
if err != nil { if err != nil {
return "", err return "", err
} }
m.Meshes[key.String()] = nodeManager m.Meshes[key.String()] = nodeManager
return key.String(), nil
return key.String(), err
} }
// AddMesh: Add the mesh to the list of meshes // AddMesh: Add the mesh to the list of meshes
func (m *MeshManger) AddMesh(meshId string, devName string, port int, meshBytes []byte) error { func (m *MeshManger) AddMesh(meshId string, devName string, port int, meshBytes []byte) error {
mesh, err := crdt.NewCrdtNodeManager(meshId, m.HostEndpoint, devName, port, m.Client) mesh, err := crdt.NewCrdtNodeManager(meshId, m.HostEndpoint, devName, port, *m.conf, m.Client)
if err != nil { if err != nil {
return err return err
@ -63,6 +65,10 @@ func (m *MeshManger) AddMesh(meshId string, devName string, port int, meshBytes
// AddMeshNode: Add a mesh node // AddMeshNode: Add a mesh node
func (m *MeshManger) AddMeshNode(meshId string, node crdt.MeshNodeCrdt) { func (m *MeshManger) AddMeshNode(meshId string, node crdt.MeshNodeCrdt) {
m.Meshes[meshId].AddNode(node) m.Meshes[meshId].AddNode(node)
if m.conf.AdvertiseRoutes {
m.RouteManager.UpdateRoutes()
}
} }
func (m *MeshManger) HasChanges(meshId string) bool { func (m *MeshManger) HasChanges(meshId string) bool {
@ -100,7 +106,13 @@ func (s *MeshManger) EnableInterface(meshId string) error {
return err return err
} }
return wg.EnableInterface(mesh.IfName, node.WgHost) err = wg.EnableInterface(mesh.IfName, node.WgHost)
if s.conf.AdvertiseRoutes {
s.RouteManager.ApplyWg(mesh)
}
return nil
} }
// GetPublicKey: Gets the public key of the WireGuard mesh // GetPublicKey: Gets the public key of the WireGuard mesh
@ -135,11 +147,13 @@ func (s *MeshManger) UpdateTimeStamp() error {
func NewMeshManager(conf conf.WgMeshConfiguration, client *wgctrl.Client) *MeshManger { func NewMeshManager(conf conf.WgMeshConfiguration, client *wgctrl.Client) *MeshManger {
ip := lib.GetOutboundIP() ip := lib.GetOutboundIP()
m := &MeshManger{
return &MeshManger{
Meshes: make(map[string]*crdt.CrdtNodeManager), Meshes: make(map[string]*crdt.CrdtNodeManager),
HostEndpoint: fmt.Sprintf("%s:%s", ip.String(), conf.GrpcPort), HostEndpoint: fmt.Sprintf("%s:%s", ip.String(), conf.GrpcPort),
Client: client, Client: client,
conf: &conf, conf: &conf,
} }
m.RouteManager = NewRouteManager(m)
return m
} }

78
pkg/mesh/route_manager.go Normal file
View File

@ -0,0 +1,78 @@
package mesh
import (
"net"
crdt "github.com/tim-beatham/wgmesh/pkg/automerge"
"github.com/tim-beatham/wgmesh/pkg/ip"
logging "github.com/tim-beatham/wgmesh/pkg/log"
"github.com/tim-beatham/wgmesh/pkg/route"
)
type RouteManager interface {
UpdateRoutes() error
ApplyWg(mesh *crdt.CrdtNodeManager) error
}
type RouteManagerImpl struct {
meshManager *MeshManger
routeInstaller route.RouteInstaller
}
func (r *RouteManagerImpl) UpdateRoutes() error {
meshes := r.meshManager.Meshes
ulaBuilder := new(ip.ULABuilder)
for _, mesh1 := range meshes {
for _, mesh2 := range meshes {
if mesh1 == mesh2 {
continue
}
ipNet, err := ulaBuilder.GetIPNet(mesh2.MeshId)
if err != nil {
logging.Log.WriteErrorf(err.Error())
return err
}
mesh1.AddRoutes(ipNet.String())
}
}
return nil
}
func (r *RouteManagerImpl) ApplyWg(mesh *crdt.CrdtNodeManager) error {
snapshot, err := mesh.GetCrdt()
if err != nil {
return err
}
for _, node := range snapshot.Nodes {
if node.HostEndpoint == r.meshManager.HostEndpoint {
continue
}
for route, _ := range node.Routes {
_, netIP, err := net.ParseCIDR(route)
if err != nil {
return err
}
err = r.routeInstaller.InstallRoutes(mesh.IfName, netIP)
if err != nil {
return err
}
}
}
return nil
}
func NewRouteManager(m *MeshManger) RouteManager {
return &RouteManagerImpl{meshManager: m, routeInstaller: route.NewRouteInstaller()}
}

View File

@ -51,6 +51,7 @@ func (n *RobinIpc) CreateMesh(args *ipc.NewMeshArgs, reply *string) error {
PublicKey: pubKey.String(), PublicKey: pubKey.String(),
WgEndpoint: fmt.Sprintf("%s:%d", outBoundIp.String(), args.WgPort), WgEndpoint: fmt.Sprintf("%s:%d", outBoundIp.String(), args.WgPort),
WgHost: nodeIP.String() + "/128", WgHost: nodeIP.String() + "/128",
Routes: map[string]interface{}{},
} }
n.Server.MeshManager.AddMeshNode(meshId, meshNode) n.Server.MeshManager.AddMeshNode(meshId, meshNode)
@ -127,6 +128,7 @@ func (n *RobinIpc) JoinMesh(args ipc.JoinMeshArgs, reply *string) error {
WgEndpoint: fmt.Sprintf("%s:%d", outBoundIP.String(), args.Port), WgEndpoint: fmt.Sprintf("%s:%d", outBoundIP.String(), args.Port),
PublicKey: pubKey.String(), PublicKey: pubKey.String(),
WgHost: ipAddr.String() + "/128", WgHost: ipAddr.String() + "/128",
Routes: make(map[string]interface{}),
} }
n.Server.MeshManager.AddMeshNode(args.MeshId, node) n.Server.MeshManager.AddMeshNode(args.MeshId, node)
@ -154,6 +156,7 @@ func (n *RobinIpc) GetMesh(meshId string, reply *ipc.GetMeshReply) error {
WgHost: node.WgHost, WgHost: node.WgHost,
Failed: mesh.HasFailed(node.HostEndpoint), Failed: mesh.HasFailed(node.HostEndpoint),
Timestamp: node.Timestamp, Timestamp: node.Timestamp,
Routes: lib.MapKeys(node.Routes),
} }
nodes[i] = node nodes[i] = node

47
pkg/route/route.go Normal file
View File

@ -0,0 +1,47 @@
package route
import (
"net"
"os/exec"
logging "github.com/tim-beatham/wgmesh/pkg/log"
)
type RouteInstaller interface {
InstallRoutes(devName string, routes ...*net.IPNet) error
}
type RouteInstallerImpl struct{}
// InstallRoutes: installs a route into the routing table
func (r *RouteInstallerImpl) InstallRoutes(devName string, routes ...*net.IPNet) error {
for _, route := range routes {
err := r.installRoute(devName, route)
if err != nil {
return err
}
}
return nil
}
// installRoute: installs a route into the linux table
func (r *RouteInstallerImpl) installRoute(devName string, route *net.IPNet) error {
// TODO: Find a library that automates this
cmd := exec.Command("/usr/bin/ip", "-6", "route", "add", route.String(), "dev", devName)
logging.Log.WriteInfof("%s %s", route.String(), devName)
if msg, err := cmd.CombinedOutput(); err != nil {
logging.Log.WriteErrorf(err.Error())
logging.Log.WriteErrorf(string(msg))
return err
}
return nil
}
func NewRouteInstaller() RouteInstaller {
return &RouteInstallerImpl{}
}