Skip to content

Commit

Permalink
Merge pull request ethereum#35 from hadv/sentry-node
Browse files Browse the repository at this point in the history
cmd, p2p: implement sentry node
  • Loading branch information
uprendis authored Aug 3, 2022
2 parents 31e4934 + 61d2a2a commit 4826d95
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 18 deletions.
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ var (
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
utils.NetrestrictFlag,
utils.IPrestrictFlag,
utils.PrivateNodeFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
utils.DNSDiscoveryFlag,
Expand Down
2 changes: 2 additions & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
utils.NetrestrictFlag,
utils.IPrestrictFlag,
utils.PrivateNodeFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
},
Expand Down
16 changes: 16 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,14 @@ var (
Name: "netrestrict",
Usage: "Restricts network communication to the given IP networks (CIDR masks)",
}
IPrestrictFlag = cli.StringFlag{
Name: "iprestrict",
Usage: "Restricts network communication to the given IP addresses",
}
PrivateNodeFlag = cli.StringFlag{
Name: "privatenodes",
Usage: "Comma separated enode URLs which must not be advertised as peers to public network",
}
DNSDiscoveryFlag = cli.StringFlag{
Name: "discovery.dns",
Usage: "Sets DNS discovery entry points (use \"\" to disable DNS)",
Expand Down Expand Up @@ -1197,6 +1205,14 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
cfg.NetRestrict = list
}

if iprestrict := ctx.GlobalString(IPrestrictFlag.Name); iprestrict != "" {
cfg.IPRestrict = netutil.ParseIPs(iprestrict)
}

if privatenodes := ctx.GlobalString(PrivateNodeFlag.Name); privatenodes != "" {
cfg.PrivateNodes = enode.ParseNodes(privatenodes)
}

if ctx.GlobalBool(DeveloperFlag.Name) || ctx.GlobalBool(CatalystFlag.Name) {
// --dev mode can't use p2p networking.
cfg.MaxPeers = 0
Expand Down
8 changes: 8 additions & 0 deletions internal/web3ext/web3ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ web3._extend({
name: 'peers',
getter: 'admin_peers'
}),
new web3._extend.Property({
name: 'privateNodes',
getter: 'admin_privateNodes'
}),
new web3._extend.Property({
name: 'iprestrict',
getter: 'admin_iprestrict'
}),
new web3._extend.Property({
name: 'datadir',
getter: 'admin_datadir'
Expand Down
24 changes: 24 additions & 0 deletions node/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,30 @@ func (api *publicAdminAPI) Datadir() string {
return api.node.DataDir()
}

func (api *publicAdminAPI) PrivateNodes() ([]*p2p.NodeInfo, error) {
server := api.node.Server()
if server == nil {
return nil, ErrNodeStopped
}
infos := make([]*p2p.NodeInfo, 0, len(server.PrivateNodes))
for _, node := range server.PrivateNodes {
infos = append(infos, &p2p.NodeInfo{
ID: node.ID().String(),
Enode: node.URLv4(),
IP: node.IP().String(),
})
}
return infos, nil
}

func (api *publicAdminAPI) Iprestrict() ([]string, error) {
server := api.node.Server()
if server == nil {
return nil, ErrNodeStopped
}
return server.IPRestrict, nil
}

// publicWeb3API offers helper utils
type publicWeb3API struct {
stack *Node
Expand Down
5 changes: 5 additions & 0 deletions p2p/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ var (
errAlreadyListened = errors.New("already listened")
errRecentlyDialed = errors.New("recently dialed")
errNetRestrict = errors.New("not contained in netrestrict list")
errIPRestrict = errors.New("not contained in iprestrict list")
errNoPort = errors.New("node does not provide TCP port")
)

Expand Down Expand Up @@ -135,6 +136,7 @@ type dialConfig struct {
maxDialPeers int // maximum number of dialed peers
maxActiveDials int // maximum number of active dials
netRestrict *netutil.Netlist // IP netrestrict list, disabled if nil
ipRestrict []string // IP address restrict list, disabled if nil
resolver nodeResolver
dialer NodeDialer
log log.Logger
Expand Down Expand Up @@ -405,6 +407,9 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) {
return errNetRestrict
}
if len(d.ipRestrict) > 0 && !contains(d.ipRestrict, n.IP().String()) {
return errIPRestrict
}
if d.history.contains(string(n.ID().Bytes())) {
return errRecentlyDialed
}
Expand Down
35 changes: 35 additions & 0 deletions p2p/dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,41 @@ func TestDialSchedNetRestrict(t *testing.T) {
})
}

func TestDialSchedIPRestrict(t *testing.T) {
t.Parallel()

nodes := []*enode.Node{
newNode(uintID(0x01), "127.0.0.1:30303"),
newNode(uintID(0x02), "127.0.0.2:30303"),
newNode(uintID(0x03), "127.0.0.3:30303"),
newNode(uintID(0x04), "127.0.0.4:30303"),
newNode(uintID(0x05), "127.0.2.5:30303"),
newNode(uintID(0x06), "127.0.2.6:30303"),
newNode(uintID(0x07), "127.0.2.7:30303"),
newNode(uintID(0x08), "127.0.2.8:30303"),
}
config := dialConfig{
maxActiveDials: 10,
maxDialPeers: 10,
}
config.ipRestrict = []string{"127.0.0.1", "127.0.2.8"}
runDialTest(t, config, []dialTestRound{
{
discovered: nodes,
wantNewDials: []*enode.Node{
newNode(uintID(0x01), "127.0.0.1:30303"),
newNode(uintID(0x08), "127.0.2.8:30303"),
},
},
{
succeeded: []enode.ID{
nodes[0].ID(),
nodes[7].ID(),
},
},
})
}

// This test checks that static dials work and obey the limits.
func TestDialSchedStaticDial(t *testing.T) {
t.Parallel()
Expand Down
23 changes: 23 additions & 0 deletions p2p/discover/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type Config struct {

// These settings are optional:
NetRestrict *netutil.Netlist // list of allowed IP networks
IPRestrict []string // list of allowed IP addresses
PrivateNodes []*enode.Node // list of private enodes
Bootnodes []*enode.Node // list of bootstrap nodes
Unhandled chan<- ReadPacket // unhandled packets are sent on this channel
Log log.Logger // if set, log messages go here
Expand Down Expand Up @@ -80,3 +82,24 @@ func min(x, y int) int {
}
return x
}

// contains checks if a string is present in a slice
func has(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}

return false
}

func containsEnode(nodes []*enode.Node, node *enode.Node) bool {
for _, n := range nodes {
if n.IP().Equal(node.IP()) || n.ID() == node.ID() {
return true
}
}

return false
}
29 changes: 20 additions & 9 deletions p2p/discover/v4_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@ const (

// UDPv4 implements the v4 wire protocol.
type UDPv4 struct {
conn UDPConn
log log.Logger
netrestrict *netutil.Netlist
priv *ecdsa.PrivateKey
localNode *enode.LocalNode
db *enode.DB
tab *Table
closeOnce sync.Once
wg sync.WaitGroup
conn UDPConn
log log.Logger
netrestrict *netutil.Netlist
iprestrict []string
privateNodes []*enode.Node
priv *ecdsa.PrivateKey
localNode *enode.LocalNode
db *enode.DB
tab *Table
closeOnce sync.Once
wg sync.WaitGroup

addReplyMatcher chan *replyMatcher
gotreply chan reply
Expand Down Expand Up @@ -133,6 +135,8 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
conn: c,
priv: cfg.PrivateKey,
netrestrict: cfg.NetRestrict,
iprestrict: cfg.IPRestrict,
privateNodes: cfg.PrivateNodes,
localNode: ln,
db: ln.Database(),
gotreply: make(chan reply),
Expand Down Expand Up @@ -585,6 +589,9 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error)
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
return nil, errors.New("not contained in netrestrict list")
}
if len(t.iprestrict) > 0 && !has(t.iprestrict, rn.IP.String()) {
return nil, errors.New("not contained in iprestrict list")
}
key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID)
if err != nil {
return nil, err
Expand Down Expand Up @@ -728,6 +735,10 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
var sent bool
for _, n := range closest {
// Don't advertise the private nodes
if len(t.privateNodes) > 0 && containsEnode(t.privateNodes, &n.Node) {
continue
}
if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
p.Nodes = append(p.Nodes, nodeToRPC(n))
}
Expand Down
14 changes: 14 additions & 0 deletions p2p/discover/v5_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type UDPv5 struct {
conn UDPConn
tab *Table
netrestrict *netutil.Netlist
iprestrict []string
privateNodes []*enode.Node
priv *ecdsa.PrivateKey
localNode *enode.LocalNode
db *enode.DB
Expand Down Expand Up @@ -144,6 +146,8 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
localNode: ln,
db: ln.Database(),
netrestrict: cfg.NetRestrict,
iprestrict: cfg.IPRestrict,
privateNodes: cfg.PrivateNodes,
priv: cfg.PrivateKey,
log: cfg.Log,
validSchemes: cfg.ValidSchemes,
Expand Down Expand Up @@ -407,6 +411,12 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil {
return nil, err
}
if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
return nil, errors.New("not contained in netrestrict list")
}
if len(t.iprestrict) > 0 && !has(t.iprestrict, node.IP().String()) {
return nil, errors.New("not contained in iprestrict list")
}
if c.node.UDP() <= 1024 {
return nil, errLowPort
}
Expand Down Expand Up @@ -810,6 +820,10 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en

// Apply some pre-checks to avoid sending invalid nodes.
for _, n := range bn {
// Don't advertise the private nodes
if len(t.privateNodes) > 0 && containsEnode(t.privateNodes, n) {
continue
}
// TODO livenessChecks > 1
if netutil.CheckRelayIP(rip, n.IP()) != nil {
continue
Expand Down
15 changes: 15 additions & 0 deletions p2p/enode/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) {
return New(validSchemes, &r)
}

func ParseNodes(s string) []*Node {
ws := strings.NewReplacer(" ", "", "\n", "", "\t", "")
urls := strings.Split(ws.Replace(s), ",")
nodes := make([]*Node, 0)
for _, url := range urls {
if url == "" {
continue
}
if node, err := Parse(ValidSchemes, url); err == nil {
nodes = append(nodes, node)
}
}
return nodes
}

// ID returns the node identifier.
func (n *Node) ID() ID {
return n.id
Expand Down
15 changes: 15 additions & 0 deletions p2p/netutil/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ func ParseNetlist(s string) (*Netlist, error) {
return &l, nil
}

func ParseIPs(s string) []string {
ws := strings.NewReplacer(" ", "", "\n", "", "\t", "")
ips := strings.Split(ws.Replace(s), ",")
l := make([]string, 0)
for _, ip := range ips {
if ip == "" {
continue
}
if n := net.ParseIP(ip); n != nil {
l = append(l, n.String())
}
}
return l
}

// MarshalTOML implements toml.MarshalerRec.
func (l Netlist) MarshalTOML() interface{} {
list := make([]string, 0, len(l))
Expand Down
Loading

0 comments on commit 4826d95

Please sign in to comment.