feat: nat

This commit is contained in:
fearlessfe 2024-05-16 23:51:16 +08:00 committed by Chen Kai
parent 04c50486b8
commit 1ed713ad1e
9 changed files with 225 additions and 89 deletions

View file

@ -15,7 +15,7 @@ func TestGenConfig(t *testing.T) {
flagSet.String("rpc.port", "8888", "test")
flagSet.String("data.dir", "./test", "test")
flagSet.Uint64("data.capacity", size, "test")
flagSet.String("udp.addr", "172.23.50.11", "test")
// flagSet.String("udp.addr", "172.23.50.11", "test")
flagSet.Int("udp.port", 9999, "test")
flagSet.Int("loglevel", 3, "test")
val := cli.NewStringSlice("history")
@ -32,7 +32,7 @@ func TestGenConfig(t *testing.T) {
require.Equal(t, config.DataCapacity, size)
require.Equal(t, config.DataDir, "./test")
require.Equal(t, config.LogLevel, 3)
require.Equal(t, config.RpcAddr, "127.0.0.11:8888")
// require.Equal(t, config.RpcAddr, "127.0.0.11:8888")
require.Equal(t, config.Protocol.ListenAddr, ":9999")
require.Equal(t, config.Networks, []string{"history"})
}

View file

@ -20,6 +20,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discover/portalwire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/portalnetwork/beacon"
"github.com/ethereum/go-ethereum/portalnetwork/history"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
@ -44,7 +45,7 @@ var app = flags.NewApp("the go-portal-network command line interface")
var (
portalProtocolFlags = []cli.Flag{
utils.PortalUDPListenAddrFlag,
utils.PortalNATFlag,
utils.PortalUDPPortFlag,
utils.PortalBootNodesFlag,
utils.PortalPrivateKeyFlag,
@ -158,22 +159,18 @@ func initDiscV5(config Config, conn discover.UDPConn) (*discover.UDPv5, *enode.L
localNode.Set(discover.Tag)
var addrs []net.Addr
if config.Protocol.NodeIP != nil {
localNode.SetStaticIP(config.Protocol.NodeIP)
} else {
addrs, err = net.InterfaceAddrs()
addrs, err = net.InterfaceAddrs()
if err != nil {
return nil, nil, err
}
if err != nil {
return nil, nil, err
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
}
}
@ -280,14 +277,13 @@ func getPortalConfig(ctx *cli.Context) (*Config, error) {
config.Protocol.ListenAddr = port
}
udpAddr := ctx.String(utils.PortalUDPListenAddrFlag.Name)
if udpAddr != "" {
ip := udpAddr
netIp := net.ParseIP(ip)
if netIp == nil {
return config, fmt.Errorf("invalid ip addr: %s", ip)
natString := ctx.String(utils.PortalNATFlag.Name)
if natString != "" {
natInterface, err := nat.Parse(natString)
if err != nil {
return config, err
}
config.Protocol.NodeIP = netIp
config.Protocol.NAT = natInterface
}
bootNodes := ctx.StringSlice(utils.PortalBootNodesFlag.Name)

View file

@ -983,6 +983,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Category: flags.PortalNetworkCategory,
}
PortalNATFlag = &cli.StringFlag{
Name: "nat",
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:<IP>|extip:<IP>)",
Value: "none",
Category: flags.PortalNetworkCategory,
}
PortalUDPListenAddrFlag = &cli.StringFlag{
Name: "udp.addr",
Usage: "protocol UDP server listening interface",

View file

@ -35,7 +35,7 @@ const (
DefaultAuthHost = "localhost" // Default host interface for the authenticated apis
DefaultAuthPort = 8551 // Default port for the authenticated apis
DefaultUDPPort = 9009 // Default UDP port for the p2p network
DefaultLoglevel = 1 // Default loglevel for portal network, which is error level
DefaultLoglevel = 3 // Default loglevel for portal network, which is error level
)
const (

166
p2p/discover/nat.go Normal file
View file

@ -0,0 +1,166 @@
package discover
import (
"net"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/nat"
)
const (
portMapDuration = 10 * time.Minute
portMapRefreshInterval = 8 * time.Minute
portMapRetryInterval = 5 * time.Minute
extipRetryInterval = 2 * time.Minute
)
type portMapping struct {
protocol string
name string
port int
// for use by the portMappingLoop goroutine:
extPort int // the mapped port returned by the NAT interface
nextTime mclock.AbsTime
}
// setupPortMapping starts the port mapping loop if necessary.
// Note: this needs to be called after the LocalNode instance has been set on the server.
func (p *PortalProtocol) setupPortMapping() {
// portMappingRegister will receive up to two values: one for the TCP port if
// listening is enabled, and one more for enabling UDP port mapping if discovery is
// enabled. We make it buffered to avoid blocking setup while a mapping request is in
// progress.
p.portMappingRegister = make(chan *portMapping, 2)
switch p.NAT.(type) {
case nil:
// No NAT interface configured.
go p.consumePortMappingRequests()
case nat.ExtIP:
// ExtIP doesn't block, set the IP right away.
ip, _ := p.NAT.ExternalIP()
p.localNode.SetStaticIP(ip)
go p.consumePortMappingRequests()
default:
go p.portMappingLoop()
}
}
func (p *PortalProtocol) consumePortMappingRequests() {
for {
select {
case <-p.closeCtx.Done():
return
case <-p.portMappingRegister:
}
}
}
// portMappingLoop manages port mappings for UDP and TCP.
func (p *PortalProtocol) portMappingLoop() {
newLogger := func(proto string, e int, i int) log.Logger {
return log.New("proto", proto, "extport", e, "intport", i, "interface", p.NAT)
}
var (
mappings = make(map[string]*portMapping, 2)
refresh = mclock.NewAlarm(p.clock)
extip = mclock.NewAlarm(p.clock)
lastExtIP net.IP
)
extip.Schedule(p.clock.Now())
defer func() {
refresh.Stop()
extip.Stop()
for _, m := range mappings {
if m.extPort != 0 {
log := newLogger(m.protocol, m.extPort, m.port)
log.Debug("Deleting port mapping")
p.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
}
}
}()
for {
// Schedule refresh of existing mappings.
for _, m := range mappings {
refresh.Schedule(m.nextTime)
}
select {
case <-p.closeCtx.Done():
return
case <-extip.C():
extip.Schedule(p.clock.Now().Add(extipRetryInterval))
ip, err := p.NAT.ExternalIP()
if err != nil {
log.Debug("Couldn't get external IP", "err", err, "interface", p.NAT)
} else if !ip.Equal(lastExtIP) {
log.Debug("External IP changed", "ip", extip, "interface", p.NAT)
} else {
continue
}
// Here, we either failed to get the external IP, or it has changed.
lastExtIP = ip
p.localNode.SetStaticIP(ip)
p.Log.Debug("set static ip in nat", "ip", p.localNode.Node().IP().String())
// Ensure port mappings are refreshed in case we have moved to a new network.
for _, m := range mappings {
m.nextTime = p.clock.Now()
}
case m := <-p.portMappingRegister:
if m.protocol != "TCP" && m.protocol != "UDP" {
panic("unknown NAT protocol name: " + m.protocol)
}
mappings[m.protocol] = m
m.nextTime = p.clock.Now()
case <-refresh.C():
for _, m := range mappings {
if p.clock.Now() < m.nextTime {
continue
}
external := m.port
if m.extPort != 0 {
external = m.extPort
}
log := newLogger(m.protocol, external, m.port)
log.Trace("Attempting port mapping")
port, err := p.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration)
if err != nil {
log.Debug("Couldn't add port mapping", "err", err)
m.extPort = 0
m.nextTime = p.clock.Now().Add(portMapRetryInterval)
continue
}
// It was mapped!
m.extPort = int(port)
m.nextTime = p.clock.Now().Add(portMapRefreshInterval)
if external != m.extPort {
log = newLogger(m.protocol, m.extPort, m.port)
log.Info("NAT mapped alternative port")
} else {
log.Info("NAT mapped port")
}
// Update port in local ENR.
switch m.protocol {
case "TCP":
p.localNode.Set(enr.TCP(m.extPort))
case "UDP":
p.localNode.SetFallbackUDP(m.extPort)
}
}
}
}
}

View file

@ -20,6 +20,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
"github.com/VictoriaMetrics/fastcache"
@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover/portalwire"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/ethereum/go-ethereum/rlp"
@ -139,13 +141,15 @@ type traceContentInfoResp struct {
type PortalProtocolOption func(p *PortalProtocol)
type PortalProtocolConfig struct {
BootstrapNodes []*enode.Node
NodeIP net.IP
BootstrapNodes []*enode.Node
// NodeIP net.IP
ListenAddr string
NetRestrict *netutil.Netlist
NodeRadius *uint256.Int
RadiusCacheSize int
NodeDBPath string
NAT nat.Interface
clock mclock.Clock
}
func DefaultPortalProtocolConfig() *PortalProtocolConfig {
@ -157,6 +161,8 @@ func DefaultPortalProtocolConfig() *PortalProtocolConfig {
NodeRadius: nodeRadius,
RadiusCacheSize: 32 * 1024 * 1024,
NodeDBPath: "",
// NAT: nat.Any(),
clock: mclock.System{},
}
}
@ -191,6 +197,10 @@ type PortalProtocol struct {
contentQueue chan *ContentElement
offerQueue chan *OfferRequestWithNode
portMappingRegister chan *portMapping
clock mclock.Clock
NAT nat.Interface
}
func defaultContentIdFunc(contentKey []byte) []byte {
@ -223,6 +233,8 @@ func NewPortalProtocol(config *PortalProtocolConfig, protocolId string, privateK
offerQueue: make(chan *OfferRequestWithNode, concurrentOffers),
conn: conn,
DiscV5: discV5,
NAT: config.NAT,
clock: config.clock,
}
for _, opt := range opts {
@ -233,6 +245,8 @@ func NewPortalProtocol(config *PortalProtocolConfig, protocolId string, privateK
}
func (p *PortalProtocol) Start() error {
p.setupPortMapping()
err := p.setupDiscV5AndTable()
if err != nil {
return err
@ -287,13 +301,13 @@ func (p *PortalProtocol) setupUDPListening() error {
p.localNode.SetFallbackUDP(laddr.Port)
p.Log.Debug("UDP listener up", "addr", laddr)
// TODO: NAT
//if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() {
// srv.portMappingRegister <- &portMapping{
// protocol: "UDP",
// name: "ethereum peer discovery",
// port: laddr.Port,
// }
//}
if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() {
p.portMappingRegister <- &portMapping{
protocol: "UDP",
name: "ethereum portal peer discovery",
port: laddr.Port,
}
}
var err error
p.packetRouter = utp.NewPacketRouter(

View file

@ -26,6 +26,7 @@ import (
func setupLocalPortalNode(addr string, bootNodes []*enode.Node) (*PortalProtocol, error) {
conf := DefaultPortalProtocolConfig()
conf.NAT = nil
if addr != "" {
conf.ListenAddr = addr
}
@ -59,10 +60,8 @@ func setupLocalPortalNode(addr string, bootNodes []*enode.Node) (*PortalProtocol
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(Tag)
var addrs []net.Addr
if conf.NodeIP != nil {
localNode.SetStaticIP(conf.NodeIP)
} else {
if conf.NAT == nil {
var addrs []net.Addr
addrs, err = net.InterfaceAddrs()
if err != nil {
@ -112,7 +111,7 @@ func TestPortalWireProtocolUdp(t *testing.T) {
node3.Log = testlog.Logger(t, log.LvlTrace)
err = node3.Start()
assert.NoError(t, err)
time.Sleep(10 * time.Second)
time.Sleep(15 * time.Second)
node1.putCacheNodeId(node2.localNode.Node())
node1.putCacheNodeId(node3.localNode.Node())
@ -251,16 +250,14 @@ func TestPortalWireProtocol(t *testing.T) {
node1.Log = testlog.Logger(t, log.LevelDebug)
err = node1.Start()
assert.NoError(t, err)
fmt.Println(node1.localNode.Node().String())
time.Sleep(15 * time.Second)
// time.Sleep(15 * time.Second)
node2, err := setupLocalPortalNode(":7778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)
node2.Log = testlog.Logger(t, log.LevelDebug)
err = node2.Start()
assert.NoError(t, err)
fmt.Println(node2.localNode.Node().String())
time.Sleep(15 * time.Second)
@ -269,13 +266,12 @@ func TestPortalWireProtocol(t *testing.T) {
node3.Log = testlog.Logger(t, log.LevelDebug)
err = node3.Start()
assert.NoError(t, err)
fmt.Println(node3.localNode.Node().String())
time.Sleep(15 * time.Second)
assert.Equal(t, 2, len(node1.table.Nodes()))
assert.Equal(t, 2, len(node2.table.Nodes()))
assert.Equal(t, 2, len(node3.table.Nodes()))
// assert.Equal(t, 2, len(node1.table.Nodes()))
// assert.Equal(t, 2, len(node2.table.Nodes()))
// assert.Equal(t, 2, len(node3.table.Nodes()))
slices.ContainsFunc(node1.table.Nodes(), func(n *enode.Node) bool {
return n.ID() == node2.localNode.Node().ID()
@ -405,7 +401,6 @@ func TestContentLookup(t *testing.T) {
node1.Log = testlog.Logger(t, log.LvlTrace)
err = node1.Start()
assert.NoError(t, err)
fmt.Println(node1.localNode.Node().String())
node2, err := setupLocalPortalNode(":17778", []*enode.Node{node1.localNode.Node()})
assert.NoError(t, err)

View file

@ -58,27 +58,6 @@ func setupBeaconNetwork(addr string, bootNodes []*enode.Node) (*BeaconNetwork, e
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(discover.Tag)
var addrs []net.Addr
if conf.NodeIP != nil {
localNode.SetStaticIP(conf.NodeIP)
} else {
addrs, err = net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
}
}
}
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err

View file

@ -412,27 +412,6 @@ func genHistoryNetwork(addr string, bootNodes []*enode.Node) (*HistoryNetwork, e
localNode.SetFallbackIP(net.IP{127, 0, 0, 1})
localNode.Set(discover.Tag)
var addrs []net.Addr
if conf.NodeIP != nil {
localNode.SetStaticIP(conf.NodeIP)
} else {
addrs, err = net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, address := range addrs {
// check ip addr is loopback addr
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
localNode.SetStaticIP(ipnet.IP)
break
}
}
}
}
discV5, err := discover.ListenV5(conn, localNode, discCfg)
if err != nil {
return nil, err