mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-27 02:57:02 +00:00
Compare commits
2 Commits
v0.5.7
...
neil/linkc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2bc2b77ed9 | ||
![]() |
bfdcf0762f |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -26,25 +26,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
- in case of vulnerabilities.
|
||||
-->
|
||||
|
||||
## [0.5.7] - 2024-08-05
|
||||
|
||||
### Added
|
||||
|
||||
* WebSocket support for peerings, by using the new `ws://` scheme in `Listen` and `Peers`
|
||||
* Additionally, the `wss://` scheme can be used to connect to a WebSocket peer behind a HTTPS reverse proxy
|
||||
|
||||
### Changed
|
||||
|
||||
* On Linux, the TUN adapter now uses vectorised reads/writes where possible, which should reduce the amount of CPU time spent on syscalls and potentially improve throughput
|
||||
* Link error handling has been improved and various link error messages have been rewritten to be clearer
|
||||
* Upgrade dependencies
|
||||
|
||||
### Fixed
|
||||
|
||||
* Multiple multicast connections to the same remote machine should now work correctly
|
||||
* You may get two connections in some cases, one inbound and one outbound, this is known and will not cause problems
|
||||
* Running as a Windows service should be more reliable with service startup and shutdown bugs fixed
|
||||
|
||||
## [0.5.6] - 2024-05-30
|
||||
|
||||
* Go 1.21 is now required to build Yggdrasil
|
||||
|
@@ -53,13 +53,13 @@ func main() {
|
||||
getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key")
|
||||
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
||||
flag.Parse()
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
|
||||
// Catch interrupts from the operating system to exit gracefully.
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Capture the service being stopped on Windows.
|
||||
minwinsvc.SetOnExit(cancel)
|
||||
|
||||
// Create a new logger that logs output to stdout.
|
||||
var logger *log.Logger
|
||||
switch *logto {
|
||||
@@ -247,6 +247,7 @@ func main() {
|
||||
Listen: intf.Listen,
|
||||
Port: intf.Port,
|
||||
Priority: uint8(intf.Priority),
|
||||
Cost: uint8(intf.Cost),
|
||||
Password: intf.Password,
|
||||
})
|
||||
}
|
||||
@@ -271,14 +272,6 @@ func main() {
|
||||
n.tun.SetupAdminHandlers(n.admin)
|
||||
}
|
||||
}
|
||||
|
||||
//Windows service shutdown
|
||||
minwinsvc.SetOnExit(func() {
|
||||
logger.Infof("Shutting down service ...")
|
||||
cancel()
|
||||
// Wait for all parts to shutdown properly
|
||||
<-done
|
||||
})
|
||||
|
||||
// Block until we are told to shut down.
|
||||
<-ctx.Done()
|
||||
|
@@ -174,7 +174,7 @@ func run() int {
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Last Error"})
|
||||
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Cost", "Last Error"})
|
||||
for _, peer := range resp.Peers {
|
||||
state, lasterr, dir, rtt := "Up", "-", "Out", "-"
|
||||
if !peer.Up {
|
||||
@@ -200,6 +200,7 @@ func run() int {
|
||||
peer.RXBytes.String(),
|
||||
peer.TXBytes.String(),
|
||||
fmt.Sprintf("%d", peer.Priority),
|
||||
fmt.Sprintf("%d", peer.Cost),
|
||||
lasterr,
|
||||
})
|
||||
}
|
||||
|
@@ -97,6 +97,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||
Listen: intf.Listen,
|
||||
Port: intf.Port,
|
||||
Priority: uint8(intf.Priority),
|
||||
Cost: uint8(intf.Cost),
|
||||
Password: intf.Password,
|
||||
})
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@@ -2,6 +2,8 @@ module github.com/yggdrasil-network/yggdrasil-go
|
||||
|
||||
go 1.21
|
||||
|
||||
replace github.com/Arceliar/ironwood => github.com/neilalexander/ironwood v0.0.0-20240530214820-2be4c2c0545a
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||
|
4
go.sum
4
go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 h1:SBdYBKeXYUUFef5wi2CMhYmXFVGiYaRpTvbki0Bu+JQ=
|
||||
github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
@@ -48,6 +46,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/neilalexander/ironwood v0.0.0-20240530214820-2be4c2c0545a h1:QtmEQn0ahcIFkkt8iXjBlyej984hdFS6Cc2cqTN2CuQ=
|
||||
github.com/neilalexander/ironwood v0.0.0-20240530214820-2be4c2c0545a/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
|
@@ -24,6 +24,7 @@ type PeerEntry struct {
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Priority uint64 `json:"priority"`
|
||||
Cost uint64 `json:"cost"`
|
||||
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
|
||||
TXBytes DataUnit `json:"bytes_sent,omitempty"`
|
||||
Uptime float64 `json:"uptime,omitempty"`
|
||||
@@ -41,6 +42,7 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse)
|
||||
Up: p.Up,
|
||||
Inbound: p.Inbound,
|
||||
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
||||
Cost: uint64(p.Cost), // can't be uint8 thanks to gobind
|
||||
URI: p.URI,
|
||||
RXBytes: DataUnit(p.RXBytes),
|
||||
TXBytes: DataUnit(p.TXBytes),
|
||||
|
@@ -62,6 +62,7 @@ type MulticastInterfaceConfig struct {
|
||||
Listen bool
|
||||
Port uint16
|
||||
Priority uint64 // really uint8, but gobind won't export it
|
||||
Cost uint16 // really uint8, but gobind won't export it
|
||||
Password string
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,7 @@ type PeerInfo struct {
|
||||
Coords []uint64
|
||||
Port uint64
|
||||
Priority uint8
|
||||
Cost uint8
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
Uptime time.Duration
|
||||
@@ -94,6 +95,7 @@ func (c *Core) GetPeers() []PeerInfo {
|
||||
peerinfo.Port = p.Port
|
||||
peerinfo.Priority = p.Priority
|
||||
peerinfo.Latency = p.Latency
|
||||
peerinfo.Cost = uint8(p.Cost)
|
||||
}
|
||||
peers = append(peers, peerinfo)
|
||||
}
|
||||
|
@@ -70,6 +70,7 @@ type link struct {
|
||||
type linkOptions struct {
|
||||
pinnedEd25519Keys map[keyArray]struct{}
|
||||
priority uint8
|
||||
cost uint8
|
||||
tlsSNI string
|
||||
password []byte
|
||||
maxBackoff time.Duration
|
||||
@@ -139,8 +140,9 @@ func (e linkError) Error() string { return string(e) }
|
||||
const ErrLinkAlreadyConfigured = linkError("peer is already configured")
|
||||
const ErrLinkNotConfigured = linkError("peer is not configured")
|
||||
const ErrLinkPriorityInvalid = linkError("priority value is invalid")
|
||||
const ErrLinkCostInvalid = linkError("cost value is invalid")
|
||||
const ErrLinkPinnedKeyInvalid = linkError("pinned public key is invalid")
|
||||
const ErrLinkPasswordInvalid = linkError("invalid password supplied")
|
||||
const ErrLinkPasswordInvalid = linkError("password is invalid")
|
||||
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
||||
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
|
||||
|
||||
@@ -181,6 +183,14 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
if p := u.Query().Get("cost"); p != "" {
|
||||
c, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
retErr = ErrLinkCostInvalid
|
||||
return
|
||||
}
|
||||
options.cost = uint8(c)
|
||||
}
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
retErr = ErrLinkPasswordInvalid
|
||||
@@ -363,11 +373,9 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||
_ = lc.Close()
|
||||
phony.Block(l, func() {
|
||||
state._conn = nil
|
||||
if err == nil {
|
||||
err = fmt.Errorf("remote side closed the connection")
|
||||
if state._err = err; state._err != nil {
|
||||
state._errtime = time.Now()
|
||||
}
|
||||
state._err = err
|
||||
state._errtime = time.Now()
|
||||
})
|
||||
|
||||
// If the link is persistently configured, back off if needed
|
||||
@@ -450,6 +458,13 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
if p := u.Query().Get("cost"); p != "" {
|
||||
c, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
return nil, ErrLinkCostInvalid
|
||||
}
|
||||
options.cost = uint8(c)
|
||||
}
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
return nil, ErrLinkPasswordInvalid
|
||||
@@ -569,6 +584,7 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s
|
||||
meta := version_getBaseMetadata()
|
||||
meta.publicKey = l.core.public
|
||||
meta.priority = options.priority
|
||||
meta.cost = options.cost
|
||||
metaBytes, err := meta.encode(l.core.secret, options.password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate handshake: %w", err)
|
||||
@@ -630,17 +646,20 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s
|
||||
remoteAddr := net.IP(address.AddrForKey(meta.publicKey)[:]).String()
|
||||
remoteStr := fmt.Sprintf("%s@%s", remoteAddr, conn.RemoteAddr())
|
||||
localStr := conn.LocalAddr()
|
||||
priority := options.priority
|
||||
cost, priority := options.cost, options.priority
|
||||
if meta.priority > priority {
|
||||
priority = meta.priority
|
||||
}
|
||||
if meta.cost > cost {
|
||||
cost = meta.cost
|
||||
}
|
||||
l.core.log.Infof("Connected %s: %s, source %s",
|
||||
dir, remoteStr, localStr)
|
||||
if success != nil {
|
||||
success()
|
||||
}
|
||||
|
||||
err = l.core.HandleConn(meta.publicKey, conn, priority)
|
||||
err = l.core.HandleConn(meta.publicKey, conn, cost, priority)
|
||||
switch err {
|
||||
case io.EOF, net.ErrClosed, nil:
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s",
|
||||
@@ -649,7 +668,7 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
|
||||
dir, remoteStr, localStr, err)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func urlForLinkInfo(u url.URL) url.URL {
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
@@ -21,6 +22,7 @@ type version_metadata struct {
|
||||
minorVer uint16
|
||||
publicKey ed25519.PublicKey
|
||||
priority uint8
|
||||
cost uint8
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -35,18 +37,9 @@ const (
|
||||
metaVersionMinor // uint16
|
||||
metaPublicKey // [32]byte
|
||||
metaPriority // uint8
|
||||
metaCost // uint8
|
||||
)
|
||||
|
||||
type handshakeError string
|
||||
|
||||
func (e handshakeError) Error() string { return string(e) }
|
||||
|
||||
const ErrHandshakeInvalidPreamble = handshakeError("invalid handshake, remote side is not Yggdrasil")
|
||||
const ErrHandshakeInvalidLength = handshakeError("invalid handshake length, possible version mismatch")
|
||||
const ErrHandshakeInvalidPassword = handshakeError("invalid password supplied, check your config")
|
||||
const ErrHandshakeHashFailure = handshakeError("invalid hash length")
|
||||
const ErrHandshakeIncorrectPassword = handshakeError("password does not match remote side")
|
||||
|
||||
// Gets a base metadata with no keys set, but with the correct version numbers.
|
||||
func version_getBaseMetadata() version_metadata {
|
||||
return version_metadata{
|
||||
@@ -73,9 +66,17 @@ func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte
|
||||
bs = binary.BigEndian.AppendUint16(bs, ed25519.PublicKeySize)
|
||||
bs = append(bs, m.publicKey[:]...)
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaPriority)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||
bs = append(bs, m.priority)
|
||||
if m.priority > 0 {
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaPriority)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||
bs = append(bs, m.priority)
|
||||
}
|
||||
|
||||
if m.cost > 0 {
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaCost)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||
bs = append(bs, m.cost)
|
||||
}
|
||||
|
||||
hasher, err := blake2b.New512(password)
|
||||
if err != nil {
|
||||
@@ -86,7 +87,7 @@ func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte
|
||||
return nil, err
|
||||
}
|
||||
if n != ed25519.PublicKeySize {
|
||||
return nil, ErrHandshakeHashFailure
|
||||
return nil, fmt.Errorf("hash writer only wrote %d bytes", n)
|
||||
}
|
||||
hash := hasher.Sum(nil)
|
||||
bs = append(bs, ed25519.Sign(privateKey, hash)...)
|
||||
@@ -103,11 +104,11 @@ func (m *version_metadata) decode(r io.Reader, password []byte) error {
|
||||
}
|
||||
meta := [4]byte{'m', 'e', 't', 'a'}
|
||||
if !bytes.Equal(bh[:4], meta[:]) {
|
||||
return ErrHandshakeInvalidPreamble
|
||||
return fmt.Errorf("invalid handshake preamble")
|
||||
}
|
||||
hl := binary.BigEndian.Uint16(bh[4:6])
|
||||
if hl < ed25519.SignatureSize {
|
||||
return ErrHandshakeInvalidLength
|
||||
return fmt.Errorf("invalid handshake length")
|
||||
}
|
||||
bs := make([]byte, hl)
|
||||
if _, err := io.ReadFull(r, bs); err != nil {
|
||||
@@ -135,21 +136,24 @@ func (m *version_metadata) decode(r io.Reader, password []byte) error {
|
||||
|
||||
case metaPriority:
|
||||
m.priority = bs[0]
|
||||
|
||||
case metaCost:
|
||||
m.cost = bs[0]
|
||||
}
|
||||
bs = bs[oplen:]
|
||||
}
|
||||
|
||||
hasher, err := blake2b.New512(password)
|
||||
if err != nil {
|
||||
return ErrHandshakeInvalidPassword
|
||||
return fmt.Errorf("invalid password supplied")
|
||||
}
|
||||
n, err := hasher.Write(m.publicKey)
|
||||
if err != nil || n != ed25519.PublicKeySize {
|
||||
return ErrHandshakeHashFailure
|
||||
return fmt.Errorf("failed to generate hash")
|
||||
}
|
||||
hash := hasher.Sum(nil)
|
||||
if !ed25519.Verify(m.publicKey, hash, sig) {
|
||||
return ErrHandshakeIncorrectPassword
|
||||
return fmt.Errorf("password is incorrect")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -52,6 +52,9 @@ func TestVersionRoundtrip(t *testing.T) {
|
||||
{majorVer: 258, minorVer: 259},
|
||||
{majorVer: 3, minorVer: 5, priority: 6},
|
||||
{majorVer: 260, minorVer: 261, priority: 7},
|
||||
{majorVer: 258, minorVer: 259, cost: 5},
|
||||
{majorVer: 3, minorVer: 5, priority: 6, cost: 12},
|
||||
{majorVer: 260, minorVer: 261, priority: 7, cost: 1},
|
||||
} {
|
||||
// Generate a random public key for each time, since it is
|
||||
// a required field.
|
||||
|
@@ -45,6 +45,7 @@ type interfaceInfo struct {
|
||||
listen bool
|
||||
port uint16
|
||||
priority uint8
|
||||
cost uint8
|
||||
password []byte
|
||||
hash []byte
|
||||
}
|
||||
@@ -214,6 +215,7 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||
listen: ifcfg.Listen,
|
||||
port: ifcfg.Port,
|
||||
priority: ifcfg.Priority,
|
||||
cost: ifcfg.Cost,
|
||||
password: []byte(ifcfg.Password),
|
||||
hash: hasher.Sum(nil),
|
||||
}
|
||||
@@ -314,6 +316,7 @@ func (m *Multicast) _announce() {
|
||||
// No listener was found - let's create one
|
||||
v := &url.Values{}
|
||||
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||
v.Add("cost", fmt.Sprintf("%d", info.cost))
|
||||
v.Add("password", string(info.password))
|
||||
u := &url.URL{
|
||||
Scheme: "tls",
|
||||
@@ -428,6 +431,7 @@ func (m *Multicast) listen() {
|
||||
v := &url.Values{}
|
||||
v.Add("key", hex.EncodeToString(adv.PublicKey))
|
||||
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||
v.Add("cost", fmt.Sprintf("%d", info.cost))
|
||||
v.Add("password", string(info.password))
|
||||
u := &url.URL{
|
||||
Scheme: "tls",
|
||||
|
@@ -21,6 +21,7 @@ type MulticastInterface struct {
|
||||
Listen bool
|
||||
Port uint16
|
||||
Priority uint8
|
||||
Cost uint8
|
||||
Password string
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user