-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The EigenDA Node Reachability Scanner allows Operators to initiate po…
…rt scans from the EigenDA backend to validate e2e reachability. ``` ┌─────────────────────────────────────────────┐ ┌─────────────────────────────────────────────┐ │ │ │ │ │ EigenDA Operator Node │ │ EigenDA Operator Node │ │ │ │ │ │ │ │ │ └───────┬────────▲────────────────▲───────────┘ └───────┬─────────────────────────────────────┘ ┌───────┴────────┴────────────────┴───────────┐ ┌───────┴─────────────────────────────────────┐ │ Operator Firewall Allowing 32005 │ │ Operator Firewall Blocking 32005 │ └───────┬────────┬────────────────┬───────────┘ └───────┬─────────────────────────────────────┘ │ │ │ │ X X PortCheck │ │ PortCheck │ │ Request │ │ Request │ │ 32005 │ │ 32005 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Reachability Dispersal │ Reachability Dispersal │ Check Request │ Check Request │ 32005 32005 │ 32005 32005 ┌───────┴────────┴────────────────┴───────────┐ ┌───────┴────────┴────────────────┴───────────┐ │ Nat Gateway │ │ Nat Gateway │ └───────┬────────┬────────────────┬───────────┘ └───────┬────────┬────────────────┬───────────┘ ┌───────┼────────┼────────────────┼───────────┐ ┌───────┼────────┼────────────────┼───────────┐ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ▼ │ │ │ │ ┌──────────────┴────┐┌──────────┴────────┐ │ │ ┌──────────────┴────┐┌──────────┴────────┐ │ │ │ ││ │ │ │ │ ││ │ │ │ │ scanner ││ disperser │ │ │ │ scanner ││ disperser │ │ │ │ ││ │ │ │ │ ││ │ │ │ └───────────────────┘└───────────────────┘ │ │ └───────────────────┘└───────────────────┘ │ │ EigenDA VPC │ │ EigenDA VPC │ └─────────────────────────────────────────────┘ └─────────────────────────────────────────────┘ ``` ``` NAME: scanner - EigenDA Node Reachability Scanner USAGE: scanner [global options] command [command options] [arguments...] VERSION: 1.0.0-- DESCRIPTION: Service for checking the reachabilty of operator nodes COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --scanner.port value Server listening port (default: "32001") [$SCANNER_PORT] --scanner.metrics-port value Metrics listening port (default: "9091") [$SCANNER_METRICS_PORT] --scanner.timeout value Amount of time to wait for port scan to complete (default: "5s") [$SCANNER_TIMEOUT] --scanner.log.level value The lowest log level that will be output. Accepted options are "debug", "info", "warn", "error" (default: "info") [$SCANNER_LOG_LEVEL] --scanner.log.path value Path to file where logs will be written [$SCANNER_LOG_PATH] --scanner.log.format value The format of the log file. Accepted options are 'json' and 'text' (default: "json") [$SCANNER_LOG_FORMAT] --help, -h show help --version, -v print the version ``` Lint Refactor
- Loading branch information
Showing
14 changed files
with
504 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export SCANNER_LOG_PATH=scanner.log | ||
|
||
export SCANNER_LOG_LEVEL=debug | ||
export SCANNER_LOG_FORMAT=text | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
npc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
build: clean proto | ||
go mod tidy | ||
go build -o ./bin/npc ./cmd | ||
|
||
proto: | ||
protoc --go_out=. npc.proto | ||
protoc --go-grpc_out=. npc.proto | ||
|
||
docker: | ||
cd ../../ && docker build . -t npc -f tools/npc/cmd/Dockerfile | ||
|
||
clean: | ||
rm -rf ./bin ./npc | ||
|
||
lint: | ||
golint -set_exit_status ./... | ||
go tool fix ./.. | ||
golangci-lint run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# EigenDA Node Reachability Scanner | ||
|
||
The EigenDA Node Reachability Scanner allows Operators to initiate port scans from the EigenDA backend to validate e2e reachability. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
FROM golang:1.21.1-alpine3.18 as builder | ||
|
||
RUN apk add --no-cache make musl-dev linux-headers gcc git jq bash | ||
|
||
COPY ./tools/npc /app/tools/npc | ||
COPY api /app/api | ||
COPY common /app/common | ||
COPY go.mod /app | ||
COPY go.sum /app | ||
|
||
WORKDIR /app/tools/npc | ||
|
||
RUN go build -o ./bin/npc ./cmd | ||
|
||
FROM alpine:3.18 | ||
|
||
COPY --from=builder /app/tools/npc/bin/npc /usr/local/bin | ||
|
||
ENTRYPOINT ["npc"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"github.com/urfave/cli" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/tools/npc" | ||
"github.com/Layr-Labs/eigenda/tools/npc/flags" | ||
"github.com/Layr-Labs/eigenda/tools/npc/grpc" | ||
) | ||
|
||
func main() { | ||
app := cli.NewApp() | ||
app.Flags = flags.Flags | ||
app.Version = fmt.Sprintf("%s-%s-%s", npc.SemVer, npc.GitCommit, npc.GitDate) | ||
app.Name = npc.AppName | ||
app.Usage = "EigenDA Node Port Check Scanner " | ||
app.Description = "Service for checking the reachabilty of operator nodes" | ||
|
||
app.Action = NpcMain | ||
err := app.Run(os.Args) | ||
if err != nil { | ||
log.Fatalf("application failed: %v", err) | ||
} | ||
|
||
select {} | ||
} | ||
|
||
// NpcMain func | ||
func NpcMain(ctx *cli.Context) error { | ||
log.Println("Initializing node npc") | ||
config, err := npc.NewConfig(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
logger, err := common.NewLogger(config.LoggerConfig) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Create the npc. | ||
npc, err := npc.NewScanner(config, logger) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = npc.Start(context.Background()) | ||
if err != nil { | ||
npc.Logger.Error("could not start node npc", "error", err) | ||
return err | ||
} | ||
|
||
// Creates the GRPC server. | ||
server := grpc.NewServer(config, npc, logger) | ||
if err := server.Start(); err != nil { | ||
log.Fatalf("Failed to start server: %v", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package npc | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/tools/npc/flags" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
// Npc | ||
const ( | ||
AppName = "npc" | ||
SemVer = "1.0.0" | ||
GitCommit = "" | ||
GitDate = "" | ||
) | ||
|
||
// Config contains all of the configuration information for a npc | ||
type Config struct { | ||
ServerPort string | ||
MetricsPort string | ||
Timeout time.Duration | ||
|
||
LoggerConfig common.LoggerConfig | ||
} | ||
|
||
// NewConfig parses the Config from the provided flags or environment variables and | ||
// returns a Config. | ||
func NewConfig(ctx *cli.Context) (*Config, error) { | ||
timeout, err := time.ParseDuration(ctx.GlobalString(flags.TimeoutFlag.Name)) | ||
if err != nil { | ||
return &Config{}, err | ||
} | ||
|
||
loggerConfig, err := common.ReadLoggerCLIConfig(ctx, flags.FlagPrefix) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &Config{ | ||
ServerPort: ctx.GlobalString(flags.ServerPortFlag.Name), | ||
MetricsPort: ctx.GlobalString(flags.MetricsPortFlag.Name), | ||
Timeout: timeout, | ||
LoggerConfig: *loggerConfig, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package flags | ||
|
||
import ( | ||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
// constants | ||
const ( | ||
FlagPrefix = "npc" | ||
EnvVarPrefix = "NPC" | ||
) | ||
|
||
var ( | ||
/* Required Flags */ | ||
|
||
// ServerPortFlag used to serve requests | ||
ServerPortFlag = cli.StringFlag{ | ||
Name: common.PrefixFlag(FlagPrefix, "port"), | ||
Usage: "Server listening port", | ||
Value: "32001", | ||
Required: false, | ||
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "PORT"), | ||
} | ||
// MetricsPortFlag used to serve metrics | ||
MetricsPortFlag = cli.StringFlag{ | ||
Name: common.PrefixFlag(FlagPrefix, "metrics-port"), | ||
Usage: "Metrics listening port", | ||
Required: false, | ||
Value: "9091", | ||
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "METRICS_PORT"), | ||
} | ||
// TimeoutFlag determines scan timeout | ||
TimeoutFlag = cli.StringFlag{ | ||
Name: common.PrefixFlag(FlagPrefix, "timeout"), | ||
Usage: "Amount of time to wait for port scan to complete", | ||
Required: false, | ||
Value: "5s", | ||
EnvVar: common.PrefixEnvVar(EnvVarPrefix, "TIMEOUT"), | ||
} | ||
) | ||
|
||
var requiredFlags = []cli.Flag{ | ||
ServerPortFlag, | ||
MetricsPortFlag, | ||
TimeoutFlag, | ||
} | ||
|
||
var optionalFlags = []cli.Flag{} | ||
|
||
// init | ||
func init() { | ||
Flags = append(requiredFlags, optionalFlags...) | ||
Flags = append(Flags, common.LoggerCLIFlags(EnvVarPrefix, FlagPrefix)...) | ||
} | ||
|
||
// Flags contains the list of configuration options available to the binary. | ||
var Flags []cli.Flag |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package grpc | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"net" | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigenda/tools/npc" | ||
pb "github.com/Layr-Labs/eigenda/tools/npc/npc/grpc" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
"github.com/Ullaakut/nmap" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/peer" | ||
"google.golang.org/grpc/reflection" | ||
) | ||
|
||
const localhost = "0.0.0.0" | ||
|
||
// Server implements the Npc scanner proto APIs. | ||
type Server struct { | ||
pb.UnimplementedReachabilityServer | ||
|
||
npc *npc.Scanner | ||
config *npc.Config | ||
logger logging.Logger | ||
} | ||
|
||
// NewServer func | ||
func NewServer(config *npc.Config, npc *npc.Scanner, logger logging.Logger) *Server { | ||
return &Server{ | ||
config: config, | ||
logger: logger, | ||
npc: npc, | ||
} | ||
} | ||
|
||
// Start func | ||
func (s *Server) Start() error { | ||
|
||
addr := fmt.Sprintf("%s:%s", localhost, s.config.ServerPort) | ||
lis, err := net.Listen("tcp", addr) | ||
if err != nil { | ||
log.Fatalf("failed to start tcp listener: %v", err) | ||
} | ||
|
||
gs := grpc.NewServer() | ||
reflection.Register(gs) | ||
pb.RegisterReachabilityServer(gs, s) | ||
|
||
s.logger.Info("port", s.config.ServerPort, "address", lis.Addr().String(), "GRPC Listening") | ||
if err := gs.Serve(lis); err != nil { | ||
log.Fatalf("failed to serve: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
// PortCheck func | ||
func (s *Server) PortCheck(ctx context.Context, in *pb.PortCheckRequest) (*pb.PortCheckResponse, error) { | ||
|
||
p, ok := peer.FromContext(ctx) | ||
if !ok { | ||
log.Println("Could not get peer from context") | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Could not get peer ip from context"}, nil | ||
} | ||
peerAddr := p.Addr.String() | ||
ip, _, err := net.SplitHostPort(peerAddr) | ||
if err != nil { | ||
log.Printf("Could not split host and port: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Could not parse host ip"}, nil | ||
} | ||
|
||
var timeout = 5 * time.Second | ||
_, cancel := context.WithTimeout(context.Background(), timeout) | ||
defer cancel() | ||
|
||
scan, err := nmap.NewScanner( | ||
nmap.WithTargets(ip), | ||
nmap.WithPorts(fmt.Sprintf("%d", in.Port)), | ||
) | ||
if err != nil { | ||
//log.Fatalf("unable to create nmap npc: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: ip, Port: in.Port}, nil | ||
} | ||
|
||
result, warnings, err := scan.Run() | ||
if len(warnings) > 0 { | ||
log.Printf("run finished with warnings: %s\n", warnings) // Warnings are non-critical errors from nmap. | ||
} | ||
if err != nil { | ||
//log.Fatalf("unable to run nmap scan: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: ip, Port: in.Port}, nil | ||
} | ||
|
||
// Use the results to print an example output | ||
host := result.Hosts[0] | ||
if len(host.Ports) == 0 || len(host.Addresses) == 0 { | ||
return &pb.PortCheckResponse{Status: "error", Msg: "No scan results"}, nil | ||
} | ||
|
||
port := host.Ports[0] | ||
s.logger.Info("Port Check", ip, port.ID, port.State) | ||
fmt.Printf("Host scanned in %.2f seconds\n", result.Stats.Finished.Elapsed) | ||
return &pb.PortCheckResponse{Status: port.State.String(), Msg: "", Port: in.Port, Host: ip}, nil | ||
} | ||
|
||
// HostPortCheck func | ||
func (s *Server) HostPortCheck(ctx context.Context, in *pb.HostPortCheckRequest) (*pb.PortCheckResponse, error) { | ||
|
||
if in.Host == "" { | ||
//log.Fatalf("unable to create nmap npc: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Host must be specified", Host: in.Host, Port: in.Port}, nil | ||
} | ||
var timeout = 5 * time.Second | ||
_, cancel := context.WithTimeout(context.Background(), timeout) | ||
defer cancel() | ||
|
||
scan, err := nmap.NewScanner( | ||
nmap.WithTargets(in.Host), | ||
nmap.WithPorts(fmt.Sprintf("%d", in.Port)), | ||
) | ||
if err != nil { | ||
//log.Fatalf("unable to create nmap scanner: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: in.Host, Port: in.Port}, nil | ||
} | ||
|
||
result, warnings, err := scan.Run() | ||
if len(warnings) > 0 { | ||
log.Printf("run finished with warnings: %s\n", warnings) // Warnings are non-critical errors from nmap. | ||
} | ||
if err != nil { | ||
//log.Fatalf("unable to run nmap scan: %v", err) | ||
return &pb.PortCheckResponse{Status: "error", Msg: "Port scan failed", Host: in.Host, Port: in.Port}, nil | ||
} | ||
|
||
// Use the results to print an example output | ||
host := result.Hosts[0] | ||
if len(host.Ports) == 0 || len(host.Addresses) == 0 { | ||
return &pb.PortCheckResponse{Status: "error", Msg: "No scan results"}, nil | ||
} | ||
|
||
port := host.Ports[0] | ||
|
||
fmt.Printf("\tPort %d %s\n", port.ID, port.State) | ||
fmt.Printf("Host scanned in %.2f seconds\n", result.Stats.Finished.Elapsed) | ||
return &pb.PortCheckResponse{Status: port.State.String(), Msg: "", Port: in.Port, Host: in.Host}, nil | ||
} |
Oops, something went wrong.