mirror of
				https://github.com/yggdrasil-network/yggdrasil-go.git
				synced 2025-11-04 11:15:07 +03:00 
			
		
		
		
	
						commit
						10d3c618c8
					
				
					 24 changed files with 135 additions and 84 deletions
				
			
		| 
						 | 
					@ -19,7 +19,7 @@ import (
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						"github.com/RiV-chain/RiV-mesh/src/core"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type keySet struct {
 | 
					type keySet struct {
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,7 @@ func main() {
 | 
				
			||||||
			fmt.Println("-----")
 | 
								fmt.Println("-----")
 | 
				
			||||||
			fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
 | 
								fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
 | 
				
			||||||
			fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
 | 
								fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
 | 
				
			||||||
			addr := address.AddrForKey(newKey.pub)
 | 
								addr := core.AddrForKey(newKey.pub)
 | 
				
			||||||
			fmt.Println("IP:", net.IP(addr[:]).String())
 | 
								fmt.Println("IP:", net.IP(addr[:]).String())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,13 +25,13 @@ import (
 | 
				
			||||||
	"github.com/kardianos/minwinsvc"
 | 
						"github.com/kardianos/minwinsvc"
 | 
				
			||||||
	"github.com/mitchellh/mapstructure"
 | 
						"github.com/mitchellh/mapstructure"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/admin"
 | 
						"github.com/RiV-chain/RiV-mesh/src/admin"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/config"
 | 
						"github.com/RiV-chain/RiV-mesh/src/config"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
						"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/core"
 | 
						"github.com/RiV-chain/RiV-mesh/src/core"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/ipv6rwc"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/ipv6rwc"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/multicast"
 | 
						"github.com/RiV-chain/RiV-mesh/src/multicast"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/tun"
 | 
						"github.com/RiV-chain/RiV-mesh/src/tun"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/version"
 | 
						"github.com/RiV-chain/RiV-mesh/src/version"
 | 
				
			||||||
| 
						 | 
					@ -261,6 +261,7 @@ func run(args yggArgs, ctx context.Context) {
 | 
				
			||||||
	if cfg == nil {
 | 
						if cfg == nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						n := &node{}
 | 
				
			||||||
	// Have we been asked for the node address yet? If so, print it and then stop.
 | 
						// Have we been asked for the node address yet? If so, print it and then stop.
 | 
				
			||||||
	getNodeKey := func() ed25519.PublicKey {
 | 
						getNodeKey := func() ed25519.PublicKey {
 | 
				
			||||||
		if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
 | 
							if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
 | 
				
			||||||
| 
						 | 
					@ -271,14 +272,14 @@ func run(args yggArgs, ctx context.Context) {
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case args.getaddr:
 | 
						case args.getaddr:
 | 
				
			||||||
		if key := getNodeKey(); key != nil {
 | 
							if key := getNodeKey(); key != nil {
 | 
				
			||||||
			addr := address.AddrForKey(key)
 | 
								addr := n.core.AddrForKey(key)
 | 
				
			||||||
			ip := net.IP(addr[:])
 | 
								ip := net.IP(addr[:])
 | 
				
			||||||
			fmt.Println(ip.String())
 | 
								fmt.Println(ip.String())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	case args.getsnet:
 | 
						case args.getsnet:
 | 
				
			||||||
		if key := getNodeKey(); key != nil {
 | 
							if key := getNodeKey(); key != nil {
 | 
				
			||||||
			snet := address.SubnetForKey(key)
 | 
								snet := n.core.SubnetForKey(key)
 | 
				
			||||||
			ipnet := net.IPNet{
 | 
								ipnet := net.IPNet{
 | 
				
			||||||
				IP:   append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
 | 
									IP:   append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
 | 
				
			||||||
				Mask: net.CIDRMask(len(snet)*8, 128),
 | 
									Mask: net.CIDRMask(len(snet)*8, 128),
 | 
				
			||||||
| 
						 | 
					@ -291,7 +292,6 @@ func run(args yggArgs, ctx context.Context) {
 | 
				
			||||||
	cfg.HttpAddress = args.httpaddress
 | 
						cfg.HttpAddress = args.httpaddress
 | 
				
			||||||
	cfg.WwwRoot = args.wwwroot
 | 
						cfg.WwwRoot = args.wwwroot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	n := &node{}
 | 
					 | 
				
			||||||
	// Setup the RiV-mesh node itself.
 | 
						// Setup the RiV-mesh node itself.
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		sk, err := hex.DecodeString(cfg.PrivateKey)
 | 
							sk, err := hex.DecodeString(cfg.PrivateKey)
 | 
				
			||||||
| 
						 | 
					@ -301,6 +301,7 @@ func run(args yggArgs, ctx context.Context) {
 | 
				
			||||||
		options := []core.SetupOption{
 | 
							options := []core.SetupOption{
 | 
				
			||||||
			core.NodeInfo(cfg.NodeInfo),
 | 
								core.NodeInfo(cfg.NodeInfo),
 | 
				
			||||||
			core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
 | 
								core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
 | 
				
			||||||
 | 
								core.NetworkDomain(cfg.NetworkDomain),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, addr := range cfg.Listen {
 | 
							for _, addr := range cfg.Listen {
 | 
				
			||||||
			options = append(options, core.ListenAddress(addr))
 | 
								options = append(options, core.ListenAddress(addr))
 | 
				
			||||||
| 
						 | 
					@ -364,7 +365,7 @@ func run(args yggArgs, ctx context.Context) {
 | 
				
			||||||
			tun.InterfaceName(cfg.IfName),
 | 
								tun.InterfaceName(cfg.IfName),
 | 
				
			||||||
			tun.InterfaceMTU(cfg.IfMTU),
 | 
								tun.InterfaceMTU(cfg.IfMTU),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
 | 
							if n.tun, err = tun.New(n.core, logger, options...); err != nil {
 | 
				
			||||||
			panic(err)
 | 
								panic(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if n.admin != nil && n.tun != nil {
 | 
							if n.admin != nil && n.tun != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gologme/log"
 | 
						"github.com/gologme/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/config"
 | 
						"github.com/RiV-chain/RiV-mesh/src/config"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/core"
 | 
						"github.com/RiV-chain/RiV-mesh/src/core"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
						"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
				
			||||||
| 
						 | 
					@ -176,7 +176,7 @@ func (m *Mesh) GetPeersJSON() (result string) {
 | 
				
			||||||
		IP string
 | 
							IP string
 | 
				
			||||||
	}{}
 | 
						}{}
 | 
				
			||||||
	for _, v := range m.core.GetPeers() {
 | 
						for _, v := range m.core.GetPeers() {
 | 
				
			||||||
		a := address.AddrForKey(v.Key)
 | 
							a := m.core.AddrForKey(v.Key)
 | 
				
			||||||
		ip := net.IP(a[:]).String()
 | 
							ip := net.IP(a[:]).String()
 | 
				
			||||||
		peers = append(peers, struct {
 | 
							peers = append(peers, struct {
 | 
				
			||||||
			core.PeerInfo
 | 
								core.PeerInfo
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ import (
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetDHTRequest struct{}
 | 
					type GetDHTRequest struct{}
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) err
 | 
				
			||||||
	dht := a.core.GetDHT()
 | 
						dht := a.core.GetDHT()
 | 
				
			||||||
	res.DHT = make([]DHTEntry, 0, len(dht))
 | 
						res.DHT = make([]DHTEntry, 0, len(dht))
 | 
				
			||||||
	for _, d := range dht {
 | 
						for _, d := range dht {
 | 
				
			||||||
		addr := address.AddrForKey(d.Key)
 | 
							addr := a.core.AddrForKey(d.Key)
 | 
				
			||||||
		res.DHT = append(res.DHT, DHTEntry{
 | 
							res.DHT = append(res.DHT, DHTEntry{
 | 
				
			||||||
			IPAddress: net.IP(addr[:]).String(),
 | 
								IPAddress: net.IP(addr[:]).String(),
 | 
				
			||||||
			PublicKey: hex.EncodeToString(d.Key[:]),
 | 
								PublicKey: hex.EncodeToString(d.Key[:]),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ import (
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetPathsRequest struct {
 | 
					type GetPathsRequest struct {
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsRespons
 | 
				
			||||||
	paths := a.core.GetPaths()
 | 
						paths := a.core.GetPaths()
 | 
				
			||||||
	res.Paths = make([]PathEntry, 0, len(paths))
 | 
						res.Paths = make([]PathEntry, 0, len(paths))
 | 
				
			||||||
	for _, p := range paths {
 | 
						for _, p := range paths {
 | 
				
			||||||
		addr := address.AddrForKey(p.Key)
 | 
							addr := a.core.AddrForKey(p.Key)
 | 
				
			||||||
		res.Paths = append(res.Paths, PathEntry{
 | 
							res.Paths = append(res.Paths, PathEntry{
 | 
				
			||||||
			IPAddress: net.IP(addr[:]).String(),
 | 
								IPAddress: net.IP(addr[:]).String(),
 | 
				
			||||||
			PublicKey: hex.EncodeToString(p.Key),
 | 
								PublicKey: hex.EncodeToString(p.Key),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import (
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetPeersRequest struct {
 | 
					type GetPeersRequest struct {
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons
 | 
				
			||||||
	peers := a.core.GetPeers()
 | 
						peers := a.core.GetPeers()
 | 
				
			||||||
	res.Peers = make([]PeerEntry, 0, len(peers))
 | 
						res.Peers = make([]PeerEntry, 0, len(peers))
 | 
				
			||||||
	for _, p := range peers {
 | 
						for _, p := range peers {
 | 
				
			||||||
		addr := address.AddrForKey(p.Key)
 | 
							addr := a.core.AddrForKey(p.Key)
 | 
				
			||||||
		res.Peers = append(res.Peers, PeerEntry{
 | 
							res.Peers = append(res.Peers, PeerEntry{
 | 
				
			||||||
			IPAddress: net.IP(addr[:]).String(),
 | 
								IPAddress: net.IP(addr[:]).String(),
 | 
				
			||||||
			PublicKey: hex.EncodeToString(p.Key),
 | 
								PublicKey: hex.EncodeToString(p.Key),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ import (
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GetSessionsRequest struct{}
 | 
					type GetSessionsRequest struct{}
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessio
 | 
				
			||||||
	sessions := a.core.GetSessions()
 | 
						sessions := a.core.GetSessions()
 | 
				
			||||||
	res.Sessions = make([]SessionEntry, 0, len(sessions))
 | 
						res.Sessions = make([]SessionEntry, 0, len(sessions))
 | 
				
			||||||
	for _, s := range sessions {
 | 
						for _, s := range sessions {
 | 
				
			||||||
		addr := address.AddrForKey(s.Key)
 | 
							addr := a.core.AddrForKey(s.Key)
 | 
				
			||||||
		res.Sessions = append(res.Sessions, SessionEntry{
 | 
							res.Sessions = append(res.Sessions, SessionEntry{
 | 
				
			||||||
			IPAddress: net.IP(addr[:]).String(),
 | 
								IPAddress: net.IP(addr[:]).String(),
 | 
				
			||||||
			PublicKey: hex.EncodeToString(s.Key[:]),
 | 
								PublicKey: hex.EncodeToString(s.Key[:]),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,7 @@ type NodeConfig struct {
 | 
				
			||||||
	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."`
 | 
						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 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."`
 | 
						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."`
 | 
						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."`
 | 
				
			||||||
 | 
						NetworkDomain       NetworkDomainConfig        `comment:"Address prefix used by mesh.\nThe current implementation requires this to be a multiple of 8 bits + 7 bits.4\nNodes that configure this differently will be unable to communicate with each other using IP packets."`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MulticastInterfaceConfig struct {
 | 
					type MulticastInterfaceConfig struct {
 | 
				
			||||||
| 
						 | 
					@ -49,6 +50,10 @@ type MulticastInterfaceConfig struct {
 | 
				
			||||||
	Priority uint8
 | 
						Priority uint8
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type NetworkDomainConfig struct {
 | 
				
			||||||
 | 
					        Prefix [1]byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
 | 
					// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
 | 
				
			||||||
// signing keypair. The signing keys are used by the switch to derive the
 | 
					// signing keypair. The signing keys are used by the switch to derive the
 | 
				
			||||||
// structure of the spanning tree.
 | 
					// structure of the spanning tree.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
// Package address contains the types used by mesh 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.
 | 
					// 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
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/ed25519"
 | 
						"crypto/ed25519"
 | 
				
			||||||
| 
						 | 
					@ -16,15 +16,15 @@ type Subnet [8]byte
 | 
				
			||||||
// The current implementation requires this to be a multiple of 8 bits + 7 bits.
 | 
					// 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).
 | 
					// 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.
 | 
					// 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 {
 | 
					func (c *Core) GetPrefix() [1]byte {
 | 
				
			||||||
	return [...]byte{0xfc}
 | 
						return c.config.networkdomain.Prefix
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsValid returns true if an address falls within the range used by nodes in the network.
 | 
					// IsValid returns true if an address falls within the range used by nodes in the network.
 | 
				
			||||||
func (a *Address) IsValid() bool {
 | 
					func (c *Core) IsValidAddress(a Address) bool {
 | 
				
			||||||
	prefix := GetPrefix()
 | 
						prefix := c.GetPrefix()
 | 
				
			||||||
	for idx := range prefix {
 | 
						for idx := range prefix {
 | 
				
			||||||
		if (*a)[idx] != prefix[idx] {
 | 
							if a[idx] != prefix[idx] {
 | 
				
			||||||
			return false
 | 
								return false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -32,15 +32,15 @@ func (a *Address) IsValid() bool {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsValid returns true if a prefix falls within the range usable by the network.
 | 
					// IsValid returns true if a prefix falls within the range usable by the network.
 | 
				
			||||||
func (s *Subnet) IsValid() bool {
 | 
					func (c *Core) IsValidSubnet(s Subnet) bool {
 | 
				
			||||||
	prefix := GetPrefix()
 | 
						prefix := c.GetPrefix()
 | 
				
			||||||
	l := len(prefix)
 | 
						l := len(prefix)
 | 
				
			||||||
	for idx := range prefix[:l-1] {
 | 
						for idx := range prefix[:l-1] {
 | 
				
			||||||
		if (*s)[idx] != prefix[idx] {
 | 
							if s[idx] != prefix[idx] {
 | 
				
			||||||
			return false
 | 
								return false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return (*s)[l-1] == prefix[l-1]|0x01
 | 
						return s[l-1] == prefix[l-1]|0x01
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AddrForKey takes an ed25519.PublicKey as an argument and returns an *Address.
 | 
					// AddrForKey takes an ed25519.PublicKey as an argument and returns an *Address.
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ func (s *Subnet) IsValid() bool {
 | 
				
			||||||
// This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address.
 | 
					// This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address.
 | 
				
			||||||
// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the public key.
 | 
					// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the public key.
 | 
				
			||||||
// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address.
 | 
					// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address.
 | 
				
			||||||
func AddrForKey(publicKey ed25519.PublicKey) *Address {
 | 
					func (c *Core) AddrForKey(publicKey ed25519.PublicKey) *Address {
 | 
				
			||||||
	// 128 bit address
 | 
						// 128 bit address
 | 
				
			||||||
	// Begins with prefix
 | 
						// Begins with prefix
 | 
				
			||||||
	// Next bit is a 0
 | 
						// Next bit is a 0
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@ func AddrForKey(publicKey ed25519.PublicKey) *Address {
 | 
				
			||||||
			temp = append(temp, bits)
 | 
								temp = append(temp, bits)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	prefix := GetPrefix()
 | 
						prefix := c.GetPrefix()
 | 
				
			||||||
	copy(addr[:], prefix[:])
 | 
						copy(addr[:], prefix[:])
 | 
				
			||||||
	addr[len(prefix)] = ones
 | 
						addr[len(prefix)] = ones
 | 
				
			||||||
	copy(addr[len(prefix)+1:], temp)
 | 
						copy(addr[len(prefix)+1:], temp)
 | 
				
			||||||
| 
						 | 
					@ -98,26 +98,26 @@ func AddrForKey(publicKey ed25519.PublicKey) *Address {
 | 
				
			||||||
// The subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix.
 | 
					// The subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix.
 | 
				
			||||||
// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the key.
 | 
					// The following 8 bits are set to the number of leading 1 bits in the bitwise inverse of the key.
 | 
				
			||||||
// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet.
 | 
					// The bitwise inverse of the key, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet.
 | 
				
			||||||
func SubnetForKey(publicKey ed25519.PublicKey) *Subnet {
 | 
					func (c *Core) SubnetForKey(publicKey ed25519.PublicKey) *Subnet {
 | 
				
			||||||
	// Exactly as the address version, with two exceptions:
 | 
						// Exactly as the address version, with two exceptions:
 | 
				
			||||||
	//  1) The first bit after the fixed prefix is a 1 instead of a 0
 | 
						//  1) The first bit after the fixed prefix is a 1 instead of a 0
 | 
				
			||||||
	//  2) It's truncated to a subnet prefix length instead of 128 bits
 | 
						//  2) It's truncated to a subnet prefix length instead of 128 bits
 | 
				
			||||||
	addr := AddrForKey(publicKey)
 | 
						addr := c.AddrForKey(publicKey)
 | 
				
			||||||
	if addr == nil {
 | 
						if addr == nil {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var snet Subnet
 | 
						var snet Subnet
 | 
				
			||||||
	copy(snet[:], addr[:])
 | 
						copy(snet[:], addr[:])
 | 
				
			||||||
	prefix := GetPrefix() // nolint:staticcheck
 | 
						prefix := c.GetPrefix() // nolint:staticcheck
 | 
				
			||||||
	snet[len(prefix)-1] |= 0x01
 | 
						snet[len(prefix)-1] |= 0x01
 | 
				
			||||||
	return &snet
 | 
						return &snet
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetKet returns the partial ed25519.PublicKey for the Address.
 | 
					// GetKet returns the partial ed25519.PublicKey for the Address.
 | 
				
			||||||
// This is used for key lookup.
 | 
					// This is used for key lookup.
 | 
				
			||||||
func (a *Address) GetKey() ed25519.PublicKey {
 | 
					func (c *Core) GetAddressKey(a Address) ed25519.PublicKey {
 | 
				
			||||||
	var key [ed25519.PublicKeySize]byte
 | 
						var key [ed25519.PublicKeySize]byte
 | 
				
			||||||
	prefix := GetPrefix() // nolint:staticcheck
 | 
						prefix := c.GetPrefix() // nolint:staticcheck
 | 
				
			||||||
	ones := int(a[len(prefix)])
 | 
						ones := int(a[len(prefix)])
 | 
				
			||||||
	for idx := 0; idx < ones; idx++ {
 | 
						for idx := 0; idx < ones; idx++ {
 | 
				
			||||||
		key[idx/8] |= 0x80 >> byte(idx%8)
 | 
							key[idx/8] |= 0x80 >> byte(idx%8)
 | 
				
			||||||
| 
						 | 
					@ -143,8 +143,8 @@ func (a *Address) GetKey() ed25519.PublicKey {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetKet returns the partial ed25519.PublicKey for the Subnet.
 | 
					// GetKet returns the partial ed25519.PublicKey for the Subnet.
 | 
				
			||||||
// This is used for key lookup.
 | 
					// This is used for key lookup.
 | 
				
			||||||
func (s *Subnet) GetKey() ed25519.PublicKey {
 | 
					func (c *Core) GetSubnetKey(s Subnet) ed25519.PublicKey {
 | 
				
			||||||
	var addr Address
 | 
						var addr Address
 | 
				
			||||||
	copy(addr[:], s[:])
 | 
						copy(addr[:], s[:])
 | 
				
			||||||
	return addr.GetKey()
 | 
						return c.GetAddressKey(addr)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
package address
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ import (
 | 
				
			||||||
	//"time"
 | 
						//"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/Arceliar/phony"
 | 
						"github.com/Arceliar/phony"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SelfInfo struct {
 | 
					type SelfInfo struct {
 | 
				
			||||||
| 
						 | 
					@ -159,7 +159,7 @@ func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
 | 
				
			||||||
// that application also implements either VPN functionality or deals with IP
 | 
					// that application also implements either VPN functionality or deals with IP
 | 
				
			||||||
// packets specifically.
 | 
					// packets specifically.
 | 
				
			||||||
func (c *Core) Address() net.IP {
 | 
					func (c *Core) Address() net.IP {
 | 
				
			||||||
	addr := net.IP(address.AddrForKey(c.public)[:])
 | 
						addr := net.IP(c.AddrForKey(c.public)[:])
 | 
				
			||||||
	return addr
 | 
						return addr
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -169,7 +169,7 @@ func (c *Core) Address() net.IP {
 | 
				
			||||||
// that application also implements either VPN functionality or deals with IP
 | 
					// that application also implements either VPN functionality or deals with IP
 | 
				
			||||||
// packets specifically.
 | 
					// packets specifically.
 | 
				
			||||||
func (c *Core) Subnet() net.IPNet {
 | 
					func (c *Core) Subnet() net.IPNet {
 | 
				
			||||||
	subnet := address.SubnetForKey(c.public)[:]
 | 
						subnet := c.SubnetForKey(c.public)[:]
 | 
				
			||||||
	subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
 | 
						subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0)
 | 
				
			||||||
	return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
 | 
						return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ import (
 | 
				
			||||||
// The Core object represents the Mesh node. You should create a Core
 | 
					// The Core object represents the Mesh node. You should create a Core
 | 
				
			||||||
// object for each Mesh node you plan to run.
 | 
					// object for each Mesh node you plan to run.
 | 
				
			||||||
type Core struct {
 | 
					type Core struct {
 | 
				
			||||||
 | 
						address      Address
 | 
				
			||||||
	// This is the main data structure that holds everything else for a node
 | 
						// 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
 | 
						// We're going to keep our own copy of the provided config - that way we can
 | 
				
			||||||
	// guarantee that it will be covered by the mutex
 | 
						// guarantee that it will be covered by the mutex
 | 
				
			||||||
| 
						 | 
					@ -34,11 +35,12 @@ type Core struct {
 | 
				
			||||||
	log          Logger
 | 
						log          Logger
 | 
				
			||||||
	addPeerTimer *time.Timer
 | 
						addPeerTimer *time.Timer
 | 
				
			||||||
	config       struct {
 | 
						config       struct {
 | 
				
			||||||
		_peers             map[Peer]*linkInfo          // configurable after startup
 | 
							_peers             map[Peer]*linkInfo         // configurable after startup
 | 
				
			||||||
		_listeners         map[ListenAddress]struct{} // configurable after startup
 | 
							_listeners         map[ListenAddress]struct{} // configurable after startup
 | 
				
			||||||
		nodeinfo           NodeInfo                   // immutable after startup
 | 
							nodeinfo           NodeInfo                   // immutable after startup
 | 
				
			||||||
		nodeinfoPrivacy    NodeInfoPrivacy            // immutable after startup
 | 
							nodeinfoPrivacy    NodeInfoPrivacy            // immutable after startup
 | 
				
			||||||
		_allowedPublicKeys map[[32]byte]struct{}      // configurable after startup
 | 
							_allowedPublicKeys map[[32]byte]struct{}      // configurable after startup
 | 
				
			||||||
 | 
							networkdomain      NetworkDomain              // immutable after startup
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ import (
 | 
				
			||||||
	"sync/atomic"
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/Arceliar/phony"
 | 
						"github.com/Arceliar/phony"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
	//"github.com/Arceliar/phony" // TODO? use instead of mutexes
 | 
						//"github.com/Arceliar/phony" // TODO? use instead of mutexes
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -188,7 +188,7 @@ func (intf *link) handler() error {
 | 
				
			||||||
	if intf.incoming {
 | 
						if intf.incoming {
 | 
				
			||||||
		dir = "inbound"
 | 
							dir = "inbound"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String()
 | 
						remoteAddr := net.IP(intf.links.core.AddrForKey(meta.key)[:]).String()
 | 
				
			||||||
	remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote)
 | 
						remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote)
 | 
				
			||||||
	localStr := intf.conn.LocalAddr()
 | 
						localStr := intf.conn.LocalAddr()
 | 
				
			||||||
	intf.links.core.log.Infof("Connected %s %s: %s, source %s",
 | 
						intf.links.core.log.Infof("Connected %s %s: %s, source %s",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,8 @@ func (c *Core) _applyOption(opt SetupOption) {
 | 
				
			||||||
		c.config.nodeinfo = v
 | 
							c.config.nodeinfo = v
 | 
				
			||||||
	case NodeInfoPrivacy:
 | 
						case NodeInfoPrivacy:
 | 
				
			||||||
		c.config.nodeinfoPrivacy = v
 | 
							c.config.nodeinfoPrivacy = v
 | 
				
			||||||
 | 
						case NetworkDomain:
 | 
				
			||||||
 | 
							c.config.networkdomain = v
 | 
				
			||||||
	case AllowedPublicKey:
 | 
						case AllowedPublicKey:
 | 
				
			||||||
		pk := [32]byte{}
 | 
							pk := [32]byte{}
 | 
				
			||||||
		copy(pk[:], v)
 | 
							copy(pk[:], v)
 | 
				
			||||||
| 
						 | 
					@ -32,10 +34,14 @@ type Peer struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
type NodeInfo map[string]interface{}
 | 
					type NodeInfo map[string]interface{}
 | 
				
			||||||
type NodeInfoPrivacy bool
 | 
					type NodeInfoPrivacy bool
 | 
				
			||||||
 | 
					type NetworkDomain struct {
 | 
				
			||||||
 | 
						Prefix          [1]byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
type AllowedPublicKey ed25519.PublicKey
 | 
					type AllowedPublicKey ed25519.PublicKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a ListenAddress) isSetupOption()    {}
 | 
					func (a ListenAddress) isSetupOption()    {}
 | 
				
			||||||
func (a Peer) isSetupOption()             {}
 | 
					func (a Peer) isSetupOption()             {}
 | 
				
			||||||
func (a NodeInfo) isSetupOption()         {}
 | 
					func (a NodeInfo) isSetupOption()         {}
 | 
				
			||||||
func (a NodeInfoPrivacy) isSetupOption()  {}
 | 
					func (a NodeInfoPrivacy) isSetupOption()  {}
 | 
				
			||||||
 | 
					func (a NetworkDomain) isSetupOption()    {}
 | 
				
			||||||
func (a AllowedPublicKey) isSetupOption() {}
 | 
					func (a AllowedPublicKey) isSetupOption() {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ import (
 | 
				
			||||||
	iwt "github.com/Arceliar/ironwood/types"
 | 
						iwt "github.com/Arceliar/ironwood/types"
 | 
				
			||||||
	"github.com/Arceliar/phony"
 | 
						"github.com/Arceliar/phony"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -266,7 +266,7 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
 | 
				
			||||||
		if err := msg.UnmarshalJSON(info); err != nil {
 | 
							if err := msg.UnmarshalJSON(info); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ip := net.IP(address.AddrForKey(kbs)[:])
 | 
							ip := net.IP(p.core.AddrForKey(kbs)[:])
 | 
				
			||||||
		res := DebugGetSelfResponse{ip.String(): msg}
 | 
							res := DebugGetSelfResponse{ip.String(): msg}
 | 
				
			||||||
		return res, nil
 | 
							return res, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -316,7 +316,7 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
 | 
				
			||||||
		if err := msg.UnmarshalJSON(js); err != nil {
 | 
							if err := msg.UnmarshalJSON(js); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ip := net.IP(address.AddrForKey(kbs)[:])
 | 
							ip := net.IP(p.core.AddrForKey(kbs)[:])
 | 
				
			||||||
		res := DebugGetPeersResponse{ip.String(): msg}
 | 
							res := DebugGetPeersResponse{ip.String(): msg}
 | 
				
			||||||
		return res, nil
 | 
							return res, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -366,7 +366,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
 | 
				
			||||||
		if err := msg.UnmarshalJSON(js); err != nil {
 | 
							if err := msg.UnmarshalJSON(js); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ip := net.IP(address.AddrForKey(kbs)[:])
 | 
							ip := net.IP(p.core.AddrForKey(kbs)[:])
 | 
				
			||||||
		res := DebugGetDHTResponse{ip.String(): msg}
 | 
							res := DebugGetDHTResponse{ip.String(): msg}
 | 
				
			||||||
		return res, nil
 | 
							return res, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package defaults
 | 
				
			||||||
import "github.com/RiV-chain/RiV-mesh/src/config"
 | 
					import "github.com/RiV-chain/RiV-mesh/src/config"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
 | 
					type MulticastInterfaceConfig = config.MulticastInterfaceConfig
 | 
				
			||||||
 | 
					type NetworkDomainConfig = config.NetworkDomainConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var defaultConfig = ""      // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config
 | 
					var defaultConfig = ""      // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config
 | 
				
			||||||
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock'
 | 
					var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock'
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,9 @@ type platformDefaultParameters struct {
 | 
				
			||||||
	// Multicast interfaces
 | 
						// Multicast interfaces
 | 
				
			||||||
	DefaultMulticastInterfaces []MulticastInterfaceConfig
 | 
						DefaultMulticastInterfaces []MulticastInterfaceConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//Network domain
 | 
				
			||||||
 | 
						DefaultNetworkDomain NetworkDomainConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TUN
 | 
						// TUN
 | 
				
			||||||
	MaximumIfMTU  uint64
 | 
						MaximumIfMTU  uint64
 | 
				
			||||||
	DefaultIfMTU  uint64
 | 
						DefaultIfMTU  uint64
 | 
				
			||||||
| 
						 | 
					@ -52,6 +56,7 @@ func GenerateConfig() *config.NodeConfig {
 | 
				
			||||||
	cfg.InterfacePeers = map[string][]string{}
 | 
						cfg.InterfacePeers = map[string][]string{}
 | 
				
			||||||
	cfg.AllowedPublicKeys = []string{}
 | 
						cfg.AllowedPublicKeys = []string{}
 | 
				
			||||||
	cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
 | 
						cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
 | 
				
			||||||
 | 
						cfg.NetworkDomain = defaults.DefaultNetworkDomain
 | 
				
			||||||
	cfg.IfName = defaults.DefaultIfName
 | 
						cfg.IfName = defaults.DefaultIfName
 | 
				
			||||||
	cfg.IfMTU = defaults.DefaultIfMTU
 | 
						cfg.IfMTU = defaults.DefaultIfMTU
 | 
				
			||||||
	cfg.NodeInfoPrivacy = false
 | 
						cfg.NodeInfoPrivacy = false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: "bridge.*", Beacon: true, Listen: true},
 | 
								{Regex: "bridge.*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  65535,
 | 
							MaximumIfMTU:  65535,
 | 
				
			||||||
		DefaultIfMTU:  65535,
 | 
							DefaultIfMTU:  65535,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: ".*", Beacon: true, Listen: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  32767,
 | 
							MaximumIfMTU:  32767,
 | 
				
			||||||
		DefaultIfMTU:  32767,
 | 
							DefaultIfMTU:  32767,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: ".*", Beacon: true, Listen: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  65535,
 | 
							MaximumIfMTU:  65535,
 | 
				
			||||||
		DefaultIfMTU:  65535,
 | 
							DefaultIfMTU:  65535,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: ".*", Beacon: true, Listen: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  16384,
 | 
							MaximumIfMTU:  16384,
 | 
				
			||||||
		DefaultIfMTU:  16384,
 | 
							DefaultIfMTU:  16384,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: ".*", Beacon: true, Listen: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  65535,
 | 
							MaximumIfMTU:  65535,
 | 
				
			||||||
		DefaultIfMTU:  65535,
 | 
							DefaultIfMTU:  65535,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ func getDefaults() platformDefaultParameters {
 | 
				
			||||||
			{Regex: ".*", Beacon: true, Listen: true},
 | 
								{Regex: ".*", Beacon: true, Listen: true},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Network domain
 | 
				
			||||||
 | 
					                DefaultNetworkDomain: NetworkDomainConfig{
 | 
				
			||||||
 | 
					                        Prefix: [...]byte{0xfc},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TUN
 | 
							// TUN
 | 
				
			||||||
		MaximumIfMTU:  65535,
 | 
							MaximumIfMTU:  65535,
 | 
				
			||||||
		DefaultIfMTU:  65535,
 | 
							DefaultIfMTU:  65535,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	iwt "github.com/Arceliar/ironwood/types"
 | 
						iwt "github.com/Arceliar/ironwood/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/core"
 | 
						"github.com/RiV-chain/RiV-mesh/src/core"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,21 +30,21 @@ type keyArray [ed25519.PublicKeySize]byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type keyStore struct {
 | 
					type keyStore struct {
 | 
				
			||||||
	core         *core.Core
 | 
						core         *core.Core
 | 
				
			||||||
	address      address.Address
 | 
						address      core.Address
 | 
				
			||||||
	subnet       address.Subnet
 | 
						subnet       core.Subnet
 | 
				
			||||||
	mutex        sync.Mutex
 | 
						mutex        sync.Mutex
 | 
				
			||||||
	keyToInfo    map[keyArray]*keyInfo
 | 
						keyToInfo    map[keyArray]*keyInfo
 | 
				
			||||||
	addrToInfo   map[address.Address]*keyInfo
 | 
						addrToInfo   map[core.Address]*keyInfo
 | 
				
			||||||
	addrBuffer   map[address.Address]*buffer
 | 
						addrBuffer   map[core.Address]*buffer
 | 
				
			||||||
	subnetToInfo map[address.Subnet]*keyInfo
 | 
						subnetToInfo map[core.Subnet]*keyInfo
 | 
				
			||||||
	subnetBuffer map[address.Subnet]*buffer
 | 
						subnetBuffer map[core.Subnet]*buffer
 | 
				
			||||||
	mtu          uint64
 | 
						mtu          uint64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type keyInfo struct {
 | 
					type keyInfo struct {
 | 
				
			||||||
	key     keyArray
 | 
						key     keyArray
 | 
				
			||||||
	address address.Address
 | 
						address core.Address
 | 
				
			||||||
	subnet  address.Subnet
 | 
						subnet  core.Subnet
 | 
				
			||||||
	timeout *time.Timer // From calling a time.AfterFunc to do cleanup
 | 
						timeout *time.Timer // From calling a time.AfterFunc to do cleanup
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,21 +55,21 @@ type buffer struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *keyStore) init(c *core.Core) {
 | 
					func (k *keyStore) init(c *core.Core) {
 | 
				
			||||||
	k.core = c
 | 
						k.core = c
 | 
				
			||||||
	k.address = *address.AddrForKey(k.core.PublicKey())
 | 
						k.address = *c.AddrForKey(k.core.PublicKey())
 | 
				
			||||||
	k.subnet = *address.SubnetForKey(k.core.PublicKey())
 | 
						k.subnet = *c.SubnetForKey(k.core.PublicKey())
 | 
				
			||||||
	if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
 | 
						if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
 | 
				
			||||||
		err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
 | 
							err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	k.keyToInfo = make(map[keyArray]*keyInfo)
 | 
						k.keyToInfo = make(map[keyArray]*keyInfo)
 | 
				
			||||||
	k.addrToInfo = make(map[address.Address]*keyInfo)
 | 
						k.addrToInfo = make(map[core.Address]*keyInfo)
 | 
				
			||||||
	k.addrBuffer = make(map[address.Address]*buffer)
 | 
						k.addrBuffer = make(map[core.Address]*buffer)
 | 
				
			||||||
	k.subnetToInfo = make(map[address.Subnet]*keyInfo)
 | 
						k.subnetToInfo = make(map[core.Subnet]*keyInfo)
 | 
				
			||||||
	k.subnetBuffer = make(map[address.Subnet]*buffer)
 | 
						k.subnetBuffer = make(map[core.Subnet]*buffer)
 | 
				
			||||||
	k.mtu = 1280 // Default to something safe, expect user to set this
 | 
						k.mtu = 1280 // Default to something safe, expect user to set this
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *keyStore) sendToAddress(addr address.Address, bs []byte) {
 | 
					func (k *keyStore) sendToAddress(addr core.Address, bs []byte) {
 | 
				
			||||||
	k.mutex.Lock()
 | 
						k.mutex.Lock()
 | 
				
			||||||
	if info := k.addrToInfo[addr]; info != nil {
 | 
						if info := k.addrToInfo[addr]; info != nil {
 | 
				
			||||||
		k.resetTimeout(info)
 | 
							k.resetTimeout(info)
 | 
				
			||||||
| 
						 | 
					@ -94,11 +94,11 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		k.mutex.Unlock()
 | 
							k.mutex.Unlock()
 | 
				
			||||||
		k.sendKeyLookup(addr.GetKey())
 | 
							k.sendKeyLookup(k.core.GetAddressKey(addr))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) {
 | 
					func (k *keyStore) sendToSubnet(subnet core.Subnet, bs []byte) {
 | 
				
			||||||
	k.mutex.Lock()
 | 
						k.mutex.Lock()
 | 
				
			||||||
	if info := k.subnetToInfo[subnet]; info != nil {
 | 
						if info := k.subnetToInfo[subnet]; info != nil {
 | 
				
			||||||
		k.resetTimeout(info)
 | 
							k.resetTimeout(info)
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		k.mutex.Unlock()
 | 
							k.mutex.Unlock()
 | 
				
			||||||
		k.sendKeyLookup(subnet.GetKey())
 | 
							k.sendKeyLookup(k.core.GetSubnetKey(subnet))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,8 +136,8 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo {
 | 
				
			||||||
	if info = k.keyToInfo[kArray]; info == nil {
 | 
						if info = k.keyToInfo[kArray]; info == nil {
 | 
				
			||||||
		info = new(keyInfo)
 | 
							info = new(keyInfo)
 | 
				
			||||||
		info.key = kArray
 | 
							info.key = kArray
 | 
				
			||||||
		info.address = *address.AddrForKey(ed25519.PublicKey(info.key[:]))
 | 
							info.address = *k.core.AddrForKey(ed25519.PublicKey(info.key[:]))
 | 
				
			||||||
		info.subnet = *address.SubnetForKey(ed25519.PublicKey(info.key[:]))
 | 
							info.subnet = *k.core.SubnetForKey(ed25519.PublicKey(info.key[:]))
 | 
				
			||||||
		k.keyToInfo[info.key] = info
 | 
							k.keyToInfo[info.key] = info
 | 
				
			||||||
		k.addrToInfo[info.address] = info
 | 
							k.addrToInfo[info.address] = info
 | 
				
			||||||
		k.subnetToInfo[info.subnet] = info
 | 
							k.subnetToInfo[info.subnet] = info
 | 
				
			||||||
| 
						 | 
					@ -184,7 +184,7 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
 | 
				
			||||||
	sig := data[1:]
 | 
						sig := data[1:]
 | 
				
			||||||
	switch data[0] {
 | 
						switch data[0] {
 | 
				
			||||||
	case typeKeyLookup:
 | 
						case typeKeyLookup:
 | 
				
			||||||
		snet := *address.SubnetForKey(toKey)
 | 
							snet := *k.core.SubnetForKey(toKey)
 | 
				
			||||||
		if snet == k.subnet && ed25519.Verify(fromKey, toKey[:], sig) {
 | 
							if snet == k.subnet && ed25519.Verify(fromKey, toKey[:], sig) {
 | 
				
			||||||
			// This is looking for at least our subnet (possibly our address)
 | 
								// This is looking for at least our subnet (possibly our address)
 | 
				
			||||||
			// Send a response
 | 
								// Send a response
 | 
				
			||||||
| 
						 | 
					@ -248,8 +248,8 @@ func (k *keyStore) readPC(p []byte) (int, error) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		var srcAddr, dstAddr address.Address
 | 
							var srcAddr, dstAddr core.Address
 | 
				
			||||||
		var srcSubnet, dstSubnet address.Subnet
 | 
							var srcSubnet, dstSubnet core.Subnet
 | 
				
			||||||
		copy(srcAddr[:], bs[8:])
 | 
							copy(srcAddr[:], bs[8:])
 | 
				
			||||||
		copy(dstAddr[:], bs[24:])
 | 
							copy(dstAddr[:], bs[24:])
 | 
				
			||||||
		copy(srcSubnet[:], bs[8:])
 | 
							copy(srcSubnet[:], bs[8:])
 | 
				
			||||||
| 
						 | 
					@ -274,8 +274,8 @@ func (k *keyStore) writePC(bs []byte) (int, error) {
 | 
				
			||||||
		strErr := fmt.Sprint("undersized IPv6 packet, length: ", len(bs))
 | 
							strErr := fmt.Sprint("undersized IPv6 packet, length: ", len(bs))
 | 
				
			||||||
		return 0, errors.New(strErr)
 | 
							return 0, errors.New(strErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var srcAddr, dstAddr address.Address
 | 
						var srcAddr, dstAddr core.Address
 | 
				
			||||||
	var srcSubnet, dstSubnet address.Subnet
 | 
						var srcSubnet, dstSubnet core.Subnet
 | 
				
			||||||
	copy(srcAddr[:], bs[8:])
 | 
						copy(srcAddr[:], bs[8:])
 | 
				
			||||||
	copy(dstAddr[:], bs[24:])
 | 
						copy(dstAddr[:], bs[24:])
 | 
				
			||||||
	copy(srcSubnet[:], bs[8:])
 | 
						copy(srcSubnet[:], bs[8:])
 | 
				
			||||||
| 
						 | 
					@ -286,9 +286,9 @@ func (k *keyStore) writePC(bs []byte) (int, error) {
 | 
				
			||||||
		strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String())
 | 
							strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String())
 | 
				
			||||||
		return 0, errors.New(strErr)
 | 
							return 0, errors.New(strErr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if dstAddr.IsValid() {
 | 
						if k.core.IsValidAddress(dstAddr) {
 | 
				
			||||||
		k.sendToAddress(dstAddr, bs)
 | 
							k.sendToAddress(dstAddr, bs)
 | 
				
			||||||
	} else if dstSubnet.IsValid() {
 | 
						} else if k.core.IsValidSubnet(dstSubnet) {
 | 
				
			||||||
		k.sendToSubnet(dstSubnet, bs)
 | 
							k.sendToSubnet(dstSubnet, bs)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return 0, errors.New("invalid destination address")
 | 
							return 0, errors.New("invalid destination address")
 | 
				
			||||||
| 
						 | 
					@ -331,11 +331,11 @@ func NewReadWriteCloser(c *core.Core) *ReadWriteCloser {
 | 
				
			||||||
	return rwc
 | 
						return rwc
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (rwc *ReadWriteCloser) Address() address.Address {
 | 
					func (rwc *ReadWriteCloser) Address() core.Address {
 | 
				
			||||||
	return rwc.address
 | 
						return rwc.address
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (rwc *ReadWriteCloser) Subnet() address.Subnet {
 | 
					func (rwc *ReadWriteCloser) Subnet() core.Subnet {
 | 
				
			||||||
	return rwc.subnet
 | 
						return rwc.subnet
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import (
 | 
				
			||||||
	"github.com/Arceliar/phony"
 | 
						"github.com/Arceliar/phony"
 | 
				
			||||||
	"golang.zx2c4.com/wireguard/tun"
 | 
						"golang.zx2c4.com/wireguard/tun"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/address"
 | 
						//"github.com/RiV-chain/RiV-mesh/src/address"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/core"
 | 
						"github.com/RiV-chain/RiV-mesh/src/core"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
						"github.com/RiV-chain/RiV-mesh/src/defaults"
 | 
				
			||||||
	"github.com/RiV-chain/RiV-mesh/src/ipv6rwc"
 | 
						"github.com/RiV-chain/RiV-mesh/src/ipv6rwc"
 | 
				
			||||||
| 
						 | 
					@ -26,10 +26,11 @@ type MTU uint16
 | 
				
			||||||
// should pass this object to the mesh.SetRouterAdapter() function before
 | 
					// should pass this object to the mesh.SetRouterAdapter() function before
 | 
				
			||||||
// calling mesh.Start().
 | 
					// calling mesh.Start().
 | 
				
			||||||
type TunAdapter struct {
 | 
					type TunAdapter struct {
 | 
				
			||||||
 | 
						core        *core.Core
 | 
				
			||||||
	rwc         *ipv6rwc.ReadWriteCloser
 | 
						rwc         *ipv6rwc.ReadWriteCloser
 | 
				
			||||||
	log         core.Logger
 | 
						log         core.Logger
 | 
				
			||||||
	addr        address.Address
 | 
						addr        core.Address
 | 
				
			||||||
	subnet      address.Subnet
 | 
						subnet      core.Subnet
 | 
				
			||||||
	mtu         uint64
 | 
						mtu         uint64
 | 
				
			||||||
	iface       tun.Device
 | 
						iface       tun.Device
 | 
				
			||||||
	phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
 | 
						phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
 | 
				
			||||||
| 
						 | 
					@ -90,9 +91,10 @@ func MaximumMTU() uint64 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Init initialises the TUN module. You must have acquired a Listener from
 | 
					// Init initialises the TUN module. You must have acquired a Listener from
 | 
				
			||||||
// the Mesh	 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) {
 | 
					func New(core *core.Core, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
 | 
				
			||||||
	tun := &TunAdapter{
 | 
						tun := &TunAdapter{
 | 
				
			||||||
		rwc: rwc,
 | 
							core: core,
 | 
				
			||||||
 | 
							rwc: ipv6rwc.NewReadWriteCloser(core),
 | 
				
			||||||
		log: log,
 | 
							log: log,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, opt := range opts {
 | 
						for _, opt := range opts {
 | 
				
			||||||
| 
						 | 
					@ -107,7 +109,7 @@ func (tun *TunAdapter) _start() error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tun.addr = tun.rwc.Address()
 | 
						tun.addr = tun.rwc.Address()
 | 
				
			||||||
	tun.subnet = tun.rwc.Subnet()
 | 
						tun.subnet = tun.rwc.Subnet()
 | 
				
			||||||
	addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
 | 
						addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(tun.core.GetPrefix())-1)
 | 
				
			||||||
	if tun.config.name == "none" || tun.config.name == "dummy" {
 | 
						if tun.config.name == "none" || tun.config.name == "dummy" {
 | 
				
			||||||
		tun.log.Debugln("Not starting TUN as ifname is none or dummy")
 | 
							tun.log.Debugln("Not starting TUN as ifname is none or dummy")
 | 
				
			||||||
		tun.isEnabled = false
 | 
							tun.isEnabled = false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue