commit inicial do projeto

This commit is contained in:
Júnior
2025-05-23 10:44:32 -03:00
commit 8f04473c0b
106 changed files with 5673 additions and 0 deletions

52
internal/p2p/dht.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}