mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +00:00
A Node bound its listener with net.Listen("tcp", ":port") and reached a
peer with net.DialTimeout("tcp", addr), so a socket path was refused at
the dialer — "missing port in address" — and could not be bound at all.
Service-to-service traffic on one host therefore had to take a TCP hop
it does not need.
NodeConfig.Address says where a node listens and overrides Port. Network
derives the network from the address — a path is a unix socket, anything
else is host:port — and the listener and both dial paths share that one
rule, so a node cannot bind one transport and be dialled on another.
A unix socket is unreachable off-host, so a node bound to one does not
advertise over mDNS. A socket file left by a process that did not unlink
it is cleared before bind; one that is still served is refused, so two
live nodes cannot steal each other's address.
Co-authored-by: Hanzo Dev <[email protected]>
57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zap
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Network returns the net package's network name for addr: a filesystem
|
|
// path names a unix socket, anything else is a host:port TCP address.
|
|
//
|
|
// It is the ONE rule the listener and the dialer both use, so a node can
|
|
// never bind one transport and be dialled on another. An address is a
|
|
// path when it is absolute, explicitly relative, or in Linux's abstract
|
|
// namespace ("@name") — none of which is a legal host:port.
|
|
func Network(addr string) string {
|
|
switch {
|
|
case strings.HasPrefix(addr, "/"),
|
|
strings.HasPrefix(addr, "./"),
|
|
strings.HasPrefix(addr, "../"),
|
|
strings.HasPrefix(addr, "@"):
|
|
return "unix"
|
|
default:
|
|
return "tcp"
|
|
}
|
|
}
|
|
|
|
// removeDeadSocket clears a socket file left behind by a process that did
|
|
// not unlink it, which would otherwise make bind fail with EADDRINUSE
|
|
// while nothing is listening. A socket someone IS listening on is left
|
|
// alone, so two live nodes cannot silently steal each other's address.
|
|
func removeDeadSocket(path string) error {
|
|
if strings.HasPrefix(path, "@") {
|
|
return nil // abstract sockets have no filesystem entry
|
|
}
|
|
fi, err := os.Stat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("stat %s: %w", path, err)
|
|
}
|
|
if fi.Mode()&os.ModeSocket == 0 {
|
|
return fmt.Errorf("%s exists and is not a socket", path)
|
|
}
|
|
if c, err := net.DialTimeout("unix", path, 200*time.Millisecond); err == nil {
|
|
c.Close()
|
|
return fmt.Errorf("%s is already served by a live node", path)
|
|
}
|
|
return os.Remove(path)
|
|
}
|