Files

111 lines
3.8 KiB
Go

package network
import (
"fmt"
"net"
"net/netip"
"github.com/psviderski/uncloud/internal/secret"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type Config struct {
// Subnet is the IPv4 address range allocated to the machine. The machine's IP address is the first address
// in the subnet. Other IP addresses are allocated to containers running on the machine.
Subnet netip.Prefix
// ManagementIP is the IPv6 address assigned to the machine within the WireGuard network. This address is used
// for cluster management traffic, such as gRPC communication with the machine API server and Corrosion gossip.
ManagementIP netip.Addr
PrivateKey secret.Secret
PublicKey secret.Secret
Peers []PeerConfig `json:",omitempty"`
}
type PeerConfig struct {
Subnet *netip.Prefix `json:",omitempty"`
// ManagementIP is the IPv6 address assigned to the peer within the WireGuard network. This address is used
// for cluster management traffic, such as gRPC communication with the machine API server and Corrosion gossip.
ManagementIP netip.Addr
Endpoint *netip.AddrPort `json:",omitempty"`
AllEndpoints []netip.AddrPort `json:",omitempty"`
PublicKey secret.Secret
}
// IsConfigured returns true if the configuration is complete to establish a WireGuard network.
func (c Config) IsConfigured() bool {
return c.Subnet != (netip.Prefix{}) && c.ManagementIP != (netip.Addr{}) &&
c.PrivateKey != nil && c.PublicKey != nil
}
// toDeviceConfig converts the configuration to a WireGuard device configuration. It updates the existing peers
// without replacing them to not disrupt existing connections and to not lose track of the last handshake time.
func (c Config) toDeviceConfig(currentPeers []wgtypes.Peer) (wgtypes.Config, error) {
privateKey, err := wgtypes.NewKey(c.PrivateKey)
if err != nil {
return wgtypes.Config{}, fmt.Errorf("parse private key: %w", err)
}
listenPort := WireGuardPort
persistentKeepalive := WireGuardKeepaliveInterval
wgPeerConfigs := make([]wgtypes.PeerConfig, len(c.Peers))
// A set of new peer public keys for checking which current peers should be removed.
newPeersSet := make(map[string]struct{}, len(c.Peers))
for i, peerConfig := range c.Peers {
peerPublicKey, kErr := wgtypes.NewKey(peerConfig.PublicKey)
if kErr != nil {
return wgtypes.Config{}, fmt.Errorf("parse peer public key: %w", kErr)
}
manageIP, mErr := addrToSingleIPPrefix(peerConfig.ManagementIP)
if mErr != nil {
return wgtypes.Config{}, fmt.Errorf("parse management IP: %w", mErr)
}
allowedIPs := []net.IPNet{prefixToIPNet(manageIP)}
if peerConfig.Subnet != nil {
allowedIPs = append(allowedIPs, prefixToIPNet(*peerConfig.Subnet))
}
wgPeerConfigs[i] = wgtypes.PeerConfig{
PublicKey: peerPublicKey,
ReplaceAllowedIPs: true,
AllowedIPs: allowedIPs,
PersistentKeepaliveInterval: &persistentKeepalive,
}
if peerConfig.Endpoint != nil {
wgPeerConfigs[i].Endpoint = &net.UDPAddr{
IP: peerConfig.Endpoint.Addr().AsSlice(),
Port: int(peerConfig.Endpoint.Port()),
}
}
newPeersSet[wgPeerConfigs[i].PublicKey.String()] = struct{}{}
}
// Remove peers that are not in the configuration.
for _, p := range currentPeers {
if _, ok := newPeersSet[p.PublicKey.String()]; !ok {
wgPeerConfigs = append(wgPeerConfigs, wgtypes.PeerConfig{
PublicKey: p.PublicKey,
Remove: true,
})
}
}
return wgtypes.Config{
PrivateKey: &privateKey,
ListenPort: &listenPort,
ReplacePeers: false,
Peers: wgPeerConfigs,
}, nil
}
func (p *PeerConfig) prefixes() ([]netip.Prefix, error) {
managePrefix, err := addrToSingleIPPrefix(p.ManagementIP)
if err != nil {
return nil, fmt.Errorf("parse management IP: %w", err)
}
prefixes := []netip.Prefix{managePrefix}
if p.Subnet != nil {
prefixes = append(prefixes, *p.Subnet)
}
return prefixes, nil
}