2021-11-08 16:39:17 +00:00
|
|
|
// Copyright 2017-2021 DERO Project. All rights reserved.
|
|
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
|
|
// license can be found in the LICENSE file.
|
|
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
package p2p
|
|
|
|
|
|
|
|
/* this file implements the peer manager, keeping a list of peers which can be tried for connection etc
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
import "os"
|
|
|
|
import "fmt"
|
|
|
|
import "net"
|
|
|
|
import "sync"
|
|
|
|
import "time"
|
|
|
|
import "errors"
|
|
|
|
|
|
|
|
//import "sort"
|
|
|
|
import "path/filepath"
|
|
|
|
import "encoding/json"
|
|
|
|
|
|
|
|
//import "encoding/binary"
|
|
|
|
//import "container/list"
|
|
|
|
|
|
|
|
//import log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
import "github.com/deroproject/derohe/globals"
|
|
|
|
|
|
|
|
//import "github.com/deroproject/derosuite/crypto"
|
|
|
|
|
|
|
|
// This structure is used to do book keeping for the peer list and keeps other DATA related to peer
|
|
|
|
// all peers are servers, means they have exposed a port for connections
|
|
|
|
// all peers are identified by their endpoint tcp address
|
|
|
|
// all clients are identified by ther peer id ( however ip-address is used to control amount )
|
|
|
|
// the following daemon commands interact with the list
|
|
|
|
// bans := print current ban list
|
|
|
|
// ban address time // ban this address for specific time, if time not provided , default ban for 10 minutes
|
|
|
|
// unban address
|
|
|
|
// enableban address // by default all addresses are bannable
|
|
|
|
// disableban address // this address will never be banned
|
|
|
|
|
|
|
|
var ban_map = map[string]uint64{} // keeps ban maps
|
|
|
|
var ban_mutex sync.Mutex
|
|
|
|
|
|
|
|
// loads peers list from disk
|
|
|
|
func load_ban_list() {
|
|
|
|
go ban_clean_up_goroutine() // start routine to clean up ban list
|
|
|
|
defer ban_clean_up() // cleanup list after loading it
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
|
|
|
|
ban_file := filepath.Join(globals.GetDataDirectory(), "ban_list.json")
|
|
|
|
|
|
|
|
if _, err := os.Stat(ban_file); errors.Is(err, os.ErrNotExist) {
|
|
|
|
return // since file doesn't exist , we cannot load it
|
|
|
|
}
|
|
|
|
file, err := os.Open(ban_file)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "opening ban data file")
|
|
|
|
} else {
|
|
|
|
defer file.Close()
|
|
|
|
decoder := json.NewDecoder(file)
|
|
|
|
err = decoder.Decode(&ban_map)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "Error unmarshalling ban data")
|
|
|
|
} else { // successfully unmarshalled data
|
|
|
|
logger.V(1).Info("Successfully loaded bans from file", "ban_count", (len(ban_map)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//save ban list to disk
|
|
|
|
func save_ban_list() {
|
|
|
|
|
|
|
|
ban_clean_up() // cleanup before saving
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
|
|
|
|
ban_file := filepath.Join(globals.GetDataDirectory(), "ban_list.json")
|
|
|
|
file, err := os.Create(ban_file)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "creating ban data file")
|
|
|
|
} else {
|
|
|
|
defer file.Close()
|
|
|
|
encoder := json.NewEncoder(file)
|
|
|
|
encoder.SetIndent("", "\t")
|
|
|
|
err = encoder.Encode(&ban_map)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error(err, "Error unmarshalling ban data")
|
|
|
|
} else { // successfully unmarshalled data
|
|
|
|
logger.V(1).Info("Successfully saved bans to file", "count", (len(ban_map)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clean up ban list every 20 seconds
|
|
|
|
func ban_clean_up_goroutine() {
|
2021-11-15 08:27:35 +00:00
|
|
|
|
|
|
|
delay := time.NewTicker(20 * time.Second)
|
2021-11-08 16:39:17 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-Exit_Event:
|
|
|
|
return
|
2021-11-15 08:27:35 +00:00
|
|
|
case <-delay.C:
|
2021-11-08 16:39:17 +00:00
|
|
|
}
|
|
|
|
ban_clean_up()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* used for manual simulation once to track a hard to find bug
|
|
|
|
func init() {
|
|
|
|
load_ban_list()
|
|
|
|
Ban_Address("188.191.128.64/24",600)
|
|
|
|
|
|
|
|
IsAddressInBanList("35.185.240.198")
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// clean up by discarding entries which are in the past
|
|
|
|
func ban_clean_up() {
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
|
|
|
|
current_time := uint64(time.Now().UTC().Unix())
|
|
|
|
for k, v := range ban_map {
|
|
|
|
if v < current_time {
|
|
|
|
delete(ban_map, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert address to subnet form
|
|
|
|
// address is an IP address or ipnet in string form
|
|
|
|
func ParseAddress(address string) (ipnet *net.IPNet, result string, err error) {
|
|
|
|
var ip net.IP
|
|
|
|
ip, ipnet, err = net.ParseCIDR(address)
|
|
|
|
if err != nil { // other check whether the the IP is an IP
|
|
|
|
ip = net.ParseIP(address)
|
|
|
|
if ip == nil {
|
|
|
|
err = fmt.Errorf("IP address could not be parsed")
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
err = nil
|
|
|
|
result = ip.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
result = ipnet.String()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// check whether an IP is in the map already
|
|
|
|
// we should loop and search in subnets also
|
|
|
|
// TODO make it fast, however we are not expecting millions of bans, so this may be okay for time being
|
|
|
|
func IsAddressInBanList(address string) bool {
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
|
|
|
|
// any i which cannot be banned should never be banned
|
|
|
|
// this list contains any seed nodes/exclusive nodes/proirity nodes
|
|
|
|
// these are never banned
|
|
|
|
for i := range nonbanlist {
|
|
|
|
if address == nonbanlist[i] {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it's a subnet or direct ip, do instant check
|
|
|
|
if _, ok := ban_map[address]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(address) // user provided a valid ip parse and check subnet
|
|
|
|
if ip != nil {
|
|
|
|
|
|
|
|
// parse and check the subnets
|
|
|
|
for k, _ := range ban_map {
|
|
|
|
ipnet, _, err := ParseAddress(k)
|
|
|
|
|
|
|
|
// fmt.Printf("parsing address %s err %s checking ip %s",k,err,address)
|
|
|
|
|
|
|
|
// fmt.Printf("\nipnet %+v", ipnet)
|
|
|
|
if ip.String() == k || (err == nil && ipnet != nil && ipnet.Contains(ip)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ban a peer for specific time
|
|
|
|
// manual bans are always placed
|
|
|
|
// address can be an address or a subnet
|
|
|
|
|
|
|
|
func Ban_Address(address string, ban_seconds uint64) (err error) {
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
|
|
|
|
// make sure we are not banning seed nodes/exclusive node/priority nodes on command line
|
|
|
|
_, address, err = ParseAddress(address)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
//logger.Warnf("%s banned for %d secs", address, ban_seconds)
|
|
|
|
ban_map[address] = uint64(time.Now().UTC().Unix()) + ban_seconds
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
/// ban a peer only if it can be banned
|
|
|
|
func Peer_Ban_Internal(address string, ban_seconds uint64) (err error){
|
|
|
|
p := GetPeerInList(address)
|
|
|
|
if p == nil {
|
|
|
|
return fmt.Errorf("Peer \"%s\" not found in list")
|
|
|
|
}
|
|
|
|
p.Lock()
|
|
|
|
defer p.Unlock()
|
|
|
|
if p.NeverBlacklist == false {
|
|
|
|
p.BlacklistBefore = uint64(time.Now().Unix()) + ban_seconds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// unban a peer for specific time
|
|
|
|
func UnBan_Address(address string) (err error) {
|
|
|
|
if !IsAddressInBanList(address) {
|
|
|
|
return fmt.Errorf("Address \"%s\" not found in ban list", address)
|
|
|
|
}
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
logger.Info("unbanned", "address", address)
|
|
|
|
delete(ban_map, address)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
func Peer_DisableBan(address string) (err error){
|
|
|
|
p := GetPeerInList(address)
|
|
|
|
if p == nil {
|
|
|
|
return fmt.Errorf("Peer \"%s\" not found in list")
|
|
|
|
}
|
|
|
|
p.Lock()
|
|
|
|
defer p.Unlock()
|
|
|
|
p.NeverBlacklist = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func Peer_EnableBan(address string) (err error){
|
|
|
|
p := GetPeerInList(address)
|
|
|
|
if p == nil {
|
|
|
|
return fmt.Errorf("Peer \"%s\" not found in list")
|
|
|
|
}
|
|
|
|
p.Lock()
|
|
|
|
defer p.Unlock()
|
|
|
|
p.NeverBlacklist = false
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// prints all the connection info to screen
|
|
|
|
func BanList_Print() {
|
|
|
|
ban_clean_up() // clean up before printing
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
logger.Info(fmt.Sprintf("Ban List contains %d \n", len(ban_map)))
|
|
|
|
logger.Info(fmt.Sprintf("%-22s %-6s\n", "Addr", "Seconds to unban"))
|
|
|
|
|
|
|
|
for k, v := range ban_map {
|
|
|
|
logger.Info(fmt.Sprintf("%-22s %6d\n", k, v-uint64(time.Now().UTC().Unix())))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function return peer count which have successful handshake
|
|
|
|
func Ban_Count() (Count uint64) {
|
|
|
|
ban_mutex.Lock()
|
|
|
|
defer ban_mutex.Unlock()
|
|
|
|
return uint64(len(ban_map))
|
|
|
|
}
|