mirror of
https://github.com/hyprspace/hyprspace.git
synced 2026-09-12 19:51:07 +05:00
Add a 'domain' option to the NixOS module and JSON config that controls the domain suffix used for internal DNS names. Defaults to 'hyprspace' for backward compatibility. Supports multi-label domains (e.g. 'vpn.internal'). Co-authored-by: Max <max@privatevoid.net>
298 lines
7.9 KiB
Go
298 lines
7.9 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hyprspace/hyprspace/schema"
|
|
"github.com/libp2p/go-libp2p/core/crypto"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/multiformats/go-multiaddr"
|
|
"github.com/multiformats/go-multibase"
|
|
"github.com/yl2chen/cidranger"
|
|
)
|
|
|
|
// Config is the main Configuration Struct for Hyprspace.
|
|
type Config struct {
|
|
Path string `json:"-"`
|
|
Interface string `json:"-"`
|
|
ListenAddresses []multiaddr.Multiaddr `json:"-"`
|
|
BootstrapPeers []multiaddr.Multiaddr `json:"-"`
|
|
Peers []Peer `json:"peers"`
|
|
PeerLookup PeerLookup `json:"-"`
|
|
PrivateKey crypto.PrivKey `json:"-"`
|
|
BuiltinAddr4 net.IP `json:"-"`
|
|
BuiltinAddr6 net.IP `json:"-"`
|
|
Services map[string]Service `json:"-"`
|
|
FilterPrivateAddresses bool `json:"-"`
|
|
Domain string `json:"-"`
|
|
}
|
|
|
|
// Peer defines a peer in the configuration. We might add more to this later.
|
|
type Peer struct {
|
|
ID peer.ID `json:"id"`
|
|
Name string `json:"name"`
|
|
BuiltinAddr4 net.IP `json:"-"`
|
|
BuiltinAddr6 net.IP `json:"-"`
|
|
}
|
|
|
|
// PeerLookup is a helper struct for quickly looking up a peer based on various parameters
|
|
type PeerLookup struct {
|
|
ByRoute cidranger.Ranger
|
|
ByName map[string]Peer
|
|
ByNetID map[[4]byte]Peer
|
|
}
|
|
|
|
type RouteTableEntry struct {
|
|
Net net.IPNet
|
|
Target Peer
|
|
}
|
|
|
|
// Service represents the configuration for a specific service provided by this node.
|
|
// Whitelist and Blacklist allow fine granularity in access control.
|
|
// If Blacklist is set, this will be evaluated first and any client id present in Blacklist will
|
|
// have access denied. Whitelist is evaluated after.
|
|
type Service struct {
|
|
Target multiaddr.Multiaddr
|
|
EnableWhitelist bool
|
|
Whitelist map[peer.ID]struct{}
|
|
Blacklist map[peer.ID]struct{}
|
|
}
|
|
|
|
func (rte RouteTableEntry) Network() net.IPNet {
|
|
return rte.Net
|
|
}
|
|
|
|
// Read initializes a config from a file.
|
|
func Read(path string) (*Config, error) {
|
|
in, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
input := schema.Config{}
|
|
result := Config{}
|
|
|
|
// Read in config settings from file.
|
|
err = json.Unmarshal(in, &input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, keyBytes, err := multibase.Decode(input.PrivateKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk, err := crypto.UnmarshalPrivateKey(keyBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result.PrivateKey = pk
|
|
|
|
peerID, err := peer.IDFromPrivateKey(result.PrivateKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result.BuiltinAddr4 = mkBuiltinAddr4(peerID)
|
|
result.BuiltinAddr6 = mkBuiltinAddr6(peerID)
|
|
|
|
for _, addrString := range input.ListenAddresses {
|
|
addr, err := multiaddr.NewMultiaddr(addrString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result.ListenAddresses = append(result.ListenAddresses, addr)
|
|
}
|
|
|
|
result.PeerLookup.ByRoute = cidranger.NewPCTrieRanger()
|
|
result.PeerLookup.ByName = make(map[string]Peer)
|
|
result.PeerLookup.ByNetID = make(map[[4]byte]Peer)
|
|
result.Peers = make([]Peer, len(input.Peers))
|
|
|
|
for i, configPeer := range input.Peers {
|
|
p := Peer{}
|
|
p.ID, err = peer.Decode(configPeer.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Name = configPeer.Name
|
|
p.BuiltinAddr4 = mkBuiltinAddr4(p.ID)
|
|
p.BuiltinAddr6 = mkBuiltinAddr6(p.ID)
|
|
for _, r := range configPeer.Routes {
|
|
_, network, err := net.ParseCIDR(r.Net)
|
|
if err != nil {
|
|
log.Fatal("[!] Invalid network:", r.Net)
|
|
}
|
|
|
|
result.PeerLookup.ByRoute.Insert(&RouteTableEntry{
|
|
Net: *network,
|
|
Target: p,
|
|
})
|
|
|
|
fmt.Printf("[+] Route %s via /p2p/%s\n", network.String(), p.ID)
|
|
}
|
|
result.PeerLookup.ByRoute.Insert(&RouteTableEntry{
|
|
Net: net.IPNet{
|
|
IP: p.BuiltinAddr4,
|
|
Mask: net.CIDRMask(32, 32),
|
|
},
|
|
Target: p,
|
|
})
|
|
result.PeerLookup.ByRoute.Insert(&RouteTableEntry{
|
|
Net: net.IPNet{
|
|
IP: p.BuiltinAddr6,
|
|
Mask: net.CIDRMask(128, 128),
|
|
},
|
|
Target: p,
|
|
})
|
|
if configPeer.Name != "" {
|
|
result.PeerLookup.ByName[strings.ToLower(configPeer.Name)] = p
|
|
}
|
|
result.PeerLookup.ByNetID[[4]byte(p.BuiltinAddr6[12:16])] = p
|
|
result.Peers[i] = p
|
|
}
|
|
|
|
result.Services = make(map[string]Service)
|
|
for name, service := range input.Services {
|
|
addr, err := multiaddr.NewMultiaddr(service.Target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
whitelist := make(map[peer.ID]struct{})
|
|
blacklist := make(map[peer.ID]struct{})
|
|
for _, p := range service.Acl.Whitelist {
|
|
cfgPeer, err := FindPeerByCLIRef(result.Peers, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cfgPeer == nil {
|
|
return nil, errors.New("unknown peer: " + p)
|
|
}
|
|
whitelist[cfgPeer.ID] = struct{}{}
|
|
}
|
|
for _, peerStr := range service.Acl.Blacklist {
|
|
cfgPeer, err := FindPeerByCLIRef(result.Peers, peerStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cfgPeer == nil {
|
|
return nil, errors.New("unknown peer: " + peerStr)
|
|
}
|
|
blacklist[cfgPeer.ID] = struct{}{}
|
|
}
|
|
result.Services[name] = Service{
|
|
Target: addr,
|
|
EnableWhitelist: service.Acl.EnableWhitelist,
|
|
Whitelist: whitelist,
|
|
Blacklist: blacklist,
|
|
}
|
|
}
|
|
|
|
result.FilterPrivateAddresses = input.FilterPrivateAddresses
|
|
|
|
result.Domain = input.Domain
|
|
if result.Domain == "" {
|
|
result.Domain = "hyprspace"
|
|
}
|
|
|
|
for _, addrString := range input.BootstrapPeers {
|
|
addr, err := multiaddr.NewMultiaddr(addrString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result.BootstrapPeers = append(result.BootstrapPeers, addr)
|
|
}
|
|
|
|
// Overwrite path of config to input.
|
|
result.Path = path
|
|
return &result, nil
|
|
}
|
|
|
|
func FindPeer(peers []Peer, needle peer.ID) (*Peer, bool) {
|
|
for _, p := range peers {
|
|
if p.ID == needle {
|
|
return &p, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func FindPeerByName(peers []Peer, needle string) (*Peer, bool) {
|
|
for _, p := range peers {
|
|
if p.Name == needle {
|
|
return &p, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// FindPeerByIDPrefix returns the unique peer whose ID starts with needle.
|
|
// Returns (nil, nil) if no peer matches.
|
|
// Returns (nil, error) if more than one peer matches (ambiguous prefix).
|
|
func FindPeerByIDPrefix(peers []Peer, needle string) (*Peer, error) {
|
|
var match *Peer
|
|
count := 0
|
|
for i := range peers {
|
|
if strings.HasPrefix(peers[i].ID.String(), needle) {
|
|
match = &peers[i]
|
|
count++
|
|
}
|
|
}
|
|
if count > 1 {
|
|
return nil, fmt.Errorf("ambiguous peer prefix %q matches %d peers", needle, count)
|
|
}
|
|
return match, nil
|
|
}
|
|
|
|
// FindPeerByCLIRef resolves a CLI peer reference: "@name" for name lookup,
|
|
// otherwise a peer ID prefix. Returns (nil, nil) on no match,
|
|
// (nil, error) only on ambiguous ID prefix.
|
|
func FindPeerByCLIRef(peers []Peer, needle string) (*Peer, error) {
|
|
if needle == "" {
|
|
return nil, nil
|
|
}
|
|
if strings.HasPrefix(needle, "@") {
|
|
name := strings.TrimPrefix(needle, "@")
|
|
p, _ := FindPeerByName(peers, name)
|
|
return p, nil
|
|
}
|
|
return FindPeerByIDPrefix(peers, needle)
|
|
}
|
|
|
|
func (cfg Config) FindRoute(needle net.IPNet) (*RouteTableEntry, bool) {
|
|
networks, err := cfg.PeerLookup.ByRoute.CoveredNetworks(needle)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil, false
|
|
} else if len(networks) == 0 {
|
|
return nil, false
|
|
} else if len(networks) > 1 {
|
|
for _, n := range networks {
|
|
fmt.Printf("[!] Found duplicate route %s to /p2p/%s for %s\n", n.Network(), n.(RouteTableEntry).Target.ID, needle)
|
|
}
|
|
}
|
|
return networks[0].(*RouteTableEntry), true
|
|
}
|
|
|
|
func (cfg Config) FindRouteForIP(needle net.IP) (*RouteTableEntry, bool) {
|
|
networks, err := cfg.PeerLookup.ByRoute.ContainingNetworks(needle)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return nil, false
|
|
} else if len(networks) == 0 {
|
|
return nil, false
|
|
} else if len(networks) > 1 {
|
|
for _, n := range networks {
|
|
fmt.Printf("[!] Found duplicate route %s to /p2p/%s for %s\n", n.Network(), n.(RouteTableEntry).Target.ID, needle)
|
|
}
|
|
}
|
|
return networks[0].(*RouteTableEntry), true
|
|
}
|