hyprspace/rpc/server.go
Roland Urbano 3f573ef293
fix(config): error on ambiguous peer ID prefix (#113)
FindPeerByIDPrefix previously returned the first peer whose ID started
with the given prefix, silently ignoring further matches. With Ed25519
peer IDs all sharing the leading "12D3KooW" (8 chars), short prefixes
can match multiple peers; the chosen peer then depends on slice order,
which is effectively undefined from the user's perspective.

Change FindPeerByIDPrefix and its wrapper FindPeerByCLIRef to return
(*Peer, error):

  - 0 matches -> (nil, nil)
  - 1 match   -> (&peer, nil)
  - 2+ match  -> (nil, error) describing the ambiguity

Call sites in config.Read and rpc/server.go updated accordingly:
ambiguity surfaces as a config-load / CLI error instead of a silent
wrong-peer resolution.

Not a wire-protocol or config-format change. Only behavior visible at
CLI invocation and config load. Users relying on short prefixes that
happened to match multiple peers will now see a clear error and must
lengthen the prefix.
2026-05-23 22:26:05 +02:00

206 lines
5.0 KiB
Go

package rpc
import (
"context"
"errors"
"fmt"
"net"
"net/rpc"
"os"
"sync"
"syscall"
"github.com/hyprspace/hyprspace/config"
"github.com/hyprspace/hyprspace/p2p"
"github.com/hyprspace/hyprspace/tun"
"github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/yl2chen/cidranger"
)
var logger = log.Logger("hyprspace/rpc")
type HyprspaceRPC struct {
host host.Host
config config.Config
tunDev tun.TUN
}
func (hsr *HyprspaceRPC) Status(args *Args, reply *StatusReply) error {
netPeersCurrent := 0
var netPeerAddrsCurrent []string
for _, p := range hsr.config.Peers {
if hsr.host.Network().Connectedness(p.ID) == network.Connected {
netPeersCurrent = netPeersCurrent + 1
for _, c := range hsr.host.Network().ConnsToPeer(p.ID) {
netPeerAddrsCurrent = append(netPeerAddrsCurrent, fmt.Sprintf("@%s (%s) %s/p2p/%s",
p.Name,
hsr.host.Peerstore().LatencyEWMA(p.ID).String(),
c.RemoteMultiaddr().String(),
p.ID.String(),
))
}
}
}
var addrStrings []string
for _, ma := range hsr.host.Addrs() {
addrStrings = append(addrStrings, ma.String())
}
*reply = StatusReply{
hsr.host.ID().String(),
len(hsr.host.Network().Conns()),
netPeersCurrent,
netPeerAddrsCurrent,
len(hsr.config.Peers),
addrStrings,
}
return nil
}
func (hsr *HyprspaceRPC) Route(args *RouteArgs, reply *RouteReply) error {
switch args.Action {
case Show:
var routeInfos []RouteInfo
allRoutes4, err := hsr.config.PeerLookup.ByRoute.CoveredNetworks(*cidranger.AllIPv4)
if err != nil {
return err
}
allRoutes6, err := hsr.config.PeerLookup.ByRoute.CoveredNetworks(*cidranger.AllIPv6)
if err != nil {
return err
}
allRoutes := append(allRoutes4, allRoutes6...)
for _, r := range allRoutes {
rte := *r.(*config.RouteTableEntry)
connected := hsr.host.Network().Connectedness(rte.Target.ID) == network.Connected
relay := false
relayAddr := rte.Target.ID
if connected {
ConnLoop:
for _, c := range hsr.host.Network().ConnsToPeer(rte.Target.ID) {
for _, s := range c.GetStreams() {
if s.Protocol() == p2p.Protocol {
if _, err := c.RemoteMultiaddr().ValueForProtocol(multiaddr.P_CIRCUIT); err == nil {
relay = true
if ra, err := c.RemoteMultiaddr().ValueForProtocol(multiaddr.P_P2P); err == nil {
relayAddr, err = peer.Decode(ra)
if err != nil {
relayAddr = rte.Target.ID
}
}
} else {
relay = false
relayAddr = rte.Target.ID
break ConnLoop
}
}
}
}
}
routeInfos = append(routeInfos, RouteInfo{
Network: rte.Network(),
TargetName: rte.Target.Name,
TargetAddr: rte.Target.ID,
RelayAddr: relayAddr,
IsRelay: relay,
IsConnected: connected,
})
}
*reply = RouteReply{
Routes: routeInfos,
}
case Add:
if len(args.Args) != 2 {
return errors.New("expected exactly 2 arguments")
}
_, network, err := net.ParseCIDR(args.Args[0])
if err != nil {
return err
}
target, err := config.FindPeerByCLIRef(hsr.config.Peers, args.Args[1])
if err != nil {
return err
}
if target == nil {
return errors.New("no such peer")
}
err = hsr.tunDev.Apply(tun.Route(*network))
if err != nil {
return err
}
hsr.config.PeerLookup.ByRoute.Insert(&config.RouteTableEntry{
Net: *network,
Target: *target,
})
case Del:
if len(args.Args) != 1 {
return errors.New("expected exactly 1 argument")
}
_, network, err := net.ParseCIDR(args.Args[0])
if err != nil {
return err
}
err = hsr.tunDev.Apply(tun.RemoveRoute(*network))
if err != nil {
return err
}
_, err = hsr.config.PeerLookup.ByRoute.Remove(*network)
if err != nil {
_ = hsr.tunDev.Apply(tun.Route(*network))
return err
}
default:
return errors.New("no such action")
}
return nil
}
func (hsr *HyprspaceRPC) Peers(args *Args, reply *PeersReply) error {
var peerAddrs []string
for _, c := range hsr.host.Network().Conns() {
peerAddrs = append(peerAddrs, fmt.Sprintf("%s/p2p/%s", c.RemoteMultiaddr().String(), c.RemotePeer().String()))
}
*reply = PeersReply{peerAddrs}
return nil
}
func RpcServer(ctx context.Context, wg *sync.WaitGroup, ma multiaddr.Multiaddr, host host.Host, config config.Config, tunDev tun.TUN) {
wg.Add(1)
defer wg.Done()
hsr := HyprspaceRPC{host, config, tunDev}
rpc.Register(&hsr)
addr, err := ma.ValueForProtocol(multiaddr.P_UNIX)
if err != nil {
logger.With(err).Fatal("Failed to parse multiaddr")
}
var l net.Listener
oldUmask := syscall.Umask(0o007)
err = os.Remove(addr)
if err != nil && !os.IsNotExist(err) {
logger.Fatal("[!] Could not remove old RPC socket: ", err)
}
var lc net.ListenConfig
l, err = lc.Listen(ctx, "unix", addr)
syscall.Umask(oldUmask)
if err != nil {
logger.With(err).Fatal("Failed to launch RPC server")
}
logger.Info("RPC server ready")
defer l.Close()
go rpc.Accept(l)
<-ctx.Done()
logger.Info("Closing RPC server")
}