1. added multipath protocol and schema suport

2. added SCTP protocol and schema support
3. added set of NAS models support (Asustor, ReadyNAS, Drobo, QNAP, WD, Synology, Terramaster)
4. moved to fc00::/7 private segment
5. added Windows, MacOS and Linux UI for peers edit and current status
This commit is contained in:
vadym 2022-10-27 22:03:37 +03:00
parent cfa293d189
commit d8a4000141
198 changed files with 8589 additions and 697 deletions

View file

@ -1,4 +1,4 @@
// Package address contains the types used by yggdrasil to represent IPv6 addresses or prefixes, as well as functions for working with these types.
// Package address contains the types used by mesh to represent IPv6 addresses or prefixes, as well as functions for working with these types.
// Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches.
package address
@ -6,18 +6,18 @@ import (
"crypto/ed25519"
)
// Address represents an IPv6 address in the yggdrasil address range.
// Address represents an IPv6 address in the mesh address range.
type Address [16]byte
// Subnet represents an IPv6 /64 subnet in the yggdrasil subnet range.
// Subnet represents an IPv6 /64 subnet in the mesh subnet range.
type Subnet [8]byte
// GetPrefix returns the address prefix used by yggdrasil.
// GetPrefix returns the address prefix used by mesh.
// The current implementation requires this to be a multiple of 8 bits + 7 bits.
// The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1).
// Nodes that configure this differently will be unable to communicate with each other using IP packets, though routing and the DHT machinery *should* still work.
func GetPrefix() [1]byte {
return [...]byte{0x02}
return [...]byte{0xfc}
}
// IsValid returns true if an address falls within the range used by nodes in the network.

View file

@ -17,13 +17,13 @@ func TestAddress_Address_IsValid(t *testing.T) {
t.Fatal("invalid address marked as valid")
}
address[0] = 0x03
address[0] = 0xfd
if address.IsValid() {
t.Fatal("invalid address marked as valid")
}
address[0] = 0x02
address[0] = 0xfc
if !address.IsValid() {
t.Fatal("valid address marked as invalid")
@ -40,13 +40,13 @@ func TestAddress_Subnet_IsValid(t *testing.T) {
t.Fatal("invalid subnet marked as valid")
}
subnet[0] = 0x02
subnet[0] = 0xfc
if subnet.IsValid() {
t.Fatal("invalid subnet marked as valid")
}
subnet[0] = 0x03
subnet[0] = 0xfd
if !subnet.IsValid() {
t.Fatal("valid subnet marked as invalid")
@ -60,7 +60,7 @@ func TestAddress_AddrForKey(t *testing.T) {
}
expectedAddress := Address{
2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
f, c, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
}
if *AddrForKey(publicKey) != expectedAddress {
@ -74,7 +74,7 @@ func TestAddress_SubnetForKey(t *testing.T) {
251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251,
}
expectedSubnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126}
expectedSubnet := Subnet{f, d, 0, 132, 138, 96, 79, 187, 126}
if *SubnetForKey(publicKey) != expectedSubnet {
t.Fatal("invalid subnet returned")
@ -83,7 +83,7 @@ func TestAddress_SubnetForKey(t *testing.T) {
func TestAddress_Address_GetKey(t *testing.T) {
address := Address{
2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
f, c, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
}
expectedPublicKey := ed25519.PublicKey{
@ -99,7 +99,7 @@ func TestAddress_Address_GetKey(t *testing.T) {
}
func TestAddress_Subnet_GetKey(t *testing.T) {
subnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126}
subnet := Subnet{f, d, 0, 132, 138, 96, 79, 187, 126}
expectedPublicKey := ed25519.PublicKey{
189, 186, 207, 216, 34, 64, 255, 255,

30
src/admin/addpeers.go Normal file
View file

@ -0,0 +1,30 @@
package admin
import (
"fmt"
)
type AddPeersRequest struct {
Uri string `json:"uri"`
Intf string `json:"intf"`
}
type AddPeersResponse struct {
List []string `json:"list"`
}
func (a *AdminSocket) addPeersHandler(req *AddPeersRequest, res *AddPeersResponse) error {
// Set sane defaults
err:=a.core.AddPeer(req.Uri, req.Intf)
if err != nil {
fmt.Println("adding peer error %s", err)
return err
} else {
fmt.Println("added peer %s", req.Uri)
res.List = append(res.List, req.Uri)
}
return nil
}

View file

@ -6,13 +6,15 @@ import (
"fmt"
"net"
"net/url"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/RiV-chain/RiV-mesh/src/config"
"github.com/RiV-chain/RiV-mesh/src/core"
)
// TODO: Add authentication
@ -201,12 +203,84 @@ func (a *AdminSocket) SetupAdminHandlers() {
return res, nil
},
)
_ = a.AddHandler("addPeers", "Add peers to this node", []string{"uri", "[interface]"}, func(in json.RawMessage) (interface{}, error) {
req := &AddPeersRequest{}
res := &AddPeersResponse{}
fmt.Println("json addpeers request %s", string(in[:]))
if err := json.Unmarshal(in, &req); err != nil {
return nil, err
}
if err := a.addPeersHandler(req, res); err != nil {
return nil, err
}
return res, nil
})
_ = a.AddHandler("removePeers", "Remove all peers from this node", []string{}, func(in json.RawMessage) (interface{}, error) {
a.core.RemovePeers()
res := &AddPeersResponse{}
return res, nil
})
//_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler)
//_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler)
//_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler)
//_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler)
}
// Start runs http server
func (a *AdminSocket) StartHttpServer(nc *config.NodeConfig) {
if nc.HttpAddress != "none" && nc.HttpAddress != "" && nc.WwwRoot != "none" && nc.WwwRoot != ""{
u, err := url.Parse(nc.HttpAddress)
if err != nil {
a.log.Errorln("An error occurred parsing http address:", err)
return
}
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Following methods are allowed: getself, getpeers. litening"+u.Host)
})
http.HandleFunc("/api/getself", func(w http.ResponseWriter, r *http.Request){
w.Header().Add("Content-Type", "application/json")
req := &GetSelfRequest{}
res := &GetSelfResponse{}
if err := a.getSelfHandler(req, res); err != nil {
http.Error(w, err.Error(), 503)
}
b, err := json.Marshal(res)
if err != nil {
http.Error(w, err.Error(), 503)
}
fmt.Fprintf(w, string(b[:]))
})
http.HandleFunc("/api/getpeers", func(w http.ResponseWriter, r *http.Request){
w.Header().Add("Content-Type", "application/json")
req := &GetPeersRequest{}
res := &GetPeersResponse{}
if err := a.getPeersHandler(req, res); err != nil {
http.Error(w, err.Error(), 503)
}
b, err := json.Marshal(res)
if err != nil {
http.Error(w, err.Error(), 503)
}
fmt.Fprintf(w, string(b[:]))
})
http.Handle("/", http.FileServer(http.Dir(nc.WwwRoot)))
l, e := net.Listen("tcp4", u.Host)
if e != nil {
a.log.Errorln("%s\n", e)
} else {
a.log.Infof("Http server listening on %s\n", u.Host)
}
go func() {
a.log.Errorln(http.Serve(l, nil))
}()
}
}
// IsStarted returns true if the module has been started.
func (a *AdminSocket) IsStarted() bool {
select {
@ -327,7 +401,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
var buf json.RawMessage
var req AdminSocketRequest
var resp AdminSocketResponse
req.Arguments = []byte("{}")
if err := func() error {
if err = decoder.Decode(&buf); err != nil {
return fmt.Errorf("Failed to find request")

View file

@ -6,7 +6,7 @@ import (
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
type GetDHTRequest struct{}

View file

@ -6,7 +6,7 @@ import (
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
type GetPathsRequest struct {

View file

@ -5,7 +5,7 @@ import (
"net"
"sort"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
type GetPeersRequest struct {

View file

@ -3,7 +3,7 @@ package admin
import (
"encoding/hex"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
"github.com/RiV-chain/RiV-mesh/src/version"
)
type GetSelfRequest struct{}

View file

@ -6,7 +6,7 @@ import (
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
type GetSessionsRequest struct{}

View file

@ -1,6 +1,6 @@
/*
The config package contains structures related to the configuration of an
Yggdrasil node.
RiV-mesh node.
The configuration contains, amongst other things, encryption keys which are used
to derive a node's identity, information about peerings and node information
@ -11,7 +11,7 @@ In order for a node to maintain the same identity across restarts, you should
persist the configuration onto the filesystem or into some configuration storage
so that the encryption keys (and therefore the node ID) do not change.
Note that Yggdrasil will automatically populate sane defaults for any
Note that RiV-mesh will automatically populate sane defaults for any
configuration option that is not provided.
*/
package config
@ -22,28 +22,30 @@ import (
)
// NodeConfig is the main configuration structure, containing configuration
// options that are necessary for an Yggdrasil node to run. You will need to
// supply one of these structs to the Yggdrasil core when starting a node.
// options that are necessary for an RiV-mesh node to run. You will need to
// supply one of these structs to the RiV-mesh core when starting a node.
type NodeConfig struct {
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for meshctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead.\nExamples: unix:///var/run/mesh.sock, tcp://localhost:9001."`
HttpAddress string `comment:"Listen address for admin rest requests and web interface. Default is to listen for local\nconnections on TCP/19019. To disable the admin rest interface,\nuse the value \"none\" instead. Example: http://localhost:19019."`
WwwRoot string `comment:"Points out to embedded webserver root folder path where web interface assets are located.\nExample:/apps/mesh/www."`
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"`
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and RiV-mesh version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
}
type MulticastInterfaceConfig struct {
Regex string
Beacon bool
Listen bool
Port uint16
Regex string
Beacon bool
Listen bool
Port uint16
Priority uint8
}

View file

@ -2,15 +2,22 @@ package core
import (
"crypto/ed25519"
"encoding/json"
"fmt"
"net"
"net/url"
"sync/atomic"
"time"
//"encoding/hex"
"encoding/json"
//"errors"
//"fmt"
"net"
"net/url"
//"sort"
//"time"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
type SelfInfo struct {
@ -146,7 +153,7 @@ func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
}
}
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
// Address gets the IPv6 address of the Mesh node. This is always a /128
// address. The IPv6 address is only relevant when the node is operating as an
// IP router and often is meaningless when embedded into an application, unless
// that application also implements either VPN functionality or deals with IP
@ -156,7 +163,7 @@ func (c *Core) Address() net.IP {
return addr
}
// Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a
// Subnet gets the routed IPv6 subnet of the Mesh node. This is always a
// /64 subnet. The IPv6 subnet is only relevant when the node is operating as an
// IP router and often is meaningless when embedded into an application, unless
// that application also implements either VPN functionality or deals with IP
@ -167,7 +174,7 @@ func (c *Core) Subnet() net.IPNet {
return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
}
// SetLogger sets the output logger of the Yggdrasil node after startup. This
// SetLogger sets the output logger of the Mesh node after startup. This
// may be useful if you want to redirect the output later. Note that this
// expects a Logger from the github.com/gologme/log package and not from Go's
// built-in log package.
@ -176,36 +183,29 @@ func (c *Core) SetLogger(log Logger) {
}
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
//
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
//
// tcp://a.b.c.d:e
// socks://a.b.c.d:e/f.g.h.i:j
// This adds the peer to the peer list, so that they will be called again if the
// connection drops.
func (c *Core) AddPeer(uri string, sourceInterface string) error {
var known bool
phony.Block(c, func() {
_, known = c.config._peers[Peer{uri, sourceInterface}]
})
if known {
return fmt.Errorf("peer already configured")
func (c *Core) AddPeer(peer string, intf string) error {
select {
case <-c.ctx.Done():
return nil
default:
}
u, err := url.Parse(uri)
u, err := url.Parse(peer)
if err != nil {
c.log.Errorln("Failed to parse peer url:", peer, err)
return err
}
info, err := c.links.call(u, sourceInterface)
if err != nil {
if err := c.CallPeer(u, intf); err != nil {
c.log.Errorln("Failed to add peer:", err)
return err
}
phony.Block(c, func() {
c.config._peers[Peer{uri, sourceInterface}] = &info
})
return nil
}
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
// The peer is not disconnected immediately.
func (c *Core) RemovePeer(uri string, sourceInterface string) error {
var err error
phony.Block(c, func() {
@ -227,6 +227,15 @@ func (c *Core) RemovePeer(uri string, sourceInterface string) error {
return err
}
func (c *Core) RemovePeers() error {
c.config._peers = map[Peer]*linkInfo{}
//for k := range c.config.InterfacePeers {
// delete(c.config.InterfacePeers, k)
//}
return nil
}
// CallPeer calls a peer once. This should be specified in the peer URI format,
// e.g.:
//

View file

@ -14,11 +14,11 @@ import (
"github.com/Arceliar/phony"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
"github.com/RiV-chain/RiV-mesh/src/version"
)
// The Core object represents the Yggdrasil node. You should create a Core
// object for each Yggdrasil node you plan to run.
// The Core object represents the Mesh node. You should create a Core
// object for each Mesh node you plan to run.
type Core struct {
// This is the main data structure that holds everything else for a node
// We're going to keep our own copy of the provided config - that way we can
@ -34,7 +34,7 @@ type Core struct {
log Logger
addPeerTimer *time.Timer
config struct {
_peers map[Peer]*linkInfo // configurable after startup
_peers map[Peer]*linkInfo // configurable after startup
_listeners map[ListenAddress]struct{} // configurable after startup
nodeinfo NodeInfo // immutable after startup
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
@ -121,7 +121,7 @@ func (c *Core) _addPeerLoop() {
})
}
// Stop shuts down the Yggdrasil node.
// Stop shuts down the Mesh node.
func (c *Core) Stop() {
phony.Block(c, func() {
c.log.Infoln("Stopping...")

View file

@ -10,191 +10,73 @@ import (
"net/url"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
"sync/atomic"
type links struct {
phony.Inbox
core *Core
tcp *linkTCP // TCP interface support
tls *linkTLS // TLS interface support
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
_links map[linkInfo]*link // *link is nil if connection in progress
}
"github.com/Arceliar/phony"
"github.com/RiV-chain/RiV-mesh/src/address"
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
)
// linkInfo is used as a map key
type linkInfo struct {
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
linkType string // Type of link, e.g. TCP, AWDL
local string // Local name or address
remote string // Remote name or address
}
type link struct {
lname string
links *links
conn *linkConn
options linkOptions
info linkInfo
incoming bool
force bool
lname string
links *links
conn *linkConn
options linkOptions
info linkInfo
incoming bool
force bool
}
type linkOptions struct {
pinnedEd25519Keys map[keyArray]struct{}
pinnedEd25519Keys map[keyArray]struct{}
priority uint8
}
type Listener struct {
net.Listener
closed chan struct{}
net.Listener
closed chan struct{}
}
func (l *Listener) Close() error {
err := l.Listener.Close()
<-l.closed
return err
}
func (l *links) init(c *Core) error {
l.core = c
l.tcp = l.newLinkTCP()
l.tls = l.newLinkTLS(l.tcp)
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l._links = make(map[linkInfo]*link)
var listeners []ListenAddress
phony.Block(c, func() {
listeners = make([]ListenAddress, 0, len(c.config._listeners))
for listener := range c.config._listeners {
listeners = append(listeners, listener)
}
})
return nil
err := l.Listener.Close()
<-l.closed
return err
}
func (l *links) shutdown() {
phony.Block(l.tcp, func() {
for l := range l.tcp._listeners {
_ = l.Close()
}
})
phony.Block(l.tls, func() {
for l := range l.tls._listeners {
_ = l.Close()
}
})
phony.Block(l.unix, func() {
for l := range l.unix._listeners {
_ = l.Close()
}
})
phony.Block(l.tcp, func() {
for l := range l.tcp._listeners {
_ = l.Close()
}
})
phony.Block(l.tls, func() {
for l := range l.tls._listeners {
_ = l.Close()
}
})
phony.Block(l.unix, func() {
for l := range l.unix._listeners {
_ = l.Close()
}
})
}
func (l *links) isConnectedTo(info linkInfo) bool {
var isConnected bool
phony.Block(l, func() {
_, isConnected = l._links[info]
})
return isConnected
}
func (l *links) call(u *url.URL, sintf string) (linkInfo, error) {
info := linkInfoFor(u.Scheme, sintf, u.Host)
if l.isConnectedTo(info) {
return info, nil
}
options := linkOptions{
pinnedEd25519Keys: map[keyArray]struct{}{},
}
for _, pubkey := range u.Query()["key"] {
sigPub, err := hex.DecodeString(pubkey)
if err != nil {
return info, fmt.Errorf("pinned key contains invalid hex characters")
}
var sigPubKey keyArray
copy(sigPubKey[:], sigPub)
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
}
if p := u.Query().Get("priority"); p != "" {
pi, err := strconv.ParseUint(p, 10, 8)
if err != nil {
return info, fmt.Errorf("priority invalid: %w", err)
}
options.priority = uint8(pi)
}
switch info.linkType {
case "tcp":
go func() {
if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err)
}
}()
case "socks":
go func() {
if err := l.socks.dial(u, options); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err)
}
}()
case "tls":
// SNI headers must contain hostnames and not IP addresses, so we must make sure
// that we do not populate the SNI with an IP literal. We do this by splitting
// the host-port combo from the query option and then seeing if it parses to an
// IP address successfully or not.
var tlsSNI string
if sni := u.Query().Get("sni"); sni != "" {
if net.ParseIP(sni) == nil {
tlsSNI = sni
}
}
// If the SNI is not configured still because the above failed then we'll try
// again but this time we'll use the host part of the peering URI instead.
if tlsSNI == "" {
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
tlsSNI = host
}
}
go func() {
if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err)
}
}()
case "unix":
go func() {
if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err)
}
}()
default:
return info, errors.New("unknown call scheme: " + u.Scheme)
}
return info, nil
}
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
var listener *Listener
var err error
switch u.Scheme {
case "tcp":
listener, err = l.tcp.listen(u, sintf)
case "tls":
listener, err = l.tls.listen(u, sintf)
case "unix":
listener, err = l.unix.listen(u, sintf)
default:
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
}
return listener, err
var isConnected bool
phony.Block(l, func() {
_, isConnected = l._links[info]
})
return isConnected
}
func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, force bool, options linkOptions) error {
@ -329,6 +211,9 @@ func (intf *link) close() error {
}
func linkInfoFor(linkType, sintf, remote string) linkInfo {
if h, _, err := net.SplitHostPort(remote); err == nil {
remote = h
}
return linkInfo{
linkType: linkType,
local: sintf,

159
src/core/link_linux.go Normal file
View file

@ -0,0 +1,159 @@
//go:build linux
// +build linux
package core
import (
"encoding/hex"
"errors"
"io"
"fmt"
"net"
"net/url"
"strconv"
"github.com/Arceliar/phony"
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
)
type links struct {
phony.Inbox
core *Core
tcp *linkTCP // TCP interface support
tls *linkTLS // TLS interface support
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
sctp *linkSCTP // SCTP interface support
mpath *linkMPATH // Multipath interface support
_links map[linkInfo]*link // *link is nil if connection in progress
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
func (l *links) init(c *Core) error {
l.core = c
l.tcp = l.newLinkTCP()
l.tls = l.newLinkTLS(l.tcp)
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l.sctp = l.newLinkSCTP()
l.mpath = l.newLinkMPATH()
l._links = make(map[linkInfo]*link)
var listeners []ListenAddress
phony.Block(c, func() {
listeners = make([]ListenAddress, 0, len(c.config._listeners))
for listener := range c.config._listeners {
listeners = append(listeners, listener)
}
})
return nil
}
func (l *links) call(u *url.URL, sintf string) (linkInfo, error) {
info := linkInfoFor(u.Scheme, sintf, u.Host)
if l.isConnectedTo(info) {
return info, nil
}
options := linkOptions{
pinnedEd25519Keys: map[keyArray]struct{}{},
}
for _, pubkey := range u.Query()["key"] {
sigPub, err := hex.DecodeString(pubkey)
if err != nil {
return info, fmt.Errorf("pinned key contains invalid hex characters")
}
var sigPubKey keyArray
copy(sigPubKey[:], sigPub)
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
}
if p := u.Query().Get("priority"); p != "" {
pi, err := strconv.ParseUint(p, 10, 8)
if err != nil {
return info, fmt.Errorf("priority invalid: %w", err)
}
options.priority = uint8(pi)
}
switch info.linkType {
case "tcp":
go func() {
if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err)
}
}()
case "socks":
go func() {
if err := l.socks.dial(u, options); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err)
}
}()
case "tls":
// SNI headers must contain hostnames and not IP addresses, so we must make sure
// that we do not populate the SNI with an IP literal. We do this by splitting
// the host-port combo from the query option and then seeing if it parses to an
// IP address successfully or not.
var tlsSNI string
if sni := u.Query().Get("sni"); sni != "" {
if net.ParseIP(sni) == nil {
tlsSNI = sni
}
}
// If the SNI is not configured still because the above failed then we'll try
// again but this time we'll use the host part of the peering URI instead.
if tlsSNI == "" {
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
tlsSNI = host
}
}
go func() {
if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err)
}
}()
case "unix":
go func() {
if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err)
}
}()
case "sctp":
go func() {
if err := l.sctp.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial SCTP %s: %s\n", u.Host, err)
}
}()
case "mpath":
go func() {
if err := l.mpath.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial MPATH %s: %s\n", u.Host, err)
}
}()
default:
return info, errors.New("unknown call scheme: " + u.Scheme)
}
return info, nil
}
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
var listener *Listener
var err error
switch u.Scheme {
case "tcp":
listener, err = l.tcp.listen(u, sintf)
case "tls":
listener, err = l.tls.listen(u, sintf)
case "unix":
listener, err = l.unix.listen(u, sintf)
case "sctp":
listener, err = l.sctp.listen(u, sintf)
case "mpath":
listener, err = l.mpath.listen(u, sintf)
default:
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
}
return listener, err
}

282
src/core/link_mpath.go Normal file
View file

@ -0,0 +1,282 @@
//go:build !android
// +build !android
package core
import (
"context"
"fmt"
"net"
"net/url"
"strings"
"github.com/getlantern/multipath"
"github.com/Arceliar/phony"
)
type linkMPATH struct {
phony.Inbox
*links
listener *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
}
func (l *links) newLinkMPATH() *linkMPATH {
lt := &linkMPATH{
links: l,
listener: &net.ListenConfig{
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
lt.listener.Control = lt.tcpContext
return lt
}
func (l *linkMPATH) dial(url *url.URL, options linkOptions, sintf string) error {
info := linkInfoFor("mpath", sintf, strings.SplitN(url.Host, "%", 2)[0])
conn, err := l.connFor(url, sintf)
if err != nil {
return err
}
uri := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
return l.handler(uri, info, conn, options, false, false)
}
func (l *linkMPATH) listen(url *url.URL, sintf string) (*Listener, error) {
hostport := url.Host
if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil {
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
}
}
_, cancel := context.WithCancel(l.core.ctx)
listener, err := l.listenFor(url, sintf)
if err != nil {
cancel()
return nil, err
}
entry := &Listener{
Listener: listener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("Multipath listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := listener.Accept()
if err != nil {
cancel()
break
}
addr := conn.RemoteAddr().(*net.TCPAddr)
name := fmt.Sprintf("mpath://%s", addr)
info := linkInfoFor("mpath", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0])
if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = listener.Close()
close(entry.closed)
l.core.log.Printf("Multipath listener stopped on %s", listener.Addr())
}()
return entry, nil
}
func (l *linkMPATH) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
return l.links.create(
conn, // connection
name, // connection name
info, // connection info
incoming, // not incoming
force, // not forced
options, // connection options
)
}
// Returns the address of the listener.
func (l *linkMPATH) getAddr() *net.TCPAddr {
// TODO: Fix this, because this will currently only give a single address
// to multicast.go, which obviously is not great, but right now multicast.go
// doesn't have the ability to send more than one address in a packet either
var addr *net.TCPAddr
phony.Block(l, func() {
for listener := range l._listeners {
addr = listener.Addr().(*net.TCPAddr)
}
})
return addr
}
func (l *linkMPATH) connFor(url *url.URL, sinterfaces string) (net.Conn, error) {
//Peer url has following format: mpath://host-1:port-1/host-2:port-2.../host-n:port-n
hosts := strings.Split(url.String(), "/")[2:]
remoteTargets := make([]net.Addr, 0)
for _, host := range hosts {
dst, err := net.ResolveTCPAddr("tcp", host)
info := linkInfoFor("tcp", sinterfaces, dst.String())
if l.links.isConnectedTo(info) {
continue
}
if err != nil {
l.core.log.Errorln("could not resolve host ", dst.String())
continue
}
if dst.IP.IsLinkLocalUnicast() {
dst.Zone = sinterfaces
if dst.Zone == "" {
l.core.log.Errorln("link-local address requires a zone in ", dst.String())
continue
}
}
remoteTargets = append(remoteTargets, dst)
}
if len(remoteTargets) == 0 {
return nil, fmt.Errorf("no valid target hosts given")
}
dialers := make([]multipath.Dialer, 0)
trackers := make([]multipath.StatsTracker, 0)
if sinterfaces != "" {
sintfarray := strings.Split(sinterfaces, ",")
for _, dst := range remoteTargets {
for _, sintf := range sintfarray {
ief, err := net.InterfaceByName(sintf)
if err != nil {
l.core.log.Errorln("interface %s not found", sintf)
continue
}
if ief.Flags&net.FlagUp == 0 {
l.core.log.Errorln("interface %s is not up", sintf)
continue
}
addrs, err := ief.Addrs()
if err != nil {
l.core.log.Errorln("interface %s addresses not available: %w", sintf, err)
continue
}
dstIp := dst.(*net.TCPAddr).IP
for addrindex, addr := range addrs {
src, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
continue
}
bothglobal := src.IsGlobalUnicast() == dstIp.IsGlobalUnicast()
bothlinklocal := src.IsLinkLocalUnicast() == dstIp.IsLinkLocalUnicast()
if !bothglobal && !bothlinklocal {
continue
}
if (src.To4() != nil) != (dstIp.To4() != nil) {
continue
}
if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
td := newOutboundDialer(src, dst)
dialers = append(dialers, td)
trackers = append(trackers, multipath.NullTracker{})
l.core.log.Printf("added outbound dialer for %s->%s", src.String(), dst.String())
break
}
}
}
}
} else {
star := net.ParseIP("0.0.0.0")
for _, dst := range remoteTargets {
td := newOutboundDialer(star, dst)
dialers = append(dialers, td)
trackers = append(trackers, multipath.NullTracker{})
l.core.log.Printf("added outbound dialer for %s", dst.String())
}
}
if len(dialers) == 0 {
return nil, fmt.Errorf("no suitable source address found on interface %q", sinterfaces)
}
dialer := multipath.NewDialer("mpath", dialers)
//conn, err := dialer.DialContext(l.core.ctx, "tcp", remoteTargets[0].String())
conn, err := dialer.DialContext(l.core.ctx)
if err != nil {
return nil, err
}
return conn, nil
}
func (l *linkMPATH) listenFor(url *url.URL, sintf string) (net.Listener, error) {
//Public node url has following format: mpath://ip-1:port-1/ip-2:port-2.../ip-n:port-n
hosts := strings.Split(url.String(), "/")[2:]
localTargets := make([]string, 0)
for _, host := range hosts {
dst, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
l.core.log.Errorln("could not resolve host ", dst.String())
continue
}
if dst.IP.IsLinkLocalUnicast() {
dst.Zone = sintf
if dst.Zone == "" {
l.core.log.Errorln("link-local address requires a zone in ", dst.String())
continue
}
}
localTargets = append(localTargets, host)
}
if len(localTargets) == 0 {
return nil, fmt.Errorf("no valid target hosts given")
}
listeners := make([]net.Listener, 0)
trackers := make([]multipath.StatsTracker, 0)
for _, lT := range localTargets {
l, err := l.listener.Listen(l.core.ctx, "tcp", lT)
if err != nil {
continue
}
listeners = append(listeners, l)
trackers = append(trackers, multipath.NullTracker{})
}
listener := multipath.NewListener(listeners, trackers)
return listener, nil
}
type targetedDailer struct {
localDialer net.Dialer
remoteAddr net.Addr
}
func newOutboundDialer(inputLocalAddr net.IP, inputRemoteAddr net.Addr) *targetedDailer {
td := &targetedDailer{
localDialer: net.Dialer{
LocalAddr: &net.TCPAddr{
IP: inputLocalAddr,
Port: 0,
},
},
remoteAddr: inputRemoteAddr,
}
return td
}
func (td *targetedDailer) DialContext(ctx context.Context) (net.Conn, error) {
conn, err := td.localDialer.DialContext(ctx, "tcp", td.remoteAddr.String())
if err != nil {
//l.core.log.Errorln("failed to dial to %v: %v", td.remoteAddr.String(), err)
return nil, err
}
//l.core.log.Printf("Dialed to %v->%v", conn.LocalAddr(), td.remoteAddr.String())
return conn, err
}
func (td *targetedDailer) Label() string {
return fmt.Sprintf("%s|%s", td.localDialer.LocalAddr, td.remoteAddr)
}

View file

@ -0,0 +1,270 @@
//go:build android
// +build android
package core
import (
"context"
"fmt"
"net"
"net/url"
"net/netip"
"strings"
"github.com/getlantern/multipath"
"github.com/Arceliar/phony"
)
type linkMPATH struct {
phony.Inbox
*links
listener *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
}
func (l *links) newLinkMPATH() *linkMPATH {
lt := &linkMPATH{
links: l,
listener: &net.ListenConfig{
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
lt.listener.Control = lt.tcpContext
return lt
}
func (l *linkMPATH) dial(url *url.URL, options linkOptions, sintf string) error {
info := linkInfoFor("mpath", sintf, strings.SplitN(url.Host, "%", 2)[0])
if l.links.isConnectedTo(info) {
return fmt.Errorf("duplicate connection attempt")
}
conn, err := l.connFor(url, sintf)
if err != nil {
return err
}
return l.handler(url.String(), info, conn, options, false)
}
func (l *linkMPATH) listen(url *url.URL, sintf string) (*Listener, error) {
hostport := url.Host
if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil {
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
}
}
_, cancel := context.WithCancel(l.core.ctx)
listener, err := l.listenFor(url, sintf)
if err != nil {
cancel()
return nil, err
}
entry := &Listener{
Listener: listener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("Multipath listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := listener.Accept()
if err != nil {
cancel()
break
}
addr := conn.RemoteAddr().(*net.TCPAddr)
name := fmt.Sprintf("mpath://%s", addr)
info := linkInfoFor("mpath", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0])
if err = l.handler(name, info, conn, linkOptions{}, true); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = listener.Close()
close(entry.closed)
l.core.log.Printf("Multipath listener stopped on %s", listener.Addr())
}()
return entry, nil
}
func (l *linkMPATH) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
return l.links.create(
conn, // connection
name, // connection name
info, // connection info
incoming, // not incoming
false, // not forced
options, // connection options
)
}
// Returns the address of the listener.
func (l *linkMPATH) getAddr() *net.TCPAddr {
// TODO: Fix this, because this will currently only give a single address
// to multicast.go, which obviously is not great, but right now multicast.go
// doesn't have the ability to send more than one address in a packet either
var addr *net.TCPAddr
phony.Block(l, func() {
for listener := range l._listeners {
addr = listener.Addr().(*net.TCPAddr)
}
})
return addr
}
func (l *linkMPATH) connFor(url *url.URL, sinterfaces string) (net.Conn, error) {
//Peer url has following format: mpath://host-1:port-1/host-2:port-2.../host-n:port-n
hosts := strings.Split(url.String(), "/")[2:]
remoteTargets := make([]net.Addr, 0)
for _, host := range hosts {
dst, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
l.core.log.Errorln("could not resolve host ", dst.String())
continue
}
if dst.IP.IsLinkLocalUnicast() {
dst.Zone = sinterfaces
if dst.Zone == "" {
l.core.log.Errorln("link-local address requires a zone in ", dst.String())
continue
}
}
remoteTargets = append(remoteTargets, dst)
}
if len(remoteTargets) == 0 {
return nil, fmt.Errorf("no valid target hosts given")
}
dialers := make([]multipath.Dialer, 0)
trackers := make([]multipath.StatsTracker, 0)
if sinterfaces != "" {
sintfarray := strings.Split(sinterfaces, ",")
for _, dst := range remoteTargets {
for _, sintf := range sintfarray {
addr, err := netip.ParseAddr(sintf)
if err != nil {
l.core.log.Errorln("interface %s address incorrect: %w", sintf, err)
continue
}
src := net.ParseIP(addr.WithZone("").String())
dstIp := dst.(*net.TCPAddr).IP
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
continue
}
bothglobal := src.IsGlobalUnicast() == dstIp.IsGlobalUnicast()
bothlinklocal := src.IsLinkLocalUnicast() == dstIp.IsLinkLocalUnicast()
if !bothglobal && !bothlinklocal {
continue
}
if (src.To4() != nil) != (dstIp.To4() != nil) {
continue
}
if bothglobal || bothlinklocal {
td := newOutboundDialer(src, dst)
dialers = append(dialers, td)
trackers = append(trackers, multipath.NullTracker{})
l.core.log.Printf("added outbound dialer for %s->%s", src.String(), dst.String())
}
}
}
} else {
star := net.ParseIP("0.0.0.0")
for _, dst := range remoteTargets {
td := newOutboundDialer(star, dst)
dialers = append(dialers, td)
trackers = append(trackers, multipath.NullTracker{})
l.core.log.Printf("added outbound dialer for %s", dst.String())
}
}
if len(dialers) == 0 {
return nil, fmt.Errorf("no suitable source address found on interface %q", sinterfaces)
}
dialer := multipath.NewDialer("mpath", dialers)
//conn, err := dialer.DialContext(l.core.ctx, "tcp", remoteTargets[0].String())
conn, err := dialer.DialContext(l.core.ctx)
if err != nil {
return nil, err
}
return conn, nil
}
func (l *linkMPATH) listenFor(url *url.URL, sintf string) (net.Listener, error) {
//Public node url has following format: mpath://ip-1:port-1/ip-2:port-2.../ip-n:port-n
hosts := strings.Split(url.String(), "/")[2:]
localTargets := make([]string, 0)
for _, host := range hosts {
dst, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
l.core.log.Errorln("could not resolve host ", dst.String())
continue
}
if dst.IP.IsLinkLocalUnicast() {
dst.Zone = sintf
if dst.Zone == "" {
l.core.log.Errorln("link-local address requires a zone in ", dst.String())
continue
}
}
localTargets = append(localTargets, host)
}
if len(localTargets) == 0 {
return nil, fmt.Errorf("no valid target hosts given")
}
listeners := make([]net.Listener, 0)
trackers := make([]multipath.StatsTracker, 0)
for _, lT := range localTargets {
l, err := l.listener.Listen(l.core.ctx, "tcp", lT)
if err != nil {
continue
}
listeners = append(listeners, l)
trackers = append(trackers, multipath.NullTracker{})
}
listener := multipath.NewListener(listeners, trackers)
return listener, nil
}
type targetedDailer struct {
localDialer net.Dialer
remoteAddr net.Addr
}
func newOutboundDialer(inputLocalAddr net.IP, inputRemoteAddr net.Addr) *targetedDailer {
td := &targetedDailer{
localDialer: net.Dialer{
LocalAddr: &net.TCPAddr{
IP: inputLocalAddr,
Port: 0,
},
},
remoteAddr: inputRemoteAddr,
}
return td
}
func (td *targetedDailer) DialContext(ctx context.Context) (net.Conn, error) {
conn, err := td.localDialer.DialContext(ctx, "tcp", td.remoteAddr.String())
if err != nil {
//l.core.log.Errorln("failed to dial to %v: %v", td.remoteAddr.String(), err)
return nil, err
}
//l.core.log.Printf("Dialed to %v->%v", conn.LocalAddr(), td.remoteAddr.String())
return conn, err
}
func (td *targetedDailer) Label() string {
return fmt.Sprintf("%s|%s", td.localDialer.LocalAddr, td.remoteAddr)
}

View file

@ -0,0 +1,33 @@
//go:build darwin
// +build darwin
package core
import (
"syscall"
"golang.org/x/sys/unix"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var recvanyif error
control = c.Control(func(fd uintptr) {
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
})
switch {
case recvanyif != nil:
return recvanyif
default:
return control
}
}
func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error {
return t.tcpContext
}

View file

@ -0,0 +1,46 @@
//go:build linux
// +build linux
package core
import (
"syscall"
"golang.org/x/sys/unix"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var bbr error
control = c.Control(func(fd uintptr) {
bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr")
})
// Log any errors
if bbr != nil {
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr)
}
if control != nil {
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control)
}
// Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal
return nil
}
func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error {
return func(network, address string, c syscall.RawConn) error {
var err error
btd := func(fd uintptr) {
err = unix.BindToDevice(int(fd), sintf)
}
_ = c.Control(btd)
if err != nil {
t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf)
}
return t.tcpContext(network, address, c)
}
}

View file

@ -0,0 +1,18 @@
//go:build !darwin && !linux
// +build !darwin,!linux
package core
import (
"syscall"
)
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *linkMPATH) tcpContext(network, address string, c syscall.RawConn) error {
return nil
}
func (t *linkMPATH) getControl(sintf string) func(string, string, syscall.RawConn) error {
return t.tcpContext
}

147
src/core/link_other.go Normal file
View file

@ -0,0 +1,147 @@
//go:build !linux
// +build !linux
package core
import (
"encoding/hex"
"errors"
"fmt"
"net"
"net/url"
"github.com/Arceliar/phony"
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
)
type links struct {
phony.Inbox
core *Core
tcp *linkTCP // TCP interface support
tls *linkTLS // TLS interface support
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
mpath *linkMPATH // Multipath interface support
_links map[linkInfo]*link // *link is nil if connection in progress
// TODO timeout (to remove from switch), read from config.ReadTimeout
}
func (l *links) init(c *Core) error {
l.core = c
l.tcp = l.newLinkTCP()
l.tls = l.newLinkTLS(l.tcp)
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l.mpath = l.newLinkMPATH()
l._links = make(map[linkInfo]*link)
var listeners []ListenAddress
phony.Block(c, func() {
listeners = make([]ListenAddress, 0, len(c.config._listeners))
for listener := range c.config._listeners {
listeners = append(listeners, listener)
}
})
return nil
}
func (l *links) call(u *url.URL, sintf string) (linkInfo, error) {
info := linkInfoFor(u.Scheme, sintf, u.Host)
if l.isConnectedTo(info) {
return nil
}
options := linkOptions{
pinnedEd25519Keys: map[keyArray]struct{}{},
}
for _, pubkey := range u.Query()["key"] {
sigPub, err := hex.DecodeString(pubkey)
if err != nil {
return fmt.Errorf("pinned key contains invalid hex characters")
}
var sigPubKey keyArray
copy(sigPubKey[:], sigPub)
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
}
if p := u.Query().Get("priority"); p != "" {
pi, err := strconv.ParseUint(p, 10, 8)
if err != nil {
return info, fmt.Errorf("priority invalid: %w", err)
}
options.priority = uint8(pi)
switch info.linkType {
case "tcp":
go func() {
if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF
l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err)
}
}()
case "socks":
go func() {
if err := l.socks.dial(u, options); err != nil && err != io.EOF
l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err)
}
}()
case "tls":
// SNI headers must contain hostnames and not IP addresses, so we must make sure
// that we do not populate the SNI with an IP literal. We do this by splitting
// the host-port combo from the query option and then seeing if it parses to an
// IP address successfully or not.
var tlsSNI string
if sni := u.Query().Get("sni"); sni != "" {
if net.ParseIP(sni) == nil {
tlsSNI = sni
}
}
// If the SNI is not configured still because the above failed then we'll try
// again but this time we'll use the host part of the peering URI instead.
if tlsSNI == "" {
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
tlsSNI = host
}
}
go func() {
if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF
l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err)
}
}()
case "unix":
go func() {
if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF {
if err := l.unix.dial(u, options, sintf); err != nil {
l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err)
}
}()
case "mpath":
go func() {
if err := l.mpath.dial(u, options, sintf); err != nil && err != io.EOF {
l.core.log.Warnf("Failed to dial MPATH %s: %s\n", u.Host, err)
}
}()
default:
return errors.New("unknown call scheme: " + u.Scheme)
}
return nil
}
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
var listener *Listener
var err error
switch u.Scheme {
case "tcp":
listener, err = l.tcp.listen(u, sintf)
case "tls":
listener, err = l.tls.listen(u, sintf)
case "unix":
listener, err = l.unix.listen(u, sintf)
case "mpath":
listener, err = l.mpath.listen(u, sintf)
default:
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
}
return listener, err
}

176
src/core/link_sctp_linux.go Normal file
View file

@ -0,0 +1,176 @@
//go:build linux
// +build linux
package core
import (
"context"
"fmt"
"net"
"net/url"
"encoding/json"
"strings"
"strconv"
"github.com/Arceliar/phony"
sctp "github.com/vikulin/sctp"
)
type linkSCTP struct {
phony.Inbox
*links
listener *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
}
func (l *links) newLinkSCTP() *linkSCTP {
lt := &linkSCTP{
links: l,
listener: &net.ListenConfig{
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
return lt
}
func (l *linkSCTP) dial(url *url.URL, options linkOptions, sintf string) error {
info := linkInfoFor("sctp", sintf, strings.SplitN(url.Host, "%", 2)[0])
if l.links.isConnectedTo(info) {
return nil
}
host, port, err := net.SplitHostPort(url.Host)
if err != nil {
return err
}
dst, err := net.ResolveIPAddr("ip", host)
if err != nil {
return err
}
raddress := l.getAddress(dst.String()+":"+port)
var conn net.Conn
laddress := l.getAddress("0.0.0.0:0")
conn, err = sctp.NewSCTPConnection(laddress, laddress.AddressFamily, sctp.InitMsg{NumOstreams: 2, MaxInstreams: 2, MaxAttempts: 2, MaxInitTimeout: 5}, sctp.OneToOne, false)
if err != nil {
return err
}
err = conn.(*sctp.SCTPConn).Connect(raddress)
//conn.(*sctp.SCTPConn).SetWriteBuffer(324288)
//conn.(*sctp.SCTPConn).SetReadBuffer(324288)
//wbuf, _ := conn.(*sctp.SCTPConn).GetWriteBuffer()
//rbuf, _ := conn.(*sctp.SCTPConn).GetReadBuffer()
//l.core.log.Printf("Read buffer %d", rbuf)
//l.core.log.Printf("Write buffer %d", wbuf)
conn.(*sctp.SCTPConn).SetEvents(sctp.SCTP_EVENT_DATA_IO)
return l.handler(url.String(), info, conn, options, false)
}
func (l *linkSCTP) listen(url *url.URL, sintf string) (*Listener, error) {
//_, cancel := context.WithCancel(l.core.ctx)
hostport := url.Host
if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil {
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
}
}
addr := l.getAddress(url.Host)
listener, err := sctp.NewSCTPListener(addr, sctp.InitMsg{NumOstreams: 2, MaxInstreams: 2, MaxAttempts: 2, MaxInitTimeout: 5}, sctp.OneToOne, false)
if err != nil {
//cancel()
return nil, err
}
listener.SetEvents(sctp.SCTP_EVENT_DATA_IO)
entry := &Listener{
Listener: listener,
closed: make(chan struct{}),
}
//phony.Block(l, func() {
// l._listeners[entry] = cancel
//})
l.core.log.Printf("SCTP listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := listener.Accept()
if err != nil {
//cancel()
break
}
addr := conn.RemoteAddr().(*sctp.SCTPAddr)
ips, err := json.Marshal(addr.IPAddrs)
if err != nil {
break
}
name := fmt.Sprintf("sctp://%s", ips)
info := linkInfoFor("sctp", sintf, string(ips))
//conn.(*sctp.SCTPConn).SetWriteBuffer(324288)
//conn.(*sctp.SCTPConn).SetReadBuffer(324288)
wbuf, _ := conn.(*sctp.SCTPConn).GetWriteBuffer()
rbuf, _ := conn.(*sctp.SCTPConn).GetReadBuffer()
l.core.log.Printf("Read buffer %d", rbuf)
l.core.log.Printf("Write buffer %d", wbuf)
if err = l.handler(name, info, conn, linkOptions{}, true); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = listener.Close()
close(entry.closed)
l.core.log.Printf("SCTP listener stopped on %s", listener.Addr())
}()
return entry, nil
}
func (l *linkSCTP) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
return l.links.create(
conn, // connection
name, // connection name
info, // connection info
incoming, // not incoming
false, // not forced
options, // connection options
)
}
// Returns the address of the listener.
func (l *linkSCTP) getAddr() *net.TCPAddr {
// TODO: Fix this, because this will currently only give a single address
// to multicast.go, which obviously is not great, but right now multicast.go
// doesn't have the ability to send more than one address in a packet either
var addr *net.TCPAddr
phony.Block(l, func() {
for listener := range l._listeners {
addr = listener.Addr().(*net.TCPAddr)
}
})
return addr
}
//SCTP infrastructure
func (l *linkSCTP) getAddress(host string) *sctp.SCTPAddr {
//sctp supports multihoming but current implementation reuires only one path
ips := []net.IPAddr{}
ip, port, err := net.SplitHostPort(host)
if err != nil {
panic(err)
}
for _, i := range strings.Split(ip, ",") {
if a, err := net.ResolveIPAddr("ip", i); err == nil {
fmt.Sprintf("Resolved address '%s' to %s", i, a)
ips = append(ips, *a)
} else {
l.core.log.Errorln("Error resolving address '%s': %v", i, err)
}
}
p, _ := strconv.Atoi(port)
addr := &sctp.SCTPAddr{
IPAddrs: ips,
Port: p,
}
return addr
}

View file

@ -181,6 +181,7 @@ func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error)
return dialer, nil
}
func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string {
if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) {
// Nodes running on the same host — include both the IP and port.

View file

@ -10,7 +10,10 @@ import (
iwt "github.com/Arceliar/ironwood/types"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/version"
//"github.com/RiV-chain/RiV-mesh/src/crypto"
"github.com/RiV-chain/RiV-mesh/src/version"
)
type nodeinfo struct {

View file

@ -12,7 +12,7 @@ import (
iwt "github.com/Arceliar/ironwood/types"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/RiV-chain/RiV-mesh/src/address"
)
const (

View file

@ -20,7 +20,7 @@ type version_metadata struct {
// Gets a base metadata with no keys set, but with the correct version numbers.
func version_getBaseMetadata() version_metadata {
return version_metadata{
meta: [4]byte{'m', 'e', 't', 'a'},
meta: [4]byte{'z', 'e', 't', 'a'},
ver: 0,
minorVer: 4,
}

View file

@ -1,6 +1,6 @@
package defaults
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
import "github.com/RiV-chain/RiV-mesh/src/config"
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
@ -14,7 +14,7 @@ type platformDefaultParameters struct {
// Admin socket
DefaultAdminListen string
// Configuration (used for yggdrasilctl)
// Configuration (used for meshctl)
DefaultConfigFile string
// Multicast interfaces

View file

@ -8,10 +8,10 @@ package defaults
func getDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "/etc/mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{

View file

@ -8,10 +8,10 @@ package defaults
func getDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/usr/local/etc/yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "/usr/local/etc/mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{

View file

@ -8,10 +8,10 @@ package defaults
func getDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "/etc/mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{

View file

@ -8,10 +8,10 @@ package defaults
func getDefaults() platformDefaultParameters {
return platformDefaultParameters{
// Admin
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "/etc/mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{

View file

@ -10,8 +10,8 @@ func getDefaults() platformDefaultParameters {
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "/etc/yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "/etc/mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{

View file

@ -10,8 +10,8 @@ func getDefaults() platformDefaultParameters {
// Admin
DefaultAdminListen: "tcp://localhost:9001",
// Configuration (used for yggdrasilctl)
DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf",
// Configuration (used for meshctl)
DefaultConfigFile: "C:\\Program Files\\RiV-mesh\\mesh.conf",
// Multicast interfaces
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
@ -21,6 +21,6 @@ func getDefaults() platformDefaultParameters {
// TUN
MaximumIfMTU: 65535,
DefaultIfMTU: 65535,
DefaultIfName: "Yggdrasil",
DefaultIfName: "RiV-mesh",
}
}

View file

@ -13,8 +13,8 @@ import (
iwt "github.com/Arceliar/ironwood/types"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/RiV-chain/RiV-mesh/src/address"
"github.com/RiV-chain/RiV-mesh/src/core"
)
const keyStoreTimeout = 2 * time.Minute

View file

@ -3,7 +3,7 @@ package multicast
import (
"encoding/json"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/RiV-chain/RiV-mesh/src/admin"
)
type GetMulticastInterfacesRequest struct{}

View file

@ -14,13 +14,13 @@ import (
"github.com/Arceliar/phony"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/RiV-chain/RiV-mesh/src/core"
"golang.org/x/net/ipv6"
)
// Multicast represents the multicast advertisement and discovery mechanism used
// by Yggdrasil to find peers on the same subnet. When a beacon is received on a
// configured multicast interface, Yggdrasil will attempt to peer with that node
// by RiV-mesh to find peers on the same subnet. When a beacon is received on a
// configured multicast interface, RiV-mesh will attempt to peer with that node
// automatically.
type Multicast struct {
phony.Inbox
@ -195,11 +195,10 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
continue
}
interfaces[iface.Name] = &interfaceInfo{
iface: iface,
beacon: ifcfg.Beacon,
listen: ifcfg.Listen,
port: ifcfg.Port,
priority: ifcfg.Priority,
iface: iface,
beacon: ifcfg.Beacon,
listen: ifcfg.Listen,
port: ifcfg.Port,
}
break
}

View file

@ -4,6 +4,7 @@
package multicast
import (
"syscall"

View file

@ -4,6 +4,8 @@
package multicast
import (
"fmt"
"os"
"syscall"
"golang.org/x/sys/unix"
@ -18,17 +20,13 @@ func (m *Multicast) multicastReuse(network string, address string, c syscall.Raw
var reuseaddr error
control = c.Control(func(fd uintptr) {
// Previously we used SO_REUSEPORT here, but that meant that machines running
// Yggdrasil nodes as different users would inevitably fail with EADDRINUSE.
// The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR
// instead.
reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
// Previously we used SO_REUSEPORT here, but that meant that machines running
// RiV-mesh nodes as different users would inevitably fail with EADDRINUSE.
// The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR
// instead.
if reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); reuseaddr != nil {
fmt.Fprintf(os.Stderr, "Failed to set SO_REUSEADDR on socket: %s\n", reuseaddr)
}
})
switch {
case reuseaddr != nil:
return reuseaddr
default:
return control
}
return control
}

View file

@ -3,7 +3,7 @@ package tun
import (
"encoding/json"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/RiV-chain/RiV-mesh/src/admin"
)
type GetTUNRequest struct{}

View file

@ -13,18 +13,18 @@ import (
"github.com/Arceliar/phony"
"golang.zx2c4.com/wireguard/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
"github.com/RiV-chain/RiV-mesh/src/address"
"github.com/RiV-chain/RiV-mesh/src/core"
"github.com/RiV-chain/RiV-mesh/src/defaults"
"github.com/RiV-chain/RiV-mesh/src/ipv6rwc"
)
type MTU uint16
// TunAdapter represents a running TUN interface and extends the
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
// should pass this object to the yggdrasil.SetRouterAdapter() function before
// calling yggdrasil.Start().
// mesh.Adapter type. In order to use the TUN adapter with Mesh, you
// should pass this object to the mesh.SetRouterAdapter() function before
// calling mesh.Start().
type TunAdapter struct {
rwc *ipv6rwc.ReadWriteCloser
log core.Logger
@ -89,7 +89,7 @@ func MaximumMTU() uint64 {
}
// Init initialises the TUN module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
// the Mesh core before this point and it must not be in use elsewhere.
func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
tun := &TunAdapter{
rwc: rwc,

View file

@ -6,10 +6,11 @@ package tun
import (
"bytes"
"errors"
"time"
"log"
"net"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/RiV-chain/RiV-mesh/src/defaults"
"golang.org/x/sys/windows"
wgtun "golang.zx2c4.com/wireguard/tun"
@ -28,16 +29,25 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
var err error
var iface wgtun.Device
var guid windows.GUID
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
if guid, err = windows.GUIDFromString("{f1369c05-0344-40ed-a772-bfb4770abdd0}"); err != nil {
return err
}
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil {
return err
}
tun.iface = iface
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
for i := 1; i < 10; i++ {
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
log.Printf("waiting...")
if i > 8 {
return err
} else {
time.Sleep(time.Duration(2 * i) * time.Second)
}
} else {
break
}
}
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
tun.log.Errorln("Failed to set up TUN MTU:", err)