Initial commit.

This commit is contained in:
Kleissner
2021-02-26 05:57:42 +01:00
commit fec65603f5
13 changed files with 1549 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.exe
log.txt
*debug_bin
*.yaml

187
Bootstrap.go Normal file
View File

@@ -0,0 +1,187 @@
/*
File Name: Bootstrap.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
Strategy for sending our IPv6 Multicast and IPv4 Broadcast messages:
* During bootstrap: Immediately at the beginning, then every 10 seconds until there is at least 1 peer.
* Every 10 minutes during regular operation.
* Each time a network adapter / IP change is detected. [FUTURE]
*/
package core
import (
"encoding/hex"
"errors"
"log"
"net"
"strconv"
"time"
"github.com/btcsuite/btcd/btcec"
)
// rootPeer is a single root peer info
type rootPeer struct {
peer *PeerInfo // loaded PeerInfo
publicKey *btcec.PublicKey // Public key
addresses []*net.UDPAddr // IP:Port addresses
}
var rootPeers map[[btcec.PubKeyBytesLenCompressed]byte]*rootPeer
// initSeedList loads the seed list from the config
func initSeedList() {
rootPeers = make(map[[btcec.PubKeyBytesLenCompressed]byte]*rootPeer)
loopSeedList:
for _, seed := range config.SeedList {
peer := &rootPeer{}
// parse the Public Key
publicKeyB, err := hex.DecodeString(seed.PublicKey)
if err != nil {
log.Printf("initSeedList error public key '%s': %v", seed.PublicKey, err.Error())
continue
}
if peer.publicKey, err = btcec.ParsePubKey(publicKeyB, btcec.S256()); err != nil {
log.Printf("initSeedList error public key '%s': %v", seed.PublicKey, err.Error())
continue
}
// parse all IP addresses
for _, addressA := range seed.Address {
address, err := parseAddress(addressA)
if err != nil {
log.Printf("initSeedList error public key '%s' address '%s': %v", seed.PublicKey, addressA, err.Error())
continue loopSeedList
}
peer.addresses = append(peer.addresses, address)
}
rootPeers[publicKey2Compressed(peer.publicKey)] = peer
}
}
// parseAddress parses an input peer address in the form "IP:Port".
func parseAddress(Address string) (remote *net.UDPAddr, err error) {
host, portA, err := net.SplitHostPort(Address)
if err != nil {
return nil, err
}
portI, err := strconv.Atoi(portA)
if err != nil {
return nil, err
} else if portI <= 0 || portI > 65535 {
return nil, errors.New("invalid port number")
}
ip := net.ParseIP(host)
if ip == nil {
return nil, errors.New("invalid input IP")
}
return &net.UDPAddr{IP: ip, Port: portI}, err
}
// contact tries to contact the root peer on all networks
func (peer *rootPeer) contact() {
for _, address := range peer.addresses {
sendAllNetworks(peer.publicKey, &packetRaw{Command: 0}, address)
}
}
// bootstrap connects to the initial set of peers. It will also start the routine for ongoing sending of multicast/broadcast messages.
func bootstrap() {
if len(rootPeers) == 0 {
log.Printf("bootstrap warning: Empty list of root peers. Connectivity relies on local peer discovery and incoming connections.\n")
return
}
contactRootPeers := func() {
for _, peer := range rootPeers {
if peer.peer == nil {
peer.contact()
}
}
}
countConnectedRootPeers := func() (connectedCount, total int) {
for _, peer := range rootPeers {
if peer.peer != nil {
connectedCount++
} else if peer.peer = PeerlistLookup(peer.publicKey); peer.peer != nil {
connectedCount++
}
}
return connectedCount, len(rootPeers)
}
// initial contact to all root peer
contactRootPeers()
// Phase 1: First 10 minutes. Try every 7 seconds to connect to all root peers until at least 2 peers connected.
for n := 0; n < 10*60/7; n++ {
time.Sleep(time.Second * 7)
if connected, total := countConnectedRootPeers(); connected == total || connected >= 2 {
return
}
contactRootPeers()
}
// Phase 2: After that (if not 2 peers), try every 5 minutes to connect to remaining root peers for a maximum of 1 hour.
for n := 0; n < 1*60/5; n++ {
time.Sleep(time.Minute * 5)
contactRootPeers()
if connected, total := countConnectedRootPeers(); connected == total || connected >= 2 {
return
}
}
log.Printf("bootstrap unable to connect to at least 2 root peers, aborting\n")
}
func autoMulticastBroadcast() {
sendMulticastBroadcast := func() {
for _, network := range networks6 {
if err := network.MulticastIPv6Send(); err != nil {
log.Printf("bootstrap error multicast from network address '%s': %v", network.address.IP.String(), err.Error())
}
}
for _, network := range networks4 {
if err := network.BroadcastIPv4Send(); err != nil {
log.Printf("bootstrap error broadcast from network address '%s': %v", network.address.IP.String(), err.Error())
}
}
}
// Send out multicast/broadcast immediately.
sendMulticastBroadcast()
// Phase 1: Resend every 10 seconds until at least 1 peer in the peer list
for {
time.Sleep(time.Second * 10)
if PeerlistCount() >= 1 {
break
}
sendMulticastBroadcast()
}
// Phase 2: Every 10 minutes.
for {
time.Sleep(time.Minute * 10)
sendMulticastBroadcast()
}
}

73
Commands.go Normal file
View File

@@ -0,0 +1,73 @@
/*
File Name: Commands.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec"
)
// packet2 is a high-level message between peers
type packet2 struct {
packetRaw
SenderPublicKey *btcec.PublicKey // Sender Public Key, ECDSA (secp256k1) 257-bit
network *Network // network which received the packet
address *net.UDPAddr // address of the sender or receiver
}
// announcement handles an incoming announcement
func (peer *PeerInfo) announcement(msg *packet2) {
if peer == nil {
peer, added := PeerlistAdd(msg.SenderPublicKey, &Connection{Network: msg.network, Address: msg.address})
fmt.Printf("Incoming initial announcement from %s\n", msg.address.String())
// send the Response
if added {
peer.send(&packetRaw{Command: 1})
}
return
}
fmt.Printf("Incoming secondary announcement from %s\n", msg.address.String())
// Announcement from existing peer means the peer most likely restarted
peer.send(&packetRaw{Command: 1})
}
// response handles the response to the announcement
func (peer *PeerInfo) response(msg *packet2) {
if peer == nil {
peer, _ = PeerlistAdd(msg.SenderPublicKey, &Connection{Network: msg.network, Address: msg.address})
fmt.Printf("Incoming initial response from %s\n", msg.address.String())
return
}
fmt.Printf("Incoming response from %s on %s\n", msg.address.String(), msg.network.address.String())
}
// chat handles a chat message [debug]
func (peer *PeerInfo) chat(msg *packet2) {
fmt.Printf("Chat from '%s': %s\n", msg.address.String(), string(msg.packetRaw.Payload))
}
// autoPingAll automatically pings all peers. This has multiple important reasons:
// * Keeping the UDP port open at NAT, if applicable
// * Making sure to recognize invalid connections and drop them.
func autoPingAll() {
// TODO: If peer was previously unresponsive for X times, start pinging on all available ports.
}
// SendChatAll sends a text message to all peers
func SendChatAll(text string) {
for _, peer := range PeerlistGet() {
peer.send(&packetRaw{Command: 10, Payload: []byte(text)})
}
}

71
Network Detection.go Normal file
View File

@@ -0,0 +1,71 @@
/*
File Name: Network Detection.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
import "net"
// FindInterfaceByIP finds an interface based on the IP. The IP must be available at the interface.
func FindInterfaceByIP(ip net.IP) (iface *net.Interface, ipnet *net.IPNet) {
interfaceList, err := net.Interfaces()
if err != nil {
return nil, nil
}
// iterate through all interfaces
for _, ifaceSingle := range interfaceList {
addresses, err := ifaceSingle.Addrs()
if err != nil {
continue
}
// iterate through all IPs of the interfaces
for _, address := range addresses {
addressIP := address.(*net.IPNet).IP
if addressIP.Equal(ip) {
return &ifaceSingle, address.(*net.IPNet)
}
}
}
return nil, nil
}
// NetworkListIPs returns a list of all IPs
func NetworkListIPs() (IPs []net.IP, err error) {
interfaceList, err := net.Interfaces()
if err != nil {
return nil, err
}
// iterate through all interfaces
for _, ifaceSingle := range interfaceList {
addresses, err := ifaceSingle.Addrs()
if err != nil {
continue
}
// iterate through all IPs of the interfaces
for _, address := range addresses {
addressIP := address.(*net.IPNet).IP
IPs = append(IPs, addressIP)
}
}
return IPs, nil
}
// IsIPv4 checks if an IP address is IPv4
func IsIPv4(IP net.IP) bool {
return IP.To4() != nil
}
// IsIPv6 checks if an IP address is IPv6
func IsIPv6(IP net.IP) bool {
return IP.To4() == nil && IP.To16() != nil
}

168
Network IPv4 Broadcast.go Normal file
View File

@@ -0,0 +1,168 @@
/*
File Name: Network IPv4 Broadcast.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
IPv4 Multicast just sucks (can't use socket bound to 0.0.0.0:PortMain and send to 224.0.0.1:PortMulticast), so we rely on Broadcast instead.
*/
package core
import (
"encoding/hex"
"log"
"net"
"strconv"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/libp2p/go-reuseport"
)
const ipv4BroadcastPort = 12912
// special Public-Private Key pair for local discovery
var ipv4BroadcastPrivateKey *btcec.PrivateKey
var ipv4BroadcastPublicKey *btcec.PublicKey
const ipv4BroadcastPrivateKeyH = "5e27ecc8e54a24e71dca9ba84a9bf465400e27b8c46a977d34962d3d88558c8e"
func initBroadcastIPv4() {
if configPK, err := hex.DecodeString(ipv4BroadcastPrivateKeyH); err == nil {
ipv4BroadcastPrivateKey, ipv4BroadcastPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
}
}
// BroadcastIPv4Join prepares sending Broadcasts
func (network *Network) BroadcastIPv4() (err error) {
if ipv4BroadcastPrivateKey == nil || ipv4BroadcastPublicKey == nil {
return
}
// listen on a special socket
network.broadcastSocket, err = reuseport.ListenPacket("udp4", net.JoinHostPort(network.address.IP.String(), strconv.Itoa(ipv4BroadcastPort)))
if err != nil {
return err
}
network.broadcastIPv4 = networkToIPv4BroadcastIPs(network.ipnet)
go network.BroadcastIPv4Listen()
return nil
}
// BroadcastIPv4Listen listens for incoming broadcast packets
// Fork from network.Listen! Keep any changes synced.
func (network *Network) BroadcastIPv4Listen() {
for {
// Buffer: Must be created for each packet as it is passed as pointer.
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
buffer := make([]byte, maxPacketSize)
length, sender, err := network.broadcastSocket.ReadFrom(buffer)
if err != nil {
log.Printf("Listen Error receiving UDP message: %v\n", err) // Only log for debug purposes.
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
continue
}
if IsAddressSelf(sender.(*net.UDPAddr)) {
continue
}
//fmt.Printf("BroadcastIPv4Listen from %s at network %s\n", sender.String(), network.address.String())
if length < packetLengthMin {
// Discard packets that do not meet the minimum length.
continue
}
// send the packet to a channel which is processed by multiple workers.
rawPacketsIncoming <- networkWire{network: network, sender: sender.(*net.UDPAddr), raw: buffer[:length], receiverPublicKey: ipv4BroadcastPublicKey}
}
}
// BroadcastIPv4Send sends out a single broadcast messages to discover peers
func (network *Network) BroadcastIPv4Send() (err error) {
raw, err := packetEncrypt(peerPrivateKey, ipv4BroadcastPublicKey, &packetRaw{Protocol: 0, Command: 0})
if err != nil {
return err
}
// send out the wire
for _, ip := range network.broadcastIPv4 {
err = network.send(ip, ipv4BroadcastPort, raw)
if err != nil {
log.Printf("Error sending UDP packet: %v\n", err)
}
}
return nil
}
// networkToIPv4BroadcastIPs generates the IPv4 addresses to send out the broadcast to
func networkToIPv4BroadcastIPs(ipnet *net.IPNet) (broadcastIPs []net.IP) {
broadcastIPs = append(broadcastIPs, net.IPv4bcast)
if ipnet != nil {
if ip2 := ipv4DirectedBroadcast(ipnet); ip2 != nil {
broadcastIPs = append(broadcastIPs, ip2)
}
} else {
interfaceList, err := net.Interfaces()
if err != nil {
return
}
for _, iface := range interfaceList {
addresses, err := iface.Addrs()
if err != nil {
continue
}
for _, address := range addresses {
net1 := address.(*net.IPNet)
// TODO: Does the rfc3927Net make sense?
if !IsIPv4(net1.IP) || rfc3927Net.Contains(net1.IP) {
continue
}
if ip2 := ipv4DirectedBroadcast(net1); ip2 != nil {
broadcastIPs = append(broadcastIPs, ip2)
}
}
}
}
// TODO: Result could contain duplicates, filter them out
return broadcastIPs
}
func ipv4DirectedBroadcast(n *net.IPNet) net.IP {
ip4 := n.IP.To4()
if ip4 == nil {
return nil
}
last := make(net.IP, len(ip4))
copy(last, ip4)
for i := range ip4 {
last[i] |= ^n.Mask[i]
}
return last
}
var (
// rfc3927Net specifies the IPv4 auto configuration address block as
// defined by RFC3927 (169.254.0.0/16).
rfc3927Net = ipNet("169.254.0.0", 16, 32)
)
// ipNet returns a net.IPNet struct given the passed IP address string, number
// of one bits to include at the start of the mask, and the total number of bits
// for the mask.
func ipNet(ip string, ones, bits int) net.IPNet {
return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)}
}

141
Network IPv6 Multicast.go Normal file
View File

@@ -0,0 +1,141 @@
/*
File Name: Network IPv6 Multicast.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
IPv6 Multicast implementation to support discovery of peers within the same network (Site-local).
Loopback is enabled, which means that Multicast packets sent will be looped back and received by any local listeners. This allows to connect local processes with each other.
Using the separate Multicast port, it allows sending unsolicited announcements without knowing the target's public key. Instead, a hard-coded key is used.
The Multicast listener opens port 12912 with SO_REUSEADDR to allow multiple processes receive the incoming Multicast packets.
[1] mentions "If two sockets are bound to the same interface and port and are members of the same multicast group, data will be delivered to both sockets, rather than an arbitrarily chosen one."
[1] https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
*/
package core
import (
"encoding/hex"
"log"
"net"
"strconv"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/libp2p/go-reuseport"
"golang.org/x/net/ipv6"
)
// Multicast group is site-local. Group ID is 112.
const ipv6MulticastGroup = "ff05::112"
const ipv6MulticastPort = 12912
// special Public-Private Key pair for local discovery
var ipv6MulticastPrivateKey *btcec.PrivateKey
var ipv6MulticastPublicKey *btcec.PublicKey
const ipv6MulticastPrivateKeyH = "016ad30bfb369926523bf18d136298b6c31d0817e9fb6c21feed89ae22cad788"
func initMulticastIPv6() {
if configPK, err := hex.DecodeString(ipv6MulticastPrivateKeyH); err == nil {
ipv6MulticastPrivateKey, ipv6MulticastPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
}
}
// MulticastIPv6Join joins the Multicast group
func (network *Network) MulticastIPv6Join() (err error) {
if ipv6MulticastPrivateKey == nil || ipv6MulticastPublicKey == nil {
return
}
network.multicastIP = net.ParseIP(ipv6MulticastGroup)
// listen on a special socket
network.multicastSocket, err = reuseport.ListenPacket("udp6", net.JoinHostPort(network.address.IP.String(), strconv.Itoa(ipv6MulticastPort)))
if err != nil {
return err
}
joinMulticastGroup := func(iface *net.Interface) (err error) {
pc := ipv6.NewPacketConn(network.multicastSocket)
if err := pc.JoinGroup(iface, &net.UDPAddr{IP: network.multicastIP}); err != nil {
return err
}
// receive messages from self or other processes running on the same computer
if loop, err := pc.MulticastLoopback(); err == nil {
if !loop {
if err := pc.SetMulticastLoopback(true); err != nil {
log.Printf("MulticastJoin Error setting multicast loopback status: %v\n", err)
}
}
}
return nil
}
// specific interface or join all?
if network.iface != nil {
if err = joinMulticastGroup(network.iface); err != nil {
return err
}
} else {
interfaceList, err := net.Interfaces()
if err != nil {
return err
}
for _, ifaceSingle := range interfaceList {
joinMulticastGroup(&ifaceSingle)
}
}
go network.MulticastIPv6Listen()
return nil
}
// MulticastIPv6Listen listens for incoming multicast packets
// Fork from network.Listen! Keep any changes synced.
func (network *Network) MulticastIPv6Listen() {
for {
// Buffer: Must be created for each packet as it is passed as pointer.
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
buffer := make([]byte, maxPacketSize)
length, sender, err := network.multicastSocket.ReadFrom(buffer)
if err != nil {
log.Printf("Listen Error receiving UDP message: %v\n", err) // Only log for debug purposes.
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
continue
}
// skip incoming packets that were looped back
if IsAddressSelf(sender.(*net.UDPAddr)) {
continue
}
//fmt.Printf("MulticastIPv6Listen from %s at network %s\n", sender.String(), network.address.String())
if length < packetLengthMin {
// Discard packets that do not meet the minimum length.
continue
}
// send the packet to a channel which is processed by multiple workers.
rawPacketsIncoming <- networkWire{network: network, sender: sender.(*net.UDPAddr), raw: buffer[:length], receiverPublicKey: ipv6MulticastPublicKey}
}
}
// MulticastIPv6Send sends out a single multicast messages to discover peers at the same site
func (network *Network) MulticastIPv6Send() (err error) {
raw, err := packetEncrypt(peerPrivateKey, ipv6MulticastPublicKey, &packetRaw{Protocol: 0, Command: 0})
if err != nil {
return err
}
// send out the wire
return network.send(network.multicastIP, ipv6MulticastPort, raw)
}

177
Network Init.go Normal file
View File

@@ -0,0 +1,177 @@
/*
File Name: Network Init.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
Magic 🪄 to start the network configuration with 0 manual input. Users may specify the list of IPs (and optional ports) to listen; otherwise it listens on all.
IPv6 is always preferred.
*/
package core
import (
"errors"
"log"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcec"
)
// networkWire is an incoming packet
type networkWire struct {
network *Network // network which received the packet
sender *net.UDPAddr // sender of the packet
receiverPublicKey *btcec.PublicKey // public key associated with the receiver
raw []byte // buffer
}
var (
rawPacketsIncoming chan networkWire // channel for processing incoming decoded packets by workers
ipsListen map[string]struct{} // list of IPs currently listening on
)
// initNetwork sets up the network configuration and starts listening.
func initNetwork() {
rawPacketsIncoming = make(chan networkWire, 1000) // buffer up to 1000 UDP packets before they get buffered by the OS network stack and eventually dropped
ipsListen = make(map[string]struct{})
rand.Seed(time.Now().UnixNano()) // we are not using "crypto/rand" for speed tradeoff
if config.ListenWorkers == 0 {
config.ListenWorkers = 2
}
for n := 0; n < config.ListenWorkers; n++ {
go packetWorker(rawPacketsIncoming)
}
// check if user specified where to listen
if len(config.Listen) > 0 {
for _, listenA := range config.Listen {
host, portA, err := net.SplitHostPort(listenA)
if err != nil && strings.Contains(err.Error(), "missing port in address") { // port is optional
host = listenA
portA = "0"
} else if err != nil {
log.Printf("initNetwork Error invalid input listen address '%s': %s\n", listenA, err.Error())
continue
}
portI, _ := strconv.Atoi(portA)
if _, err := networkPrepareListen(host, portI); err != nil {
log.Printf("initNetwork Error listen on '%s': %s\n", listenA, err.Error())
continue
}
}
return
}
// Listen on all IPv4 and IPv6 addresses
//if _, err := networkPrepareListen("0.0.0.0", 0); err != nil {
// log.Printf("initNetwork Error listen on all IPv4 addresses (0.0.0.0): %s\n", err.Error())
//}
//if _, err := networkPrepareListen("::", 0); err != nil {
// log.Printf("initNetwork Error listen on all IPv6 addresses (::): %s\n", err.Error())
//}
// Listen on each network adapter on each IP. This guarantees the highest deliverability, even though it brings on additional challenges such as:
// * Packet duplicates on IPv6 Multicast (listening on multiple IPs and joining the group on the same adapter) and IPv4 Broadcast (listening on multiple IPs on the same adapter).
// * Local peers are more likely to connect on the same adapter via multiple IPs (i.e. link-local and others, including public IPv6 and temporary public IPv6).
// * Network adapters and IPs might change. Simplest case is if someone changes Wifi network.
interfaceList, err := net.Interfaces()
if err != nil {
log.Printf("initNetwork enumerating network adapters failed: %s\n", err.Error())
return
}
for _, iface := range interfaceList {
addresses, err := iface.Addrs()
if err != nil {
log.Printf("initNetwork error enumerating IPs for network adapter '%s': %s\n", iface.Name, err.Error())
continue
}
for _, address := range addresses {
net1 := address.(*net.IPNet)
// Do not listen on lookpback IPs. They are not even needed for discovery of machine-local peers (they will be discovered via regular multicast/broadcast).
if net1.IP.IsLoopback() {
continue
}
netw, err := networkPrepareListen(net1.IP.String(), 0)
if err != nil {
// Do not log common errors:
// * "listen udp4 169.254.X.X:X: bind: The requested address is not valid in its context."
// Windows reports link-local addresses for inactive network adapters.
if net1.IP.IsLinkLocalUnicast() {
continue
}
log.Printf("initNetwork error listening on network adapter '%s' IPv4 '%s': %s\n", iface.Name, net1.IP.String(), err.Error())
continue
}
addListenAddress(netw.address)
log.Printf("Listen on UDP %s\n", netw.address.String())
}
}
}
// networkPrepareListen prepares to listen on the given IP address. If port is 0, one is chosen automatically.
func networkPrepareListen(ipA string, port int) (network *Network, err error) {
ip := net.ParseIP(ipA)
if ip == nil {
return nil, errors.New("Invalid input IP")
}
network = new(Network)
// get the network interface that belongs to the IP
if ipA != "0.0.0.0" && ipA != "::" {
network.iface, network.ipnet = FindInterfaceByIP(ip)
if network.iface == nil {
return nil, errors.New("Error finding the network interface belonging to IP")
}
}
// open up the port
if err = network.AutoAssignPort(ip, port); err != nil {
return nil, err
}
// Success - port is open. Add to the list and start accepting incoming messages.
if IsIPv4(ip) {
networks4 = append(networks4, network)
network.BroadcastIPv4()
} else {
networks6 = append(networks6, network)
network.MulticastIPv6Join()
}
go network.Listen()
return network, nil
}
func addListenAddress(addr *net.UDPAddr) {
ipsListen[net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port))] = struct{}{}
}
// IsAddressSelf checks if the senders address is actually listening address. This prevents loopback packets from being considered.
// Note: This does not work when listening on 0.0.0.0 or ::1 and binding the sending socket to that.
func IsAddressSelf(addr *net.UDPAddr) bool {
if addr == nil {
return false
}
// do not use addr.String() since it addds the Zone for IPv6 which may be ambiguous (can be adapter name or address literal).
_, ok := ipsListen[net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port))]
return ok
}

173
Network.go Normal file
View File

@@ -0,0 +1,173 @@
/*
File Name: Network.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
import (
"errors"
"log"
"net"
"sync/atomic"
"time"
)
// Network is a connection adapter through one network interface (adapter).
// Note that for each IP on the same adapter separate network entries are created.
type Network struct {
iface *net.Interface // Network interface belonging to the IP. May not be set.
ipnet *net.IPNet // IP network the listening address belongs to. May not be set.
address *net.UDPAddr // IP:Port where the server listens
socket *net.UDPConn // active socket for send/receive
multicastIP net.IP // Multicast IP, IPv6 only.
multicastSocket net.PacketConn // Multicast socket, IPv6 only.
broadcastSocket net.PacketConn // Broadcast socket, IPv4 only.
broadcastIPv4 []net.IP // Broadcast IPs, IPv4 only.
}
// networks is a list of all connected networks
var networks4, networks6 []*Network
// Default ports to use. This may be randomized in the future to prevent fingerprinting (and subsequent blocking) by corporate and ISP firewalls.
const defaultPort = 'p' // 112
// AutoAssignPort assigns a port for the given IP. Use port 0 for zero configuration.
func (network *Network) AutoAssignPort(ip net.IP, port int) (err error) {
networkA := "udp6"
if IsIPv4(ip) {
networkA = "udp4"
}
// A common error return is "bind: The requested address is not valid in its context.".
// This error was observed when the network interface might not be ready after boot but also when listening on a link-local IPv4 (169.254.) for an inactive adapter.
// Previously the algorithm retried up to n times, but this would unnecessarily delay startup in case the IP is actual unlistenable.
connectPortTry := func(port int) (address *net.UDPAddr, socket *net.UDPConn, err error) {
address = &net.UDPAddr{IP: ip, Port: port}
if socket, err = net.ListenUDP(networkA, address); err != nil {
return nil, nil, err
}
if port == 0 {
localAddr := socket.LocalAddr()
if localAddr == nil {
return nil, nil, errors.New("invalid port assignment")
}
address.Port = localAddr.(*net.UDPAddr).Port
}
return address, socket, nil
}
if port != 0 {
network.address, network.socket, err = connectPortTry(port)
return err
}
// try default main port, then random
if network.address, network.socket, err = connectPortTry(defaultPort); err == nil {
return nil
}
if network.address, network.socket, err = connectPortTry(0); err == nil {
return nil
}
return err
}
// send sends a message
func (network *Network) send(IP net.IP, port int, raw []byte) (err error) {
_, err = network.socket.WriteTo(raw, &net.UDPAddr{IP: IP, Port: port})
return err
}
// Currently packets are maxed at 4 KB. This is going to be refined.
const maxPacketSize = 4096
// Listen starts listening for incoming packets on the given UDP connection
func (network *Network) Listen() {
for {
// Buffer: Must be created for each packet as it is passed as pointer.
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
buffer := make([]byte, maxPacketSize)
length, sender, err := network.socket.ReadFromUDP(buffer)
if err != nil {
log.Printf("Listen Error receiving UDP message: %v\n", err) // Only log for debug purposes.
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
continue
}
if length < packetLengthMin {
// Discard packets that do not meet the minimum length.
continue
}
// send the packet to a channel which is processed by multiple workers.
rawPacketsIncoming <- networkWire{network: network, sender: sender, raw: buffer[:length], receiverPublicKey: peerPublicKey}
}
}
// packetWorker handles incoming packets.
func packetWorker(packets <-chan networkWire) {
for packet := range packets {
decoded, senderPublicKey, err := packetDecrypt(packet.raw, packet.receiverPublicKey)
if err != nil {
log.Printf("packetWorker Error decrypting packet from '%s': %s\n", packet.sender.String(), err.Error())
continue
}
// immediately discard message if sender = self
if senderPublicKey.IsEqual(peerPublicKey) {
continue
}
// supported protocol version
if decoded.Protocol != 0 {
continue
}
peer := PeerlistLookup(senderPublicKey)
if peer != nil {
// Existing peers: Update statistics and network address if new
atomic.AddUint64(&peer.StatsPacketReceived, 1)
peer.registerConnection(packet.sender, packet.network)
}
// process the packet
message := &packet2{SenderPublicKey: senderPublicKey, packetRaw: *decoded, network: packet.network, address: packet.sender}
switch decoded.Command {
case 0: // Announce
peer.announcement(message)
case 1: // Response
peer.response(message)
case 10: // Chat [debug]
peer.chat(message)
default: // Unknown command
}
}
}
// GetNetworks returns the list of connected networks
func GetNetworks(networkType int) (networks []*Network) {
switch networkType {
case 4:
return networks4
case 6:
return networks6
}
return nil
}
// GetListen returns connectivity information
func (network *Network) GetListen() (listen *net.UDPAddr, multicastIPv6 net.IP, broadcastIPv4 []net.IP) {
return network.address, network.multicastIP, network.broadcastIPv4
}

149
Packet Encoding.go Normal file
View File

@@ -0,0 +1,149 @@
/*
File Name: Packet Encoding.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
Basic packet structure of ALL packets:
Offset Size Info
0 4 Nonce
4 1 Protocol version = 0
5 1 Command
6 2 Size of payload data
8 ? Payload
? Randomized garbage
? 65 Signature, ECDSA secp256k1 512-bit + 1 header byte
The peer ID of the sender, which is a ECDSA (secp256k1) 257-bit public key, can be extracted from the ECDSA signature.
The signature is applied on the entire packet, which guarantees that the signature becomes invalid should someone try to forge the receiver (i.e. forward the packet).
Because the signature could be a possible fingerpint, it is encrypted itself.
*/
package core
import (
"encoding/binary"
"errors"
"math/rand"
"github.com/btcsuite/btcd/btcec"
"golang.org/x/crypto/salsa20"
"lukechampine.com/blake3"
)
// packetRaw is a decrypted P2P message
type packetRaw struct {
Protocol uint8 // Protocol version = 0
Command uint8 // 0 = Announcement
Payload []byte // Payload
}
// The minimum packet size is 8 bytes (minimum header size) + 65 bytes (signature)
const packetLengthMin = 8 + signatureSize
const signatureSize = 65
const maxRandomGarbage = 20
// packetDecrypt decrypts the packet, verifies its signature and returns a high-level version of the packet.
func packetDecrypt(raw []byte, receiverPublicKey *btcec.PublicKey) (packet *packetRaw, senderPublicKey *btcec.PublicKey, err error) {
// Packet is assumed to be already checked for minimum length.
// Prepare Salsa20 nonce and key. Nonce = 2x first 4 bytes. For size reasons, only 4 bytes (instead of 8 bytes) is supplied in the packet.
// This could be a risk, but considering we only use the PUBLIC key as decryption key, it is negligible.
nonce := make([]byte, 8)
copy(nonce[0:4], raw[0:4])
copy(nonce[4:8], raw[0:4])
// Verify the signature and extract the public key from it.
var signature [signatureSize]byte
copy(signature[:], raw[len(raw)-signatureSize:])
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
senderPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature[:], hashData(raw[:len(raw)-signatureSize]))
if err != nil {
return nil, nil, err
}
// Decrypt the packet using Salsa20.
bufferDecrypted := make([]byte, len(raw)-signatureSize-4) // full length -signature -nonce
salsa20.XORKeyStream(bufferDecrypted[:], raw[4:len(raw)-signatureSize], nonce, keySalsa)
// copy all fields
packet = &packetRaw{Protocol: bufferDecrypted[0], Command: bufferDecrypted[1]}
sizePayload := binary.LittleEndian.Uint16(bufferDecrypted[2:4])
if int(sizePayload) > len(bufferDecrypted)-4 { // invalid length?
return nil, nil, errors.New("invalid length field")
}
if sizePayload > 0 {
packet.Payload = make([]byte, int(sizePayload))
copy(packet.Payload, bufferDecrypted[4:4+int(sizePayload)])
}
return packet, senderPublicKey, nil
}
// packetEncrypt encrypts a packet using the provided senders private key and receivers compressed public key.
func packetEncrypt(senderPrivateKey *btcec.PrivateKey, receiverPublicKey *btcec.PublicKey, packet *packetRaw) (raw []byte, err error) {
garbage := packetGarbage(packetLengthMin + len(packet.Payload))
raw = make([]byte, packetLengthMin+len(packet.Payload)+len(garbage))
nonceC := rand.Uint32()
nonce := make([]byte, 8)
binary.LittleEndian.PutUint32(nonce[0:4], nonceC)
binary.LittleEndian.PutUint32(nonce[4:8], nonceC)
copy(raw[0:4], nonce[0:4])
raw[4] = packet.Protocol
raw[5] = packet.Command
binary.LittleEndian.PutUint16(raw[6:8], uint16(len(packet.Payload)))
copy(raw[8:], packet.Payload)
copy(raw[8+len(packet.Payload):8+len(packet.Payload)+len(garbage)], garbage)
// encrypt it using Salsa20
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
salsa20.XORKeyStream(raw[4:8+len(packet.Payload)+len(garbage)], raw[4:8+len(packet.Payload)+len(garbage)], nonce, keySalsa)
// add signature
signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, hashData(raw[:len(raw)-signatureSize]), true)
if err != nil {
return nil, err
}
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
copy(raw[len(raw)-signatureSize:], signature)
return raw, nil
}
func packetGarbage(packetLength int) (random []byte) {
// Align maximum length at 508 bytes (UDP minimum no fragmentation) and 1472 bytes (MTU).
maxLength := maxRandomGarbage
switch {
case packetLength == 508, packetLength == 1472:
return nil
case packetLength < 508 && (508-packetLength) < maxRandomGarbage:
maxLength = packetLength - 508
case packetLength < 1472 && (1472-packetLength) < maxRandomGarbage:
maxLength = packetLength - 1472
}
b := make([]byte, rand.Intn(maxLength))
if _, err := rand.Read(b); err != nil {
return nil
}
return b
}
func publicKeyToSalsa20Key(publicKey *btcec.PublicKey) (key *[32]byte) {
// bit 0 from PublicKey.Y is ignored here, but is negligible for this purpose
key = new([32]byte)
copy(key[:], publicKey.SerializeCompressed()[1:])
return key
}
// hashData abstracts the hash function.
func hashData(data []byte) (hash []byte) {
hash32 := blake3.Sum256(data)
return hash32[:]
}

263
Peer ID.go Normal file
View File

@@ -0,0 +1,263 @@
/*
File Name: Peer ID.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
import (
"encoding/hex"
"errors"
"log"
"net"
"os"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcec"
)
// peerID is the current peers ID. It is a ECDSA (secp256k1) 257-bit public key.
var peerPrivateKey *btcec.PrivateKey
var peerPublicKey *btcec.PublicKey
func initPeerID() {
peerList = make(map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo)
// load existing key from config, if available
if len(config.PrivateKey) > 0 {
configPK, err := hex.DecodeString(config.PrivateKey)
if err == nil {
peerPrivateKey, peerPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
return
}
log.Printf("Private key in config is corrupted! Error: %s\n", err.Error())
os.Exit(1)
}
// if the peer ID is empty, create a new user public-private key pair
var err error
peerPrivateKey, peerPublicKey, err = Secp256k1NewPrivateKey()
if err != nil {
log.Printf("Error generating public-private key pairs: %s\n", err.Error())
os.Exit(1)
}
// save the newly generated private key into the config
config.PrivateKey = hex.EncodeToString(peerPublicKey.SerializeCompressed())
saveConfig()
}
// Secp256k1NewPrivateKey creates a new public-private key pair
func Secp256k1NewPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey, err error) {
key, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, nil, err
}
return key, (*btcec.PublicKey)(&key.PublicKey), nil
}
// ExportPrivateKey returns the peers public and private key
func ExportPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey) {
return peerPrivateKey, peerPublicKey
}
// Connection is an established connection between a remote IP address and a local network adapter.
// New connections may only be created in case of successful INCOMING packets.
type Connection struct {
Network *Network // network which received the packet
Address *net.UDPAddr // address of the sender or receiver
}
// PeerInfo stores information about a single remote peer
type PeerInfo struct {
PublicKey *btcec.PublicKey // Public key
Connections []*Connection // list of established connections to the peer
connectionLatest *Connection // Latest valid connection. Allows to switch on the fly between connections; if one becomes invalid, try others.
// statistics
StatsPacketSent uint64 // Count of packets sent
StatsPacketReceived uint64 // Count of packets received
}
var peerList map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo
var peerlistMutex sync.RWMutex
// PeerlistAdd adds a new peer to the peer list. It does not validate the peer info. If the peer is already added, it does nothing.
func PeerlistAdd(PublicKey *btcec.PublicKey, connections ...*Connection) (peer *PeerInfo, added bool) {
if len(connections) == 0 {
return nil, false
}
peerlistMutex.Lock()
defer peerlistMutex.Unlock()
peer, ok := peerList[publicKey2Compressed(PublicKey)]
if ok {
return peer, false
}
peer = &PeerInfo{PublicKey: PublicKey, Connections: connections, connectionLatest: connections[0]}
peerList[publicKey2Compressed(peer.PublicKey)] = peer
return peer, true
}
// PeerlistRemove removes a peer from the peer list.
func PeerlistRemove(peer *PeerInfo) {
peerlistMutex.Lock()
defer peerlistMutex.Unlock()
delete(peerList, publicKey2Compressed(peer.PublicKey))
}
// PeerlistGet returns the full peer list
func PeerlistGet() (peers []*PeerInfo) {
peerlistMutex.RLock()
defer peerlistMutex.RUnlock()
for _, peer := range peerList {
peers = append(peers, peer)
}
return peers
}
// PeerlistLookup returns the peer from the list with the public key
func PeerlistLookup(publicKey *btcec.PublicKey) (peer *PeerInfo) {
peerlistMutex.RLock()
defer peerlistMutex.RUnlock()
peer, _ = peerList[publicKey2Compressed(publicKey)]
return peer
}
// PeerlistCount returns the current count of peers in the peer list
func PeerlistCount() (count int) {
peerlistMutex.RLock()
defer peerlistMutex.RUnlock()
return len(peerList)
}
func publicKey2Compressed(publicKey *btcec.PublicKey) [btcec.PubKeyBytesLenCompressed]byte {
var key [btcec.PubKeyBytesLenCompressed]byte
copy(key[:], publicKey.SerializeCompressed())
return key
}
// send sends a raw packet to the peer
func (peer *PeerInfo) send(packet *packetRaw) (err error) {
if len(peer.Connections) == 0 {
return errors.New("no valid connection to peer")
}
packet.Protocol = 0
raw, err := packetEncrypt(peerPrivateKey, peer.PublicKey, packet)
if err != nil {
return err
}
atomic.AddUint64(&peer.StatsPacketSent, 1)
// Send out the wire. Use connectionLatest if available.
// Failover: If sending fails and there are other connections available, try those. Automatically update connectionLatest if one is successful.
// Windows: This works great on if the adapter is disabled, however, does not detect if the network cable is unplugged.
if peer.connectionLatest != nil {
useConnection := peer.connectionLatest
if err = useConnection.Network.send(useConnection.Address.IP, useConnection.Address.Port, raw); err != nil && len(peer.Connections) > 1 {
err = peer.sendFailover(useConnection, raw)
}
return err
}
if err = peer.Connections[0].Network.send(peer.Connections[0].Address.IP, peer.Connections[0].Address.Port, raw); err != nil && len(peer.Connections) > 1 {
err = peer.sendFailover(peer.Connections[0], raw)
}
return err
}
// sendFailover tries to send the packet over any other different (than the failed) connection.
// If send works, it will switch over connectionLatest.
// Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context".
func (peer *PeerInfo) sendFailover(failed *Connection, raw []byte) (err error) {
for _, connection := range peer.Connections {
if connection != failed {
err = connection.Network.send(connection.Address.IP, connection.Address.Port, raw)
if err == nil {
peer.connectionLatest = connection
return nil
}
}
}
return err
}
// sendAllNetworks sends a raw packet via all networks
func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *packetRaw, remote *net.UDPAddr) (err error) {
packet.Protocol = 0
raw, err := packetEncrypt(peerPrivateKey, receiverPublicKey, packet)
if err != nil {
return err
}
successCount := 0
if IsIPv6(remote.IP.To16()) {
for _, network := range networks6 {
// Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local)
if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
continue
}
err = network.send(remote.IP, remote.Port, raw)
if err == nil {
successCount++
}
}
} else {
for _, network := range networks4 {
// Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local)
if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
continue
}
err = network.send(remote.IP, remote.Port, raw)
if err == nil {
successCount++
}
}
}
if successCount == 0 {
return errors.New("no successful send")
}
return nil
}
// registerConnection registers an incoming connection for an existing peer. If new, it will add to the list.
// Currently there is only a connectionLatest indicating the latest valid connection. In the future other priority such as last packet time, speed, local vs remote IP might be used.
func (peer *PeerInfo) registerConnection(remote *net.UDPAddr, network *Network) {
for n := range peer.Connections {
if peer.Connections[n].Address.IP.Equal(remote.IP) && peer.Connections[n].Network.address.IP.Equal(network.address.IP) {
// Connection already established. Verify port and update if necessary.
// Some NATs may rotate ports. Some mobile phone providers even rotate IPs which is not detected here.
if peer.Connections[n].Address.Port != remote.Port {
peer.Connections[n].Address.Port = remote.Port
}
peer.connectionLatest = peer.Connections[n]
return
}
}
peer.Connections = append(peer.Connections, &Connection{Address: remote, Network: network})
peer.connectionLatest = peer.Connections[len(peer.Connections)-1]
}

22
Peernet.go Normal file
View File

@@ -0,0 +1,22 @@
/*
File Name: Peernet.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
func init() {
loadConfig()
initPeerID()
initMulticastIPv6()
initBroadcastIPv4()
initNetwork()
initSeedList()
}
// Connect starts bootstrapping and local peer discovery.
func Connect() {
go bootstrap()
go autoMulticastBroadcast()
}

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# Peernet Core
The core library which is needed for any Peernet application. It provides connectivity to the network and all basic functions.
Current version: 0.1 (pre-alpha)
## Encryption and Hashing functions
* Salsa20 is used for encrypting the packets.
* secp256k1 is used to generate the peer IDs (public keys).
* blake3 is used for hashing the packets when signing.
## Dependencies
Before compiling, make sure to download and update all 3rd party packages:
```
go get -u github.com/gofrs/uuid
go get -u github.com/btcsuite/btcd/btcec
go get -u github.com/libp2p/go-reuseport
```
## Configuration
Peernet follows a "zeroconf" approach, meaning there is no manual configuration required. However, in certain cases such as providing root peers [1] that shall listen on a fixed IP and port, it is desirable to create a config file.
The name of the config file is hard-coded to `Settings.yaml`. If it does not exist, it will be created with default values. It uses the YAML format. Any public/private keys in the config are hex encoded. Here are some notable settings:
* `ListenWorkers` defines the count of concurrent workers processing packets (decrypting them and then taking action). Default 2.
* `Listen` defines IP:Port combinations to listen on. If not specified, it will listen on all IPs. You can specify an IP but port 0 for auto port selection. IPv6 addresses must be in the format "[IPv6]:Port".
[1] Root peer = A peer operated by a known trusted entity. They allow to speed up the network including discovery of peers and data.
## Contributing
Please note that by contributing code, documentation, ideas, snippets, or any other intellectual property you agree that you have all the necessary rights and you agree that we, the Peernet Foundation, may use it for any purpose.
&copy; 2021 Peernet Foundation

83
Settings.go Normal file
View File

@@ -0,0 +1,83 @@
/*
File Name: Settings.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package core
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"gopkg.in/yaml.v3"
)
const configFile = "Settings.yaml"
const Version = "0.1"
var config struct {
LogFile string `yaml:"LogFile"` // Log file
Listen []string `yaml:"Listen"` // IP:Port combinations
ListenWorkers int `yaml:"ListenWorkers"` // Count of workers to process incoming raw packets. Default 2.
// User specific settings
PrivateKey string `yaml:"PrivateKey"` // The Private Key, hex encoded so it can be copied manually
// Initial peer seed list
SeedList []peerSeed `yaml:"SeedList"`
}
// peerSeed is a singl peer entry from the config's seed list
type peerSeed struct {
PublicKey string `yaml:"PublicKey"` // Public key = peer ID. Hex encoded.
Address []string `yaml:"Address"` // IP:Port
}
// loadConfig reads the YAML configuration file
func loadConfig() {
cfg, err := ioutil.ReadFile(configFile)
if err != nil && strings.Contains(err.Error(), "system cannot find the file specified") {
// Does not exist. Use default values.
config.LogFile = "Log.txt"
} else if err != nil {
fmt.Printf("Configuration file '%s' could not be read. Error: %s\n", configFile, err.Error())
os.Exit(1)
} else {
// read existing config
err = yaml.Unmarshal(cfg, &config)
if err != nil {
fmt.Printf("Configuration file '%s' could not be read. Please make sure it contains valid YAML data. Error: %v\n", configFile, err.Error())
os.Exit(1)
}
}
// redirect all output to the log file
logFile, err := os.OpenFile(config.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("Error creating log file '%s': %v\n", config.LogFile, err)
}
// defer logFile.Close() // has to remain open until program closes
log.SetOutput(logFile)
log.Printf("---- Peernet Command-Line Client " + Version + " ----\n")
}
func saveConfig() {
data, err := yaml.Marshal(config)
if err != nil {
log.Printf("saveConfig Error marshalling config: %v\n", err.Error())
return
}
err = ioutil.WriteFile(configFile, data, 0644)
if err != nil {
log.Printf("saveConfig Error writing config '%s': %v\n", configFile, err.Error())
return
}
}