diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..7ad7457 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,15 @@ +version: v2 +managed: + enabled: true + override: + - file_option: go_package_prefix + value: github.com/dennis-tra/nebula-crawler/proto +plugins: + - remote: buf.build/protocolbuffers/go + out: proto + opt: paths=source_relative + - remote: buf.build/connectrpc/go + out: proto + opt: paths=source_relative +inputs: + - directory: proto \ No newline at end of file diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..53da97a --- /dev/null +++ b/buf.yaml @@ -0,0 +1,10 @@ +# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml +version: v2 +modules: + - path: proto +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/cmd/nebula/cmd.go b/cmd/nebula/cmd.go index bef02b1..5739702 100644 --- a/cmd/nebula/cmd.go +++ b/cmd/nebula/cmd.go @@ -260,6 +260,7 @@ func main() { ResolveCommand, NetworksCommand, HealthCommand, + ServeCommand, }, } diff --git a/cmd/nebula/cmd_serve.go b/cmd/nebula/cmd_serve.go new file mode 100644 index 0000000..6e1c002 --- /dev/null +++ b/cmd/nebula/cmd_serve.go @@ -0,0 +1,185 @@ +package main + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "connectrpc.com/connect" + "github.com/dennis-tra/nebula-crawler/config" + v1 "github.com/dennis-tra/nebula-crawler/proto/nebula/v1" + "github.com/dennis-tra/nebula-crawler/proto/nebula/v1/nebulav1connect" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + + "github.com/dennis-tra/nebula-crawler/db" +) + +var serveConfig = &config.Serve{ + Root: rootConfig, + Host: "localhost", + Port: 8080, +} + +// ServeCommand . +var ServeCommand = &cli.Command{ + Name: "serve", + Usage: "Serves data from a Nebula database", + Action: ServeAction, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "host", + Usage: "Let the server listen on the specified host", + EnvVars: []string{"NEBULA_SERVE_HOST"}, + Value: serveConfig.Host, + Destination: &serveConfig.Host, + }, + &cli.IntFlag{ + Name: "port", + Usage: "Let the server listen on the specified port", + EnvVars: []string{"NEBULA_SERVE_PORT"}, + Value: serveConfig.Port, + Destination: &serveConfig.Port, + }, + }, +} + +// ServeAction is the function that is called when running `nebula resolve`. +func ServeAction(c *cli.Context) error { + log.Infoln("Start serving Nebula data...") + defer log.Infoln("Stopped serving Nebula data.") + + ctx := c.Context + + // initialize a new database client based on the given configuration. + // Options are Postgres, JSON, and noop (dry-run). + dbc, err := db.NewServerClient(ctx, rootConfig.Database) + if err != nil { + return fmt.Errorf("new database client: %w", err) + } + defer func() { + if err := dbc.Close(); err != nil && !errors.Is(err, sql.ErrConnDone) && !strings.Contains(err.Error(), "use of closed network connection") { + log.WithError(err).Warnln("Failed closing database handle") + } + }() + + mux := http.NewServeMux() + path, handler := nebulav1connect.NewNebulaServiceHandler(&nebulaServiceServer{ + dbc: dbc, + }) + mux.Handle(path, handler) + + address := fmt.Sprintf("%s:%d", serveConfig.Host, serveConfig.Port) + + s := http.Server{ + Addr: address, + Handler: h2c.NewHandler(mux, &http2.Server{}), + } + + done := make(chan struct{}) + go func() { + defer close(done) + log.WithField("addr", address).Infoln("Start listening...") + if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.WithError(err).WithField("addr", address).Error("Failed to serve gRPC server") + } + }() + + select { + case <-done: + case <-c.Context.Done(): + } + + shutdownTimeout := 30 * time.Second + shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + + log.WithField("timeout", shutdownTimeout).WithField("addr", address).Infoln("Shutting down...") + if err := s.Shutdown(shutdownCtx); err != nil { + log.WithError(err).Error("Failed to shutdown gRPC server") + } + + return nil +} + +// petStoreServiceServer implements the PetStoreService API. +type nebulaServiceServer struct { + dbc db.ServerClient +} + +var _ nebulav1connect.NebulaServiceHandler = (*nebulaServiceServer)(nil) + +func (n *nebulaServiceServer) GetPeer(ctx context.Context, c *connect.Request[v1.GetPeerRequest]) (*connect.Response[v1.GetPeerResponse], error) { + log.WithField("multihash", c.Msg.MultiHash).Info("GetPeer") + + dbPeer, dbProtocols, err := n.dbc.GetPeer(ctx, c.Msg.MultiHash) + if err != nil { + return nil, err + } + + v1Maddrs := make([]*v1.MultiAddress, 0, len(dbPeer.R.MultiAddresses)) + for _, dbMaddr := range dbPeer.R.MultiAddresses { + var asn *int32 + if !dbMaddr.Asn.IsZero() { + val := int32(dbMaddr.Asn.Int) + asn = &val + } + + var isCloud *int32 + if !dbMaddr.IsCloud.IsZero() { + val := int32(dbMaddr.IsCloud.Int) + asn = &val + } + + var country *string + if !dbMaddr.Country.IsZero() { + country = &dbMaddr.Country.String + } + + var continent *string + if !dbMaddr.Continent.IsZero() { + continent = &dbMaddr.Country.String + } + + var ip *string + if !dbMaddr.Addr.IsZero() { + ip = &dbMaddr.Addr.String + } + + v1Maddrs = append(v1Maddrs, &v1.MultiAddress{ + MultiAddress: dbMaddr.Maddr, + Asn: asn, + IsCloud: isCloud, + Country: country, + Continent: continent, + Ip: ip, + }) + } + + protocols := make([]string, 0, len(dbProtocols)) + for _, dbProtocol := range dbProtocols { + protocols = append(protocols, dbProtocol.Protocol) + } + + var av *string + if dbPeer.R.AgentVersion != nil { + av = &dbPeer.R.AgentVersion.AgentVersion + } + + resp := &connect.Response[v1.GetPeerResponse]{ + Msg: &v1.GetPeerResponse{ + MultiHash: dbPeer.MultiHash, + AgentVersion: av, + MultiAddresses: v1Maddrs, + Protocols: protocols, + }, + } + + return resp, nil +} diff --git a/config/config.go b/config/config.go index e0566ca..36cc066 100644 --- a/config/config.go +++ b/config/config.go @@ -471,3 +471,19 @@ type Resolve struct { FilePathMaxmindCountry string FilePathMaxmindASN string } + +type Serve struct { + Root *Root + + // the network interfaces that the server should to bind to + Host string + + // the port that the server should bind to + Port int +} + +// String prints the configuration as a json string +func (m *Serve) String() string { + data, _ := json.MarshalIndent(m, "", " ") + return string(data) +} diff --git a/db/client.go b/db/client.go index 28d8dcc..5b3cfc8 100644 --- a/db/client.go +++ b/db/client.go @@ -24,6 +24,11 @@ type Client interface { PersistNeighbors(ctx context.Context, crawl *models.Crawl, dbPeerID *int, peerID peer.ID, errorBits uint16, dbNeighborsIDs []int, neighbors []peer.ID) error } +type ServerClient interface { + io.Closer + GetPeer(ctx context.Context, multiHash string) (*models.Peer, models.ProtocolSlice, error) +} + // NewClient will initialize the right database client based on the given // configuration. This can either be a Postgres, JSON, or noop client. The noop // client is a dummy implementation of the [Client] interface that does nothing @@ -53,3 +58,25 @@ func NewClient(ctx context.Context, cfg *config.Database) (Client, error) { return dbc, nil } + +func NewServerClient(ctx context.Context, cfg *config.Database) (ServerClient, error) { + var ( + dbc ServerClient + err error + ) + + // dry run has precedence. Then, if a JSON output directory is given, use + // the JSON client. In any other case, use the Postgres database client. + if cfg.DryRun { + return nil, fmt.Errorf("server client not implemented") + } else if cfg.JSONOut != "" { + return nil, fmt.Errorf("server client not implemented") + } else { + dbc, err = InitDBServerClient(ctx, cfg) + } + if err != nil { + return nil, fmt.Errorf("init db client: %w", err) + } + + return dbc, nil +} diff --git a/db/client_db_server.go b/db/client_db_server.go new file mode 100644 index 0000000..e20912e --- /dev/null +++ b/db/client_db_server.go @@ -0,0 +1,105 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + + "github.com/dennis-tra/nebula-crawler/db/models" + log "github.com/sirupsen/logrus" + "github.com/uptrace/opentelemetry-go-extra/otelsql" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + + "github.com/dennis-tra/nebula-crawler/config" +) + +type DBServerClient struct { + ctx context.Context + + // Reference to the configuration + cfg *config.Database + + // Database handler + dbh *sql.DB + + // reference to all relevant db telemetry + telemetry *telemetry +} + +var _ ServerClient = (*DBServerClient)(nil) + +// InitDBServerClient establishes a database connection with the provided +// configuration +func InitDBServerClient(ctx context.Context, cfg *config.Database) (*DBServerClient, error) { + log.WithFields(log.Fields{ + "host": cfg.DatabaseHost, + "port": cfg.DatabasePort, + "name": cfg.DatabaseName, + "user": cfg.DatabaseUser, + "ssl": cfg.DatabaseSSLMode, + }).Infoln("Initializing database client") + + dbh, err := otelsql.Open("postgres", cfg.DatabaseSourceName(), + otelsql.WithAttributes(semconv.DBSystemPostgreSQL), + otelsql.WithMeterProvider(cfg.MeterProvider), + otelsql.WithTracerProvider(cfg.TracerProvider), + ) + if err != nil { + return nil, fmt.Errorf("opening database: %w", err) + } + + // Set to match the writer worker + dbh.SetMaxIdleConns(cfg.MaxIdleConns) // default is 2 which leads to many connection open/closings + + otelsql.ReportDBStatsMetrics(dbh, otelsql.WithMeterProvider(cfg.MeterProvider)) + + // Ping database to verify connection. + if err = dbh.Ping(); err != nil { + return nil, fmt.Errorf("pinging database: %w", err) + } + + telemetry, err := newTelemetry(cfg.TracerProvider, cfg.MeterProvider) + if err != nil { + return nil, fmt.Errorf("new telemetry: %w", err) + } + + client := &DBServerClient{ctx: ctx, cfg: cfg, dbh: dbh, telemetry: telemetry} + + return client, nil +} + +func (d *DBServerClient) Close() error { + return d.dbh.Close() +} + +func (d *DBServerClient) GetPeer(ctx context.Context, multiHash string) (*models.Peer, models.ProtocolSlice, error) { + // write a hand-crafted query to avoid two DB round-trips + + dbPeer, err := models.Peers( + models.PeerWhere.MultiHash.EQ(multiHash), + qm.Load(models.PeerRels.AgentVersion), + qm.Load(models.PeerRels.MultiAddresses), + qm.Load(models.PeerRels.ProtocolsSet), + ).One(ctx, d.dbh) + if err != nil { + return nil, nil, fmt.Errorf("getting peer: %w", err) + } + + if dbPeer.R.ProtocolsSet == nil { + return dbPeer, nil, nil + } + + protocolIDs := dbPeer.R.ProtocolsSet.ProtocolIds + ids := make([]int, 0, len(protocolIDs)) + for _, id := range protocolIDs { + ids = append(ids, int(id)) + } + + dbProtocols, err := models.Protocols(models.ProtocolWhere.ID.IN(ids)).All(ctx, d.dbh) + if err != nil { + return dbPeer, nil, fmt.Errorf("getting protocols: %w", err) + } + + return dbPeer, dbProtocols, nil +} diff --git a/go.mod b/go.mod index 50c8239..c2ca8b7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/dennis-tra/nebula-crawler go 1.23 require ( + connectrpc.com/connect v1.17.0 github.com/benbjohnson/clock v1.3.5 github.com/cenkalti/backoff/v4 v4.3.0 github.com/deckarep/golang-set/v2 v2.6.0 @@ -43,6 +44,7 @@ require ( go.uber.org/mock v0.4.0 golang.org/x/net v0.30.0 golang.org/x/sync v0.8.0 + google.golang.org/protobuf v1.35.1 ) require ( @@ -206,7 +208,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/go.sum b/go.sum index aa11284..3ab271a 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk= +connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= diff --git a/libp2p/driver_crawler.go b/libp2p/driver_crawler.go index c7f47d3..2e1aa4e 100644 --- a/libp2p/driver_crawler.go +++ b/libp2p/driver_crawler.go @@ -2,6 +2,8 @@ package libp2p import ( "fmt" + "math" + "net/netip" "runtime" "time" @@ -184,9 +186,23 @@ func (d *CrawlDriver) Close() {} func newLibp2pHost(userAgent string) (Host, error) { // Configure the resource manager to not limit anything + v4PrefixLimits := []rcmgr.NetworkPrefixLimit{ + { + Network: netip.MustParsePrefix("0.0.0.0/0"), + ConnCount: math.MaxInt, // Unlimited + }, + } + + v6PrefixLimits := []rcmgr.NetworkPrefixLimit{ + { + Network: netip.MustParsePrefix("::1/0"), + ConnCount: math.MaxInt, // Unlimited + }, + } + var noSubnetLimit []rcmgr.ConnLimitPerSubnet limiter := rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits) - rm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithLimitPerSubnet(noSubnetLimit, noSubnetLimit)) + rm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithLimitPerSubnet(noSubnetLimit, noSubnetLimit), rcmgr.WithNetworkPrefixLimit(v4PrefixLimits, v6PrefixLimits)) if err != nil { return nil, fmt.Errorf("new resource manager: %w", err) } diff --git a/proto/nebula/v1/nebula.pb.go b/proto/nebula/v1/nebula.pb.go new file mode 100644 index 0000000..2bc2fa2 --- /dev/null +++ b/proto/nebula/v1/nebula.pb.go @@ -0,0 +1,331 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc (unknown) +// source: nebula/v1/nebula.proto + +package nebulav1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MultiAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MultiAddress string `protobuf:"bytes,1,opt,name=multi_address,json=multiAddress,proto3" json:"multi_address,omitempty"` + Asn *int32 `protobuf:"varint,2,opt,name=asn,proto3,oneof" json:"asn,omitempty"` + IsCloud *int32 `protobuf:"varint,3,opt,name=is_cloud,json=isCloud,proto3,oneof" json:"is_cloud,omitempty"` + Country *string `protobuf:"bytes,4,opt,name=country,proto3,oneof" json:"country,omitempty"` + Continent *string `protobuf:"bytes,5,opt,name=continent,proto3,oneof" json:"continent,omitempty"` + Ip *string `protobuf:"bytes,6,opt,name=ip,proto3,oneof" json:"ip,omitempty"` +} + +func (x *MultiAddress) Reset() { + *x = MultiAddress{} + mi := &file_nebula_v1_nebula_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MultiAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MultiAddress) ProtoMessage() {} + +func (x *MultiAddress) ProtoReflect() protoreflect.Message { + mi := &file_nebula_v1_nebula_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MultiAddress.ProtoReflect.Descriptor instead. +func (*MultiAddress) Descriptor() ([]byte, []int) { + return file_nebula_v1_nebula_proto_rawDescGZIP(), []int{0} +} + +func (x *MultiAddress) GetMultiAddress() string { + if x != nil { + return x.MultiAddress + } + return "" +} + +func (x *MultiAddress) GetAsn() int32 { + if x != nil && x.Asn != nil { + return *x.Asn + } + return 0 +} + +func (x *MultiAddress) GetIsCloud() int32 { + if x != nil && x.IsCloud != nil { + return *x.IsCloud + } + return 0 +} + +func (x *MultiAddress) GetCountry() string { + if x != nil && x.Country != nil { + return *x.Country + } + return "" +} + +func (x *MultiAddress) GetContinent() string { + if x != nil && x.Continent != nil { + return *x.Continent + } + return "" +} + +func (x *MultiAddress) GetIp() string { + if x != nil && x.Ip != nil { + return *x.Ip + } + return "" +} + +type GetPeerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MultiHash string `protobuf:"bytes,1,opt,name=multi_hash,json=multiHash,proto3" json:"multi_hash,omitempty"` +} + +func (x *GetPeerRequest) Reset() { + *x = GetPeerRequest{} + mi := &file_nebula_v1_nebula_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPeerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPeerRequest) ProtoMessage() {} + +func (x *GetPeerRequest) ProtoReflect() protoreflect.Message { + mi := &file_nebula_v1_nebula_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPeerRequest.ProtoReflect.Descriptor instead. +func (*GetPeerRequest) Descriptor() ([]byte, []int) { + return file_nebula_v1_nebula_proto_rawDescGZIP(), []int{1} +} + +func (x *GetPeerRequest) GetMultiHash() string { + if x != nil { + return x.MultiHash + } + return "" +} + +type GetPeerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MultiHash string `protobuf:"bytes,1,opt,name=multi_hash,json=multiHash,proto3" json:"multi_hash,omitempty"` + AgentVersion *string `protobuf:"bytes,2,opt,name=agent_version,json=agentVersion,proto3,oneof" json:"agent_version,omitempty"` + Protocols []string `protobuf:"bytes,3,rep,name=protocols,proto3" json:"protocols,omitempty"` + MultiAddresses []*MultiAddress `protobuf:"bytes,4,rep,name=multi_addresses,json=multiAddresses,proto3" json:"multi_addresses,omitempty"` +} + +func (x *GetPeerResponse) Reset() { + *x = GetPeerResponse{} + mi := &file_nebula_v1_nebula_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPeerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPeerResponse) ProtoMessage() {} + +func (x *GetPeerResponse) ProtoReflect() protoreflect.Message { + mi := &file_nebula_v1_nebula_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPeerResponse.ProtoReflect.Descriptor instead. +func (*GetPeerResponse) Descriptor() ([]byte, []int) { + return file_nebula_v1_nebula_proto_rawDescGZIP(), []int{2} +} + +func (x *GetPeerResponse) GetMultiHash() string { + if x != nil { + return x.MultiHash + } + return "" +} + +func (x *GetPeerResponse) GetAgentVersion() string { + if x != nil && x.AgentVersion != nil { + return *x.AgentVersion + } + return "" +} + +func (x *GetPeerResponse) GetProtocols() []string { + if x != nil { + return x.Protocols + } + return nil +} + +func (x *GetPeerResponse) GetMultiAddresses() []*MultiAddress { + if x != nil { + return x.MultiAddresses + } + return nil +} + +var File_nebula_v1_nebula_proto protoreflect.FileDescriptor + +var file_nebula_v1_nebula_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x65, 0x62, 0x75, + 0x6c, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, + 0x2e, 0x76, 0x31, 0x22, 0xf7, 0x01, 0x0a, 0x0c, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x15, 0x0a, 0x03, 0x61, 0x73, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x03, 0x61, 0x73, 0x6e, 0x88, 0x01, 0x01, + 0x12, 0x1e, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x48, 0x01, 0x52, 0x07, 0x69, 0x73, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x02, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, + 0x21, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6e, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, + 0x52, 0x02, 0x69, 0x70, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x61, 0x73, 0x6e, 0x42, + 0x0b, 0x0a, 0x09, 0x5f, 0x69, 0x73, 0x5f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x42, 0x0a, 0x0a, 0x08, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x65, 0x6e, 0x74, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x70, 0x22, 0x2f, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x48, 0x61, 0x73, 0x68, 0x22, 0xcc, + 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x28, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x09, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x40, 0x0a, 0x0f, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0e, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x53, 0x0a, + 0x0d, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6e, 0x65, 0x62, 0x75, + 0x6c, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0xa0, 0x01, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x2e, 0x6e, 0x65, 0x62, 0x75, 0x6c, + 0x61, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x64, 0x65, 0x6e, 0x6e, 0x69, 0x73, 0x2d, 0x74, 0x72, 0x61, 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, + 0x61, 0x2d, 0x63, 0x72, 0x61, 0x77, 0x6c, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x76, 0x31, 0x3b, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4e, 0x58, 0x58, 0xaa, 0x02, 0x09, 0x4e, 0x65, 0x62, 0x75, 0x6c, + 0x61, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x09, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x15, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0a, 0x4e, 0x65, 0x62, 0x75, 0x6c, + 0x61, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_nebula_v1_nebula_proto_rawDescOnce sync.Once + file_nebula_v1_nebula_proto_rawDescData = file_nebula_v1_nebula_proto_rawDesc +) + +func file_nebula_v1_nebula_proto_rawDescGZIP() []byte { + file_nebula_v1_nebula_proto_rawDescOnce.Do(func() { + file_nebula_v1_nebula_proto_rawDescData = protoimpl.X.CompressGZIP(file_nebula_v1_nebula_proto_rawDescData) + }) + return file_nebula_v1_nebula_proto_rawDescData +} + +var file_nebula_v1_nebula_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_nebula_v1_nebula_proto_goTypes = []any{ + (*MultiAddress)(nil), // 0: nebula.v1.MultiAddress + (*GetPeerRequest)(nil), // 1: nebula.v1.GetPeerRequest + (*GetPeerResponse)(nil), // 2: nebula.v1.GetPeerResponse +} +var file_nebula_v1_nebula_proto_depIdxs = []int32{ + 0, // 0: nebula.v1.GetPeerResponse.multi_addresses:type_name -> nebula.v1.MultiAddress + 1, // 1: nebula.v1.NebulaService.GetPeer:input_type -> nebula.v1.GetPeerRequest + 2, // 2: nebula.v1.NebulaService.GetPeer:output_type -> nebula.v1.GetPeerResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_nebula_v1_nebula_proto_init() } +func file_nebula_v1_nebula_proto_init() { + if File_nebula_v1_nebula_proto != nil { + return + } + file_nebula_v1_nebula_proto_msgTypes[0].OneofWrappers = []any{} + file_nebula_v1_nebula_proto_msgTypes[2].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_nebula_v1_nebula_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_nebula_v1_nebula_proto_goTypes, + DependencyIndexes: file_nebula_v1_nebula_proto_depIdxs, + MessageInfos: file_nebula_v1_nebula_proto_msgTypes, + }.Build() + File_nebula_v1_nebula_proto = out.File + file_nebula_v1_nebula_proto_rawDesc = nil + file_nebula_v1_nebula_proto_goTypes = nil + file_nebula_v1_nebula_proto_depIdxs = nil +} diff --git a/proto/nebula/v1/nebula.proto b/proto/nebula/v1/nebula.proto new file mode 100644 index 0000000..96e17fc --- /dev/null +++ b/proto/nebula/v1/nebula.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package nebula.v1; + +message MultiAddress { + string multi_address = 1; + optional int32 asn = 2; + optional int32 is_cloud = 3; + optional string country = 4; + optional string continent = 5; + optional string ip = 6; +} + +message GetPeerRequest { + string multi_hash = 1; +} + +message GetPeerResponse { + string multi_hash = 1; + optional string agent_version = 2; + repeated string protocols = 3; + repeated MultiAddress multi_addresses = 4; +} + +service NebulaService { + rpc GetPeer(GetPeerRequest) returns (GetPeerResponse) {} +} \ No newline at end of file diff --git a/proto/nebula/v1/nebulav1connect/nebula.connect.go b/proto/nebula/v1/nebulav1connect/nebula.connect.go new file mode 100644 index 0000000..e44e758 --- /dev/null +++ b/proto/nebula/v1/nebulav1connect/nebula.connect.go @@ -0,0 +1,112 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: nebula/v1/nebula.proto + +package nebulav1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/dennis-tra/nebula-crawler/proto/nebula/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // NebulaServiceName is the fully-qualified name of the NebulaService service. + NebulaServiceName = "nebula.v1.NebulaService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // NebulaServiceGetPeerProcedure is the fully-qualified name of the NebulaService's GetPeer RPC. + NebulaServiceGetPeerProcedure = "/nebula.v1.NebulaService/GetPeer" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + nebulaServiceServiceDescriptor = v1.File_nebula_v1_nebula_proto.Services().ByName("NebulaService") + nebulaServiceGetPeerMethodDescriptor = nebulaServiceServiceDescriptor.Methods().ByName("GetPeer") +) + +// NebulaServiceClient is a client for the nebula.v1.NebulaService service. +type NebulaServiceClient interface { + GetPeer(context.Context, *connect.Request[v1.GetPeerRequest]) (*connect.Response[v1.GetPeerResponse], error) +} + +// NewNebulaServiceClient constructs a client for the nebula.v1.NebulaService service. By default, +// it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and +// sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() +// or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewNebulaServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) NebulaServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &nebulaServiceClient{ + getPeer: connect.NewClient[v1.GetPeerRequest, v1.GetPeerResponse]( + httpClient, + baseURL+NebulaServiceGetPeerProcedure, + connect.WithSchema(nebulaServiceGetPeerMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// nebulaServiceClient implements NebulaServiceClient. +type nebulaServiceClient struct { + getPeer *connect.Client[v1.GetPeerRequest, v1.GetPeerResponse] +} + +// GetPeer calls nebula.v1.NebulaService.GetPeer. +func (c *nebulaServiceClient) GetPeer(ctx context.Context, req *connect.Request[v1.GetPeerRequest]) (*connect.Response[v1.GetPeerResponse], error) { + return c.getPeer.CallUnary(ctx, req) +} + +// NebulaServiceHandler is an implementation of the nebula.v1.NebulaService service. +type NebulaServiceHandler interface { + GetPeer(context.Context, *connect.Request[v1.GetPeerRequest]) (*connect.Response[v1.GetPeerResponse], error) +} + +// NewNebulaServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewNebulaServiceHandler(svc NebulaServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + nebulaServiceGetPeerHandler := connect.NewUnaryHandler( + NebulaServiceGetPeerProcedure, + svc.GetPeer, + connect.WithSchema(nebulaServiceGetPeerMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/nebula.v1.NebulaService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case NebulaServiceGetPeerProcedure: + nebulaServiceGetPeerHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedNebulaServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedNebulaServiceHandler struct{} + +func (UnimplementedNebulaServiceHandler) GetPeer(context.Context, *connect.Request[v1.GetPeerRequest]) (*connect.Response[v1.GetPeerResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("nebula.v1.NebulaService.GetPeer is not implemented")) +}