commit inicial do projeto
This commit is contained in:
52
internal/p2p/dht.go
Normal file
52
internal/p2p/dht.go
Normal file
@ -0,0 +1,52 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
dht "github.com/libp2p/go-libp2p-kad-dht"
|
||||
routingdiscovery "github.com/libp2p/go-libp2p/p2p/discovery/routing"
|
||||
)
|
||||
|
||||
const rendezvousString = "dejo-global"
|
||||
|
||||
// InitDHT inicializa o Kademlia DHT e ativa descoberta global de peers.
|
||||
func (p *P2PNode) InitDHT(ctx context.Context) error {
|
||||
d, err := dht.New(ctx, p.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erro ao iniciar DHT: %w", err)
|
||||
}
|
||||
|
||||
if err := d.Bootstrap(ctx); err != nil {
|
||||
return fmt.Errorf("erro ao bootstrap DHT: %w", err)
|
||||
}
|
||||
|
||||
routing := routingdiscovery.NewRoutingDiscovery(d)
|
||||
_, err = routing.Advertise(ctx, rendezvousString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erro ao anunciar no DHT: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("📡 Anunciado no DHT com tag:", rendezvousString)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
peers, err := routing.FindPeers(ctx, rendezvousString)
|
||||
if err != nil {
|
||||
fmt.Println("Erro ao buscar peers:", err)
|
||||
continue
|
||||
}
|
||||
for pinfo := range peers {
|
||||
if pinfo.ID == p.Host.ID() {
|
||||
continue // ignora si mesmo
|
||||
}
|
||||
fmt.Println("🌍 Peer encontrado via DHT:", pinfo.ID)
|
||||
_ = p.Host.Connect(ctx, pinfo)
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
66
internal/p2p/host.go
Normal file
66
internal/p2p/host.go
Normal file
@ -0,0 +1,66 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
crypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
hostlib "github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// NewSecureHost cria um nó P2P com proteção básica anti-Sybil/DDoS
|
||||
func NewSecureHost(port int) (hostlib.Host, error) {
|
||||
// Gera par de chaves (identidade)
|
||||
priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 2048, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("falha ao gerar chave: %v", err)
|
||||
}
|
||||
|
||||
pid, err := peer.IDFromPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erro ao obter PeerID: %v", err)
|
||||
}
|
||||
|
||||
addr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port))
|
||||
h, err := libp2p.New(
|
||||
libp2p.ListenAddrs(addr),
|
||||
libp2p.Identity(priv),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erro ao criar host libp2p: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ Host P2P criado: %s (%s)\n", pid.String(), addr)
|
||||
|
||||
// Proteção: loga e valida conexões
|
||||
h.Network().Notify(&connLogger{})
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// connLogger registra e controla conexões suspeitas
|
||||
type connLogger struct{}
|
||||
|
||||
func (c *connLogger) Connected(n network.Network, conn network.Conn) {
|
||||
if err := LimitConnections(conn); err != nil {
|
||||
log.Printf("🚫 Conexão rejeitada: %s (%v)", conn.RemotePeer(), err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
log.Printf("🔗 Peer conectado: %s (%s)", conn.RemotePeer(), conn.RemoteMultiaddr())
|
||||
}
|
||||
|
||||
func (c *connLogger) Disconnected(n network.Network, conn network.Conn) {
|
||||
log.Printf("❌ Peer desconectado: %s", conn.RemotePeer())
|
||||
ClearConnection(conn)
|
||||
}
|
||||
|
||||
func (c *connLogger) OpenedStream(n network.Network, s network.Stream) {}
|
||||
func (c *connLogger) ClosedStream(n network.Network, s network.Stream) {}
|
||||
func (c *connLogger) Listen(n network.Network, addr ma.Multiaddr) {}
|
||||
func (c *connLogger) ListenClose(n network.Network, addr ma.Multiaddr) {}
|
||||
58
internal/p2p/limiter.go
Normal file
58
internal/p2p/limiter.go
Normal file
@ -0,0 +1,58 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxConnsPerIP = 5
|
||||
MinReconnectGap = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
ipConnCount = make(map[string]int)
|
||||
peerLastSeen = make(map[string]time.Time)
|
||||
limiterMu sync.Mutex
|
||||
)
|
||||
|
||||
// LimitConnections implementa proteção básica anti-DDoS/Sybil
|
||||
func LimitConnections(conn network.Conn) error {
|
||||
limiterMu.Lock()
|
||||
defer limiterMu.Unlock()
|
||||
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteMultiaddr().String())
|
||||
if err != nil {
|
||||
return nil // fallback: não bloqueia
|
||||
}
|
||||
|
||||
ipConnCount[ip]++
|
||||
if ipConnCount[ip] > MaxConnsPerIP {
|
||||
return network.ErrReset
|
||||
}
|
||||
|
||||
peerID := conn.RemotePeer().String()
|
||||
last := peerLastSeen[peerID]
|
||||
if time.Since(last) < MinReconnectGap {
|
||||
return network.ErrReset
|
||||
}
|
||||
peerLastSeen[peerID] = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearConnection cleanup quando o peer desconecta
|
||||
func ClearConnection(conn network.Conn) {
|
||||
limiterMu.Lock()
|
||||
defer limiterMu.Unlock()
|
||||
ip, _, err := net.SplitHostPort(conn.RemoteMultiaddr().String())
|
||||
if err == nil {
|
||||
ipConnCount[ip]--
|
||||
if ipConnCount[ip] <= 0 {
|
||||
delete(ipConnCount, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
internal/p2p/network.go
Normal file
16
internal/p2p/network.go
Normal file
@ -0,0 +1,16 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// StartNetwork inicializa a rede P2P com host seguro
|
||||
func StartNetwork(port int) {
|
||||
h, err := NewSecureHost(port)
|
||||
if err != nil {
|
||||
log.Fatalf("Erro ao iniciar host P2P: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("🌐 Rede P2P inicializada com sucesso: PeerID %s\n", h.ID())
|
||||
select {} // Mantém processo vivo
|
||||
}
|
||||
81
internal/p2p/p2p.go
Normal file
81
internal/p2p/p2p.go
Normal file
@ -0,0 +1,81 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
host "github.com/libp2p/go-libp2p/core/host"
|
||||
libp2p "github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
const ProtocolID = "/dejo/1.0.0"
|
||||
|
||||
// P2PNode representa um nó da rede P2P da DEJO.
|
||||
type P2PNode struct {
|
||||
Host host.Host
|
||||
PeerChan chan peer.AddrInfo
|
||||
}
|
||||
|
||||
// NewP2PNode cria um novo nó libp2p.
|
||||
func NewP2PNode(ctx context.Context) (*P2PNode, error) {
|
||||
priv, _, _ := crypto.GenerateKeyPairWithReader(crypto.Ed25519, -1, rand.Reader)
|
||||
|
||||
h, err := libp2p.New(
|
||||
libp2p.Identity(priv),
|
||||
libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := &P2PNode{
|
||||
Host: h,
|
||||
PeerChan: make(chan peer.AddrInfo),
|
||||
}
|
||||
|
||||
// Handler para conexões recebidas
|
||||
h.SetStreamHandler(ProtocolID, func(s network.Stream) {
|
||||
buf := make([]byte, 1024)
|
||||
s.Read(buf)
|
||||
fmt.Printf("📡 Recebido: %s\n", string(buf))
|
||||
s.Close()
|
||||
})
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// HandlePeerFound é chamado quando um novo peer é descoberto.
|
||||
func (p *P2PNode) HandlePeerFound(pi peer.AddrInfo) {
|
||||
fmt.Println("👋 Novo peer descoberto:", pi.ID)
|
||||
p.PeerChan <- pi
|
||||
}
|
||||
|
||||
// ConnectToPeers tenta se conectar aos peers encontrados.
|
||||
func (p *P2PNode) ConnectToPeers(ctx context.Context) {
|
||||
go func() {
|
||||
for pi := range p.PeerChan {
|
||||
if err := p.Host.Connect(ctx, pi); err != nil {
|
||||
fmt.Println("Erro ao conectar ao peer:", err)
|
||||
} else {
|
||||
fmt.Println("🔗 Conectado ao peer:", pi.ID)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Broadcast envia uma mensagem para todos os peers conectados.
|
||||
func (p *P2PNode) Broadcast(msg string) {
|
||||
for _, c := range p.Host.Network().Peers() {
|
||||
stream, err := p.Host.NewStream(context.Background(), c, ProtocolID)
|
||||
if err != nil {
|
||||
fmt.Println("Erro ao abrir stream:", err)
|
||||
continue
|
||||
}
|
||||
stream.Write([]byte(msg))
|
||||
stream.Close()
|
||||
}
|
||||
}
|
||||
35
internal/p2p/p2p_test.go
Normal file
35
internal/p2p/p2p_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package p2p_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dejo_node/internal/p2p"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestP2PNode_DHT(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodeA, err := p2p.NewP2PNode(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("falha ao criar node A: %v", err)
|
||||
}
|
||||
nodeA.ConnectToPeers(ctx)
|
||||
if err := nodeA.InitDHT(ctx); err != nil {
|
||||
t.Fatalf("erro ao iniciar DHT no node A: %v", err)
|
||||
}
|
||||
|
||||
nodeB, err := p2p.NewP2PNode(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("falha ao criar node B: %v", err)
|
||||
}
|
||||
nodeB.ConnectToPeers(ctx)
|
||||
if err := nodeB.InitDHT(ctx); err != nil {
|
||||
t.Fatalf("erro ao iniciar DHT no node B: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
nodeB.Broadcast("msg: test from B to A")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
Reference in New Issue
Block a user