diff --git a/docs/streamer_types.md b/docs/streamer_types.md new file mode 100644 index 00000000..dc2d8950 --- /dev/null +++ b/docs/streamer_types.md @@ -0,0 +1,81 @@ +# Streamer Types + +`gnetcli` supports different connection types (streamers) for connecting to network equipment: SSH, Telnet. + +## Supported Types + +### Unknown (StreamerType_unknown) +- **Value**: `0` +- **Description**: Unspecified connection type, server will determine based on port or default to SSH +- **Usage**: When not explicitly set + +### SSH (StreamerType_ssh) +- **Value**: `1` +- **Description**: Connection via SSH protocol (default when unknown) +- **Default port**: 22 +- **Features**: + - SSH tunnel support (ProxyJump) + - SSH Control Files support + - SSH Agent support + - Private key authentication + - SFTP for file transfers + +### Telnet (StreamerType_telnet) +- **Value**: `2` +- **Description**: Connection via Telnet protocol +- **Default port**: 23 +- **Features**: + - Plain text connection + - Username/password authentication + - Custom port support + +## API Usage + +### Via gRPC +The streamer type is specified in host parameters: + +```protobuf +message HostParams { + string host = 1; + Credentials credentials = 2; + int32 port = 3; + string device = 4; + string ip = 5; + StreamerType streamer_type = 6; // SSH or Telnet +} +``` + +### Example Request +```json +{ + "host": "192.168.1.1", + "cmd": "show version", + "host_params": { + "streamer_type": 2, + "port": 23, + "credentials": { + "login": "admin", + "password": "password" + } + } +} +``` +Note: `streamer_type: 2` for Telnet, `streamer_type: 1` for SSH, `streamer_type: 0` for unknown/auto-detect. + +## Choosing Streamer Type + +### SSH is recommended for: +- Modern network equipment +- Production environments where security is important +- Devices supporting cryptographic authentication +- Cases requiring file transfers + +### Telnet is suitable for: +- Legacy equipment without SSH support +- Lab and test environments +- Devices with limited computational resources +- Connection debugging and diagnostics + +## Default Configuration + +If `streamer_type` is not explicitly specified, SSH is used as the more secure default option. diff --git a/grpc_sdk/python/gnetclisdk/__init__.py b/grpc_sdk/python/gnetclisdk/__init__.py new file mode 100644 index 00000000..cd7bca5d --- /dev/null +++ b/grpc_sdk/python/gnetclisdk/__init__.py @@ -0,0 +1,29 @@ +"""gnetcli SDK for Python""" + +from .client import ( + Credentials, + File, + Gnetcli, + HostParams, + QA, + STREAMER_UNKNOWN, + STREAMER_SSH, + STREAMER_TELNET, +) +from .exceptions import ( + DeviceConnectError, + GnetcliException, +) + +__all__ = [ + "Credentials", + "File", + "Gnetcli", + "HostParams", + "QA", + "STREAMER_UNKNOWN", + "STREAMER_SSH", + "STREAMER_TELNET", + "DeviceConnectError", + "GnetcliException", +] diff --git a/grpc_sdk/python/gnetclisdk/client.py b/grpc_sdk/python/gnetclisdk/client.py index 4882100f..ec3d43a6 100644 --- a/grpc_sdk/python/gnetclisdk/client.py +++ b/grpc_sdk/python/gnetclisdk/client.py @@ -7,7 +7,7 @@ from contextlib import asynccontextmanager from dataclasses import dataclass, field from functools import partial -from typing import Any, AsyncIterator, List, Optional, Tuple, Dict, Callable +from typing import Any, AsyncIterator, List, Optional, Tuple, Dict, Callable, Union import grpc from google.protobuf.message import Message @@ -25,6 +25,10 @@ SERVER_ENV = "GNETCLI_SERVER" GRPC_MAX_MESSAGE_LENGTH = 130 * 1024**2 +STREAMER_UNKNOWN = server_pb2.StreamerType_unknown +STREAMER_SSH = server_pb2.StreamerType_ssh +STREAMER_TELNET = server_pb2.StreamerType_telnet + default_grpc_options: List[Tuple[str, Any]] = [ ("grpc.max_concurrent_streams", 900), ("grpc.max_send_message_length", GRPC_MAX_MESSAGE_LENGTH), @@ -72,6 +76,7 @@ class HostParams: hostname: Optional[str] = None credentials: Optional[Credentials] = None ip: Optional[str] = None + streamer_type: Optional[int] = None def make_pb(self) -> server_pb2.HostParams: creds_pb: Optional[server_pb2.Credentials] = None @@ -83,6 +88,7 @@ def make_pb(self) -> server_pb2.HostParams: credentials=creds_pb, device=self.device, ip=self.ip, + streamer_type=self.streamer_type if self.streamer_type is not None else server_pb2.StreamerType_unknown, ) return pbcmd @@ -247,6 +253,8 @@ async def set_host_params(self, hostname: str, params: HostParams) -> None: port=params.port, credentials=params.credentials.make_pb(), device=params.device, + ip=params.ip, + streamer_type=params.streamer_type if params.streamer_type is not None else server_pb2.StreamerType_unknown, ) _logger.debug("connect to %s", self._server) async with self._grpc_channel_fn(self._server, options=self._options) as channel: diff --git a/pkg/server/devuath.go b/pkg/server/devuath.go index fe2184d4..15241350 100644 --- a/pkg/server/devuath.go +++ b/pkg/server/devuath.go @@ -48,7 +48,7 @@ func (m authApp) GetHostParams(host string, params *pb.HostParams) (hostParams, if err != nil { return hostParams{}, err } - res := NewHostParams(creds, params.GetDevice(), ip, port, proxyJump, controlPath, connectHost) + res := NewHostParams(creds, params.GetDevice(), ip, port, proxyJump, controlPath, connectHost, params.GetStreamerType()) return res, nil } diff --git a/pkg/server/proto/server.pb.go b/pkg/server/proto/server.pb.go index 6176636f..d30668fa 100644 --- a/pkg/server/proto/server.pb.go +++ b/pkg/server/proto/server.pb.go @@ -123,6 +123,55 @@ func (DeviceResultStatus) EnumDescriptor() ([]byte, []int) { return file_server_proto_rawDescGZIP(), []int{1} } +type StreamerType int32 + +const ( + StreamerType_StreamerType_unknown StreamerType = 0 + StreamerType_StreamerType_ssh StreamerType = 1 + StreamerType_StreamerType_telnet StreamerType = 2 +) + +// Enum value maps for StreamerType. +var ( + StreamerType_name = map[int32]string{ + 0: "StreamerType_unknown", + 1: "StreamerType_ssh", + 2: "StreamerType_telnet", + } + StreamerType_value = map[string]int32{ + "StreamerType_unknown": 0, + "StreamerType_ssh": 1, + "StreamerType_telnet": 2, + } +) + +func (x StreamerType) Enum() *StreamerType { + p := new(StreamerType) + *p = x + return p +} + +func (x StreamerType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StreamerType) Descriptor() protoreflect.EnumDescriptor { + return file_server_proto_enumTypes[2].Descriptor() +} + +func (StreamerType) Type() protoreflect.EnumType { + return &file_server_proto_enumTypes[2] +} + +func (x StreamerType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StreamerType.Descriptor instead. +func (StreamerType) EnumDescriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{2} +} + type FileStatus int32 const ( @@ -162,11 +211,11 @@ func (x FileStatus) String() string { } func (FileStatus) Descriptor() protoreflect.EnumDescriptor { - return file_server_proto_enumTypes[2].Descriptor() + return file_server_proto_enumTypes[3].Descriptor() } func (FileStatus) Type() protoreflect.EnumType { - return &file_server_proto_enumTypes[2] + return &file_server_proto_enumTypes[3] } func (x FileStatus) Number() protoreflect.EnumNumber { @@ -175,7 +224,7 @@ func (x FileStatus) Number() protoreflect.EnumNumber { // Deprecated: Use FileStatus.Descriptor instead. func (FileStatus) EnumDescriptor() ([]byte, []int) { - return file_server_proto_rawDescGZIP(), []int{2} + return file_server_proto_rawDescGZIP(), []int{3} } type QA struct { @@ -609,11 +658,12 @@ type HostParams struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` - Credentials *Credentials `protobuf:"bytes,2,opt,name=credentials,proto3" json:"credentials,omitempty"` - Port int32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` - Device string `protobuf:"bytes,4,opt,name=device,proto3" json:"device,omitempty"` - Ip string `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"` + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Credentials *Credentials `protobuf:"bytes,2,opt,name=credentials,proto3" json:"credentials,omitempty"` + Port int32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + Device string `protobuf:"bytes,4,opt,name=device,proto3" json:"device,omitempty"` + Ip string `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"` + StreamerType StreamerType `protobuf:"varint,6,opt,name=streamer_type,json=streamerType,proto3,enum=gnetcli.StreamerType" json:"streamer_type,omitempty"` } func (x *HostParams) Reset() { @@ -683,6 +733,13 @@ func (x *HostParams) GetIp() string { return "" } +func (x *HostParams) GetStreamerType() StreamerType { + if x != nil { + return x.StreamerType + } + return StreamerType_StreamerType_unknown +} + type CMDResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1135,7 +1192,7 @@ var file_server_proto_rawDesc = []byte{ 0x17, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x94, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xd0, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, @@ -1144,116 +1201,126 @@ var file_server_proto_rawDesc = []byte{ 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0xae, - 0x01, 0x0a, 0x09, 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6f, 0x75, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x6f, 0x75, 0x74, 0x53, 0x74, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, - 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x74, 0x72, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x72, - 0x61, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6e, 0x65, 0x74, - 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, - 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, - 0x53, 0x0a, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, - 0x2d, 0x0a, 0x03, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x67, - 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x03, 0x72, 0x65, 0x73, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x22, 0x8d, 0x01, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, - 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x48, 0x6f, - 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x73, 0x22, 0x5f, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, - 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, - 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x12, 0x34, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, - 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0x36, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, - 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2a, 0x66, - 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, - 0x74, 0x73, 0x65, 0x74, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x01, 0x12, 0x13, 0x0a, - 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x72, 0x65, 0x61, 0x64, 0x10, 0x03, 0x2a, 0x48, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x11, 0x0a, 0x0d, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x73, 0x65, 0x74, 0x10, 0x00, 0x12, - 0x0d, 0x0a, 0x09, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x6b, 0x10, 0x01, 0x12, 0x10, - 0x0a, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x02, - 0x2a, 0x7d, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, - 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6e, 0x6f, 0x74, - 0x73, 0x65, 0x74, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x5f, 0x6f, 0x6b, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x69, 0x6c, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x02, 0x12, 0x18, - 0x0a, 0x14, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6e, 0x6f, 0x74, - 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x10, 0x04, 0x32, - 0x8c, 0x05, 0x0a, 0x07, 0x47, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x12, 0x64, 0x0a, 0x0f, 0x53, - 0x65, 0x74, 0x75, 0x70, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x13, - 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, - 0x75, 0x70, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x01, - 0x2a, 0x12, 0x41, 0x0a, 0x04, 0x45, 0x78, 0x65, 0x63, 0x12, 0x0c, 0x2e, 0x67, 0x6e, 0x65, 0x74, - 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x1a, 0x12, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, - 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x17, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, - 0x63, 0x3a, 0x01, 0x2a, 0x12, 0x32, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x63, 0x43, 0x68, 0x61, 0x74, - 0x12, 0x0c, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x1a, 0x12, - 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0f, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x15, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, - 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x1d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, - 0x64, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x0b, - 0x45, 0x78, 0x65, 0x63, 0x4e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x13, 0x2e, 0x67, 0x6e, - 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x4e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, - 0x1a, 0x12, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x6e, 0x65, 0x74, 0x63, 0x6f, - 0x6e, 0x66, 0x3a, 0x01, 0x2a, 0x12, 0x40, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x4e, 0x65, 0x74, - 0x63, 0x6f, 0x6e, 0x66, 0x43, 0x68, 0x61, 0x74, 0x12, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, - 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x4e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x1a, 0x12, 0x2e, - 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, 0x69, - 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, - 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, - 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, - 0x1a, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x01, 0x2a, 0x42, 0x37, - 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6e, 0x6e, - 0x65, 0x74, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2f, 0x70, - 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, - 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x3a, + 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x22, 0xae, 0x01, 0x0a, 0x09, 0x43, + 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x75, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6f, 0x75, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x75, + 0x74, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, + 0x53, 0x74, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x53, 0x74, 0x72, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, + 0x43, 0x4d, 0x44, 0x54, 0x72, 0x61, 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x53, 0x0a, 0x0c, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2d, 0x0a, 0x03, 0x72, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, + 0x6c, 0x69, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x03, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x8d, 0x01, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, + 0x68, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0b, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, + 0x22, 0x5f, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, + 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x0b, + 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x22, 0x36, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x27, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, + 0x61, 0x74, 0x61, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2a, 0x66, 0x0a, 0x0e, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x10, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x74, 0x73, 0x65, 0x74, + 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x10, 0x02, 0x12, 0x12, + 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x64, + 0x10, 0x03, 0x2a, 0x48, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x73, 0x65, 0x74, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x6b, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x02, 0x2a, 0x57, 0x0a, 0x0c, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x6b, + 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x73, 0x73, 0x68, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x74, 0x65, 0x6c, + 0x6e, 0x65, 0x74, 0x10, 0x02, 0x2a, 0x7d, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x5f, 0x6e, 0x6f, 0x74, 0x73, 0x65, 0x74, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x46, 0x69, + 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6f, 0x6b, 0x10, 0x01, 0x12, 0x14, 0x0a, + 0x10, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0x03, 0x12, 0x15, 0x0a, + 0x11, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x69, 0x73, 0x5f, 0x64, + 0x69, 0x72, 0x10, 0x04, 0x32, 0x8c, 0x05, 0x0a, 0x07, 0x47, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, + 0x12, 0x64, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x75, 0x70, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x12, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x48, 0x6f, + 0x73, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x41, 0x0a, 0x04, 0x45, 0x78, 0x65, 0x63, 0x12, 0x0c, + 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x1a, 0x12, 0x2e, 0x67, + 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x3a, 0x01, 0x2a, 0x12, 0x32, 0x0a, 0x08, 0x45, 0x78, 0x65, + 0x63, 0x43, 0x68, 0x61, 0x74, 0x12, 0x0c, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, + 0x43, 0x4d, 0x44, 0x1a, 0x12, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, + 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, + 0x09, 0x41, 0x64, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0f, 0x2e, 0x67, 0x6e, 0x65, + 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x15, 0x2e, 0x67, 0x6e, + 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x3a, 0x01, + 0x2a, 0x12, 0x57, 0x0a, 0x0b, 0x45, 0x78, 0x65, 0x63, 0x4e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, + 0x12, 0x13, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x4e, 0x65, + 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x1a, 0x12, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, + 0x43, 0x4d, 0x44, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x5f, + 0x6e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x3a, 0x01, 0x2a, 0x12, 0x40, 0x0a, 0x0f, 0x45, 0x78, + 0x65, 0x63, 0x4e, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x43, 0x68, 0x61, 0x74, 0x12, 0x13, 0x2e, + 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, 0x4e, 0x65, 0x74, 0x63, 0x6f, + 0x6e, 0x66, 0x1a, 0x12, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x43, 0x4d, 0x44, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5c, 0x0a, 0x08, + 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, + 0x6c, 0x69, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, + 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x1c, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x06, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x2e, 0x46, + 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, + 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x3a, 0x01, 0x2a, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x6e, 0x6e, 0x65, 0x74, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x67, 0x6e, 0x65, 0x74, + 0x63, 0x6c, 0x69, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x67, 0x6e, 0x65, 0x74, 0x63, 0x6c, 0x69, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1268,60 +1335,62 @@ func file_server_proto_rawDescGZIP() []byte { return file_server_proto_rawDescData } -var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_server_proto_goTypes = []interface{}{ (TraceOperation)(0), // 0: gnetcli.TraceOperation (DeviceResultStatus)(0), // 1: gnetcli.DeviceResultStatus - (FileStatus)(0), // 2: gnetcli.FileStatus - (*QA)(nil), // 3: gnetcli.QA - (*Credentials)(nil), // 4: gnetcli.Credentials - (*CMD)(nil), // 5: gnetcli.CMD - (*Device)(nil), // 6: gnetcli.Device - (*CMDNetconf)(nil), // 7: gnetcli.CMDNetconf - (*CMDTraceItem)(nil), // 8: gnetcli.CMDTraceItem - (*HostParams)(nil), // 9: gnetcli.HostParams - (*CMDResult)(nil), // 10: gnetcli.CMDResult - (*DeviceResult)(nil), // 11: gnetcli.DeviceResult - (*FileDownloadRequest)(nil), // 12: gnetcli.FileDownloadRequest - (*FileData)(nil), // 13: gnetcli.FileData - (*FileUploadRequest)(nil), // 14: gnetcli.FileUploadRequest - (*FilesResult)(nil), // 15: gnetcli.FilesResult - (*emptypb.Empty)(nil), // 16: google.protobuf.Empty + (StreamerType)(0), // 2: gnetcli.StreamerType + (FileStatus)(0), // 3: gnetcli.FileStatus + (*QA)(nil), // 4: gnetcli.QA + (*Credentials)(nil), // 5: gnetcli.Credentials + (*CMD)(nil), // 6: gnetcli.CMD + (*Device)(nil), // 7: gnetcli.Device + (*CMDNetconf)(nil), // 8: gnetcli.CMDNetconf + (*CMDTraceItem)(nil), // 9: gnetcli.CMDTraceItem + (*HostParams)(nil), // 10: gnetcli.HostParams + (*CMDResult)(nil), // 11: gnetcli.CMDResult + (*DeviceResult)(nil), // 12: gnetcli.DeviceResult + (*FileDownloadRequest)(nil), // 13: gnetcli.FileDownloadRequest + (*FileData)(nil), // 14: gnetcli.FileData + (*FileUploadRequest)(nil), // 15: gnetcli.FileUploadRequest + (*FilesResult)(nil), // 16: gnetcli.FilesResult + (*emptypb.Empty)(nil), // 17: google.protobuf.Empty } var file_server_proto_depIdxs = []int32{ - 3, // 0: gnetcli.CMD.qa:type_name -> gnetcli.QA - 9, // 1: gnetcli.CMD.host_params:type_name -> gnetcli.HostParams + 4, // 0: gnetcli.CMD.qa:type_name -> gnetcli.QA + 10, // 1: gnetcli.CMD.host_params:type_name -> gnetcli.HostParams 0, // 2: gnetcli.CMDTraceItem.operation:type_name -> gnetcli.TraceOperation - 4, // 3: gnetcli.HostParams.credentials:type_name -> gnetcli.Credentials - 8, // 4: gnetcli.CMDResult.trace:type_name -> gnetcli.CMDTraceItem - 1, // 5: gnetcli.DeviceResult.res:type_name -> gnetcli.DeviceResultStatus - 9, // 6: gnetcli.FileDownloadRequest.host_params:type_name -> gnetcli.HostParams - 2, // 7: gnetcli.FileData.status:type_name -> gnetcli.FileStatus - 13, // 8: gnetcli.FileUploadRequest.files:type_name -> gnetcli.FileData - 9, // 9: gnetcli.FileUploadRequest.host_params:type_name -> gnetcli.HostParams - 13, // 10: gnetcli.FilesResult.files:type_name -> gnetcli.FileData - 9, // 11: gnetcli.Gnetcli.SetupHostParams:input_type -> gnetcli.HostParams - 5, // 12: gnetcli.Gnetcli.Exec:input_type -> gnetcli.CMD - 5, // 13: gnetcli.Gnetcli.ExecChat:input_type -> gnetcli.CMD - 6, // 14: gnetcli.Gnetcli.AddDevice:input_type -> gnetcli.Device - 7, // 15: gnetcli.Gnetcli.ExecNetconf:input_type -> gnetcli.CMDNetconf - 7, // 16: gnetcli.Gnetcli.ExecNetconfChat:input_type -> gnetcli.CMDNetconf - 12, // 17: gnetcli.Gnetcli.Download:input_type -> gnetcli.FileDownloadRequest - 14, // 18: gnetcli.Gnetcli.Upload:input_type -> gnetcli.FileUploadRequest - 16, // 19: gnetcli.Gnetcli.SetupHostParams:output_type -> google.protobuf.Empty - 10, // 20: gnetcli.Gnetcli.Exec:output_type -> gnetcli.CMDResult - 10, // 21: gnetcli.Gnetcli.ExecChat:output_type -> gnetcli.CMDResult - 11, // 22: gnetcli.Gnetcli.AddDevice:output_type -> gnetcli.DeviceResult - 10, // 23: gnetcli.Gnetcli.ExecNetconf:output_type -> gnetcli.CMDResult - 10, // 24: gnetcli.Gnetcli.ExecNetconfChat:output_type -> gnetcli.CMDResult - 15, // 25: gnetcli.Gnetcli.Download:output_type -> gnetcli.FilesResult - 16, // 26: gnetcli.Gnetcli.Upload:output_type -> google.protobuf.Empty - 19, // [19:27] is the sub-list for method output_type - 11, // [11:19] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 5, // 3: gnetcli.HostParams.credentials:type_name -> gnetcli.Credentials + 2, // 4: gnetcli.HostParams.streamer_type:type_name -> gnetcli.StreamerType + 9, // 5: gnetcli.CMDResult.trace:type_name -> gnetcli.CMDTraceItem + 1, // 6: gnetcli.DeviceResult.res:type_name -> gnetcli.DeviceResultStatus + 10, // 7: gnetcli.FileDownloadRequest.host_params:type_name -> gnetcli.HostParams + 3, // 8: gnetcli.FileData.status:type_name -> gnetcli.FileStatus + 14, // 9: gnetcli.FileUploadRequest.files:type_name -> gnetcli.FileData + 10, // 10: gnetcli.FileUploadRequest.host_params:type_name -> gnetcli.HostParams + 14, // 11: gnetcli.FilesResult.files:type_name -> gnetcli.FileData + 10, // 12: gnetcli.Gnetcli.SetupHostParams:input_type -> gnetcli.HostParams + 6, // 13: gnetcli.Gnetcli.Exec:input_type -> gnetcli.CMD + 6, // 14: gnetcli.Gnetcli.ExecChat:input_type -> gnetcli.CMD + 7, // 15: gnetcli.Gnetcli.AddDevice:input_type -> gnetcli.Device + 8, // 16: gnetcli.Gnetcli.ExecNetconf:input_type -> gnetcli.CMDNetconf + 8, // 17: gnetcli.Gnetcli.ExecNetconfChat:input_type -> gnetcli.CMDNetconf + 13, // 18: gnetcli.Gnetcli.Download:input_type -> gnetcli.FileDownloadRequest + 15, // 19: gnetcli.Gnetcli.Upload:input_type -> gnetcli.FileUploadRequest + 17, // 20: gnetcli.Gnetcli.SetupHostParams:output_type -> google.protobuf.Empty + 11, // 21: gnetcli.Gnetcli.Exec:output_type -> gnetcli.CMDResult + 11, // 22: gnetcli.Gnetcli.ExecChat:output_type -> gnetcli.CMDResult + 12, // 23: gnetcli.Gnetcli.AddDevice:output_type -> gnetcli.DeviceResult + 11, // 24: gnetcli.Gnetcli.ExecNetconf:output_type -> gnetcli.CMDResult + 11, // 25: gnetcli.Gnetcli.ExecNetconfChat:output_type -> gnetcli.CMDResult + 16, // 26: gnetcli.Gnetcli.Download:output_type -> gnetcli.FilesResult + 17, // 27: gnetcli.Gnetcli.Upload:output_type -> google.protobuf.Empty + 20, // [20:28] is the sub-list for method output_type + 12, // [12:20] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_server_proto_init() } @@ -1492,7 +1561,7 @@ func file_server_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_server_proto_rawDesc, - NumEnums: 3, + NumEnums: 4, NumMessages: 13, NumExtensions: 0, NumServices: 1, diff --git a/pkg/server/proto/server.proto b/pkg/server/proto/server.proto index 6a905df0..dc282f97 100644 --- a/pkg/server/proto/server.proto +++ b/pkg/server/proto/server.proto @@ -61,12 +61,19 @@ message CMDTraceItem { bytes data = 2; } +enum StreamerType { + StreamerType_unknown = 0; + StreamerType_ssh = 1; + StreamerType_telnet = 2; +} + message HostParams { string host = 1; Credentials credentials = 2; int32 port = 3; string device = 4; string ip = 5; + StreamerType streamer_type = 6; } message CMDResult { diff --git a/pkg/server/proto/server_pb2.py b/pkg/server/proto/server_pb2.py index 9d0b77a4..722809f8 100644 --- a/pkg/server/proto/server_pb2.py +++ b/pkg/server/proto/server_pb2.py @@ -16,7 +16,7 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cserver.proto\x12\x07gnetcli\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\";\n\x02QA\x12\x10\n\x08question\x18\x01 \x01(\t\x12\x0e\n\x06\x61nswer\x18\x02 \x01(\t\x12\x13\n\x0bnot_send_nl\x18\x03 \x01(\x08\".\n\x0b\x43redentials\x12\r\n\x05login\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"\xb4\x01\n\x03\x43MD\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0b\n\x03\x63md\x18\x02 \x01(\t\x12\r\n\x05trace\x18\x03 \x01(\x08\x12\x17\n\x02qa\x18\x04 \x03(\x0b\x32\x0b.gnetcli.QA\x12\x14\n\x0cread_timeout\x18\x05 \x01(\x01\x12\x13\n\x0b\x63md_timeout\x18\x06 \x01(\x01\x12\x15\n\rstring_result\x18\x08 \x01(\x08\x12(\n\x0bhost_params\x18\t \x01(\x0b\x32\x13.gnetcli.HostParams\"e\n\x06\x44\x65vice\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x11prompt_expression\x18\x02 \x01(\t\x12\x18\n\x10\x65rror_expression\x18\x03 \x01(\t\x12\x18\n\x10pager_expression\x18\x04 \x01(\t\"`\n\nCMDNetconf\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0b\n\x03\x63md\x18\x02 \x01(\t\x12\x0c\n\x04json\x18\x03 \x01(\x08\x12\x14\n\x0cread_timeout\x18\x04 \x01(\x01\x12\x13\n\x0b\x63md_timeout\x18\x05 \x01(\x01\"H\n\x0c\x43MDTraceItem\x12*\n\toperation\x18\x01 \x01(\x0e\x32\x17.gnetcli.TraceOperation\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"o\n\nHostParams\x12\x0c\n\x04host\x18\x01 \x01(\t\x12)\n\x0b\x63redentials\x18\x02 \x01(\x0b\x32\x14.gnetcli.Credentials\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x0e\n\x06\x64\x65vice\x18\x04 \x01(\t\x12\n\n\x02ip\x18\x05 \x01(\t\"\x81\x01\n\tCMDResult\x12\x0b\n\x03out\x18\x01 \x01(\x0c\x12\x0f\n\x07out_str\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\x0c\x12\x11\n\terror_str\x18\x04 \x01(\t\x12$\n\x05trace\x18\x05 \x03(\x0b\x32\x15.gnetcli.CMDTraceItem\x12\x0e\n\x06status\x18\x06 \x01(\x05\"G\n\x0c\x44\x65viceResult\x12(\n\x03res\x18\x01 \x01(\x0e\x32\x1b.gnetcli.DeviceResultStatus\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"l\n\x13\x46ileDownloadRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\r\n\x05paths\x18\x02 \x03(\t\x12\x0e\n\x06\x64\x65vice\x18\x03 \x01(\t\x12(\n\x0bhost_params\x18\x05 \x01(\x0b\x32\x13.gnetcli.HostParams\"K\n\x08\x46ileData\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.gnetcli.FileStatus\"}\n\x11\x46ileUploadRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65vice\x18\x04 \x01(\t\x12 \n\x05\x66iles\x18\x03 \x03(\x0b\x32\x11.gnetcli.FileData\x12(\n\x0bhost_params\x18\x06 \x01(\x0b\x32\x13.gnetcli.HostParams\"/\n\x0b\x46ilesResult\x12 \n\x05\x66iles\x18\x01 \x03(\x0b\x32\x11.gnetcli.FileData*f\n\x0eTraceOperation\x12\x14\n\x10Operation_notset\x10\x00\x12\x15\n\x11Operation_unknown\x10\x01\x12\x13\n\x0fOperation_write\x10\x02\x12\x12\n\x0eOperation_read\x10\x03*H\n\x12\x44\x65viceResultStatus\x12\x11\n\rDevice_notset\x10\x00\x12\r\n\tDevice_ok\x10\x01\x12\x10\n\x0c\x44\x65vice_error\x10\x02*}\n\nFileStatus\x12\x15\n\x11\x46ileStatus_notset\x10\x00\x12\x11\n\rFileStatus_ok\x10\x01\x12\x14\n\x10\x46ileStatus_error\x10\x02\x12\x18\n\x14\x46ileStatus_not_found\x10\x03\x12\x15\n\x11\x46ileStatus_is_dir\x10\x04\x32\x8c\x05\n\x07Gnetcli\x12\x64\n\x0fSetupHostParams\x12\x13.gnetcli.HostParams\x1a\x16.google.protobuf.Empty\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/setup_host_params:\x01*\x12\x41\n\x04\x45xec\x12\x0c.gnetcli.CMD\x1a\x12.gnetcli.CMDResult\"\x17\x82\xd3\xe4\x93\x02\x11\"\x0c/api/v1/exec:\x01*\x12\x32\n\x08\x45xecChat\x12\x0c.gnetcli.CMD\x1a\x12.gnetcli.CMDResult\"\x00(\x01\x30\x01\x12R\n\tAddDevice\x12\x0f.gnetcli.Device\x1a\x15.gnetcli.DeviceResult\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/add_device:\x01*\x12W\n\x0b\x45xecNetconf\x12\x13.gnetcli.CMDNetconf\x1a\x12.gnetcli.CMDResult\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/exec_netconf:\x01*\x12@\n\x0f\x45xecNetconfChat\x12\x13.gnetcli.CMDNetconf\x1a\x12.gnetcli.CMDResult\"\x00(\x01\x30\x01\x12\\\n\x08\x44ownload\x12\x1c.gnetcli.FileDownloadRequest\x1a\x14.gnetcli.FilesResult\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/api/v1/downloads:\x01*\x12W\n\x06Upload\x12\x1a.gnetcli.FileUploadRequest\x1a\x16.google.protobuf.Empty\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/api/v1/upload:\x01*B7Z5github.com/annetutil/gnetcli/pkg/server/proto;gnetclib\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cserver.proto\x12\x07gnetcli\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\";\n\x02QA\x12\x10\n\x08question\x18\x01 \x01(\t\x12\x0e\n\x06\x61nswer\x18\x02 \x01(\t\x12\x13\n\x0bnot_send_nl\x18\x03 \x01(\x08\".\n\x0b\x43redentials\x12\r\n\x05login\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"\xb4\x01\n\x03\x43MD\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0b\n\x03\x63md\x18\x02 \x01(\t\x12\r\n\x05trace\x18\x03 \x01(\x08\x12\x17\n\x02qa\x18\x04 \x03(\x0b\x32\x0b.gnetcli.QA\x12\x14\n\x0cread_timeout\x18\x05 \x01(\x01\x12\x13\n\x0b\x63md_timeout\x18\x06 \x01(\x01\x12\x15\n\rstring_result\x18\x08 \x01(\x08\x12(\n\x0bhost_params\x18\t \x01(\x0b\x32\x13.gnetcli.HostParams\"e\n\x06\x44\x65vice\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x11prompt_expression\x18\x02 \x01(\t\x12\x18\n\x10\x65rror_expression\x18\x03 \x01(\t\x12\x18\n\x10pager_expression\x18\x04 \x01(\t\"`\n\nCMDNetconf\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0b\n\x03\x63md\x18\x02 \x01(\t\x12\x0c\n\x04json\x18\x03 \x01(\x08\x12\x14\n\x0cread_timeout\x18\x04 \x01(\x01\x12\x13\n\x0b\x63md_timeout\x18\x05 \x01(\x01\"H\n\x0c\x43MDTraceItem\x12*\n\toperation\x18\x01 \x01(\x0e\x32\x17.gnetcli.TraceOperation\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x9d\x01\n\nHostParams\x12\x0c\n\x04host\x18\x01 \x01(\t\x12)\n\x0b\x63redentials\x18\x02 \x01(\x0b\x32\x14.gnetcli.Credentials\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x0e\n\x06\x64\x65vice\x18\x04 \x01(\t\x12\n\n\x02ip\x18\x05 \x01(\t\x12,\n\rstreamer_type\x18\x06 \x01(\x0e\x32\x15.gnetcli.StreamerType\"\x81\x01\n\tCMDResult\x12\x0b\n\x03out\x18\x01 \x01(\x0c\x12\x0f\n\x07out_str\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\x0c\x12\x11\n\terror_str\x18\x04 \x01(\t\x12$\n\x05trace\x18\x05 \x03(\x0b\x32\x15.gnetcli.CMDTraceItem\x12\x0e\n\x06status\x18\x06 \x01(\x05\"G\n\x0c\x44\x65viceResult\x12(\n\x03res\x18\x01 \x01(\x0e\x32\x1b.gnetcli.DeviceResultStatus\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"l\n\x13\x46ileDownloadRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\r\n\x05paths\x18\x02 \x03(\t\x12\x0e\n\x06\x64\x65vice\x18\x03 \x01(\t\x12(\n\x0bhost_params\x18\x05 \x01(\x0b\x32\x13.gnetcli.HostParams\"K\n\x08\x46ileData\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12#\n\x06status\x18\x03 \x01(\x0e\x32\x13.gnetcli.FileStatus\"}\n\x11\x46ileUploadRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65vice\x18\x04 \x01(\t\x12 \n\x05\x66iles\x18\x03 \x03(\x0b\x32\x11.gnetcli.FileData\x12(\n\x0bhost_params\x18\x06 \x01(\x0b\x32\x13.gnetcli.HostParams\"/\n\x0b\x46ilesResult\x12 \n\x05\x66iles\x18\x01 \x03(\x0b\x32\x11.gnetcli.FileData*f\n\x0eTraceOperation\x12\x14\n\x10Operation_notset\x10\x00\x12\x15\n\x11Operation_unknown\x10\x01\x12\x13\n\x0fOperation_write\x10\x02\x12\x12\n\x0eOperation_read\x10\x03*H\n\x12\x44\x65viceResultStatus\x12\x11\n\rDevice_notset\x10\x00\x12\r\n\tDevice_ok\x10\x01\x12\x10\n\x0c\x44\x65vice_error\x10\x02*W\n\x0cStreamerType\x12\x18\n\x14StreamerType_unknown\x10\x00\x12\x14\n\x10StreamerType_ssh\x10\x01\x12\x17\n\x13StreamerType_telnet\x10\x02*}\n\nFileStatus\x12\x15\n\x11\x46ileStatus_notset\x10\x00\x12\x11\n\rFileStatus_ok\x10\x01\x12\x14\n\x10\x46ileStatus_error\x10\x02\x12\x18\n\x14\x46ileStatus_not_found\x10\x03\x12\x15\n\x11\x46ileStatus_is_dir\x10\x04\x32\x8c\x05\n\x07Gnetcli\x12\x64\n\x0fSetupHostParams\x12\x13.gnetcli.HostParams\x1a\x16.google.protobuf.Empty\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/setup_host_params:\x01*\x12\x41\n\x04\x45xec\x12\x0c.gnetcli.CMD\x1a\x12.gnetcli.CMDResult\"\x17\x82\xd3\xe4\x93\x02\x11\"\x0c/api/v1/exec:\x01*\x12\x32\n\x08\x45xecChat\x12\x0c.gnetcli.CMD\x1a\x12.gnetcli.CMDResult\"\x00(\x01\x30\x01\x12R\n\tAddDevice\x12\x0f.gnetcli.Device\x1a\x15.gnetcli.DeviceResult\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/add_device:\x01*\x12W\n\x0b\x45xecNetconf\x12\x13.gnetcli.CMDNetconf\x1a\x12.gnetcli.CMDResult\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/exec_netconf:\x01*\x12@\n\x0f\x45xecNetconfChat\x12\x13.gnetcli.CMDNetconf\x1a\x12.gnetcli.CMDResult\"\x00(\x01\x30\x01\x12\\\n\x08\x44ownload\x12\x1c.gnetcli.FileDownloadRequest\x1a\x14.gnetcli.FilesResult\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/api/v1/downloads:\x01*\x12W\n\x06Upload\x12\x1a.gnetcli.FileUploadRequest\x1a\x16.google.protobuf.Empty\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/api/v1/upload:\x01*B7Z5github.com/annetutil/gnetcli/pkg/server/proto;gnetclib\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -36,12 +36,14 @@ _globals['_GNETCLI'].methods_by_name['Download']._serialized_options = b'\202\323\344\223\002\026\"\021/api/v1/downloads:\001*' _globals['_GNETCLI'].methods_by_name['Upload']._options = None _globals['_GNETCLI'].methods_by_name['Upload']._serialized_options = b'\202\323\344\223\002\023\"\016/api/v1/upload:\001*' - _globals['_TRACEOPERATION']._serialized_start=1332 - _globals['_TRACEOPERATION']._serialized_end=1434 - _globals['_DEVICERESULTSTATUS']._serialized_start=1436 - _globals['_DEVICERESULTSTATUS']._serialized_end=1508 - _globals['_FILESTATUS']._serialized_start=1510 - _globals['_FILESTATUS']._serialized_end=1635 + _globals['_TRACEOPERATION']._serialized_start=1379 + _globals['_TRACEOPERATION']._serialized_end=1481 + _globals['_DEVICERESULTSTATUS']._serialized_start=1483 + _globals['_DEVICERESULTSTATUS']._serialized_end=1555 + _globals['_STREAMERTYPE']._serialized_start=1557 + _globals['_STREAMERTYPE']._serialized_end=1644 + _globals['_FILESTATUS']._serialized_start=1646 + _globals['_FILESTATUS']._serialized_end=1771 _globals['_QA']._serialized_start=84 _globals['_QA']._serialized_end=143 _globals['_CREDENTIALS']._serialized_start=145 @@ -54,20 +56,20 @@ _globals['_CMDNETCONF']._serialized_end=575 _globals['_CMDTRACEITEM']._serialized_start=577 _globals['_CMDTRACEITEM']._serialized_end=649 - _globals['_HOSTPARAMS']._serialized_start=651 - _globals['_HOSTPARAMS']._serialized_end=762 - _globals['_CMDRESULT']._serialized_start=765 - _globals['_CMDRESULT']._serialized_end=894 - _globals['_DEVICERESULT']._serialized_start=896 - _globals['_DEVICERESULT']._serialized_end=967 - _globals['_FILEDOWNLOADREQUEST']._serialized_start=969 - _globals['_FILEDOWNLOADREQUEST']._serialized_end=1077 - _globals['_FILEDATA']._serialized_start=1079 - _globals['_FILEDATA']._serialized_end=1154 - _globals['_FILEUPLOADREQUEST']._serialized_start=1156 - _globals['_FILEUPLOADREQUEST']._serialized_end=1281 - _globals['_FILESRESULT']._serialized_start=1283 - _globals['_FILESRESULT']._serialized_end=1330 - _globals['_GNETCLI']._serialized_start=1638 - _globals['_GNETCLI']._serialized_end=2290 + _globals['_HOSTPARAMS']._serialized_start=652 + _globals['_HOSTPARAMS']._serialized_end=809 + _globals['_CMDRESULT']._serialized_start=812 + _globals['_CMDRESULT']._serialized_end=941 + _globals['_DEVICERESULT']._serialized_start=943 + _globals['_DEVICERESULT']._serialized_end=1014 + _globals['_FILEDOWNLOADREQUEST']._serialized_start=1016 + _globals['_FILEDOWNLOADREQUEST']._serialized_end=1124 + _globals['_FILEDATA']._serialized_start=1126 + _globals['_FILEDATA']._serialized_end=1201 + _globals['_FILEUPLOADREQUEST']._serialized_start=1203 + _globals['_FILEUPLOADREQUEST']._serialized_end=1328 + _globals['_FILESRESULT']._serialized_start=1330 + _globals['_FILESRESULT']._serialized_end=1377 + _globals['_GNETCLI']._serialized_start=1774 + _globals['_GNETCLI']._serialized_end=2426 # @@protoc_insertion_point(module_scope) diff --git a/pkg/server/proto/server_pb2.pyi b/pkg/server/proto/server_pb2.pyi index 2b1409cc..5f62de23 100644 --- a/pkg/server/proto/server_pb2.pyi +++ b/pkg/server/proto/server_pb2.pyi @@ -21,6 +21,12 @@ class DeviceResultStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): Device_ok: _ClassVar[DeviceResultStatus] Device_error: _ClassVar[DeviceResultStatus] +class StreamerType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + StreamerType_unknown: _ClassVar[StreamerType] + StreamerType_ssh: _ClassVar[StreamerType] + StreamerType_telnet: _ClassVar[StreamerType] + class FileStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () FileStatus_notset: _ClassVar[FileStatus] @@ -35,6 +41,9 @@ Operation_read: TraceOperation Device_notset: DeviceResultStatus Device_ok: DeviceResultStatus Device_error: DeviceResultStatus +StreamerType_unknown: StreamerType +StreamerType_ssh: StreamerType +StreamerType_telnet: StreamerType FileStatus_notset: FileStatus FileStatus_ok: FileStatus FileStatus_error: FileStatus @@ -114,18 +123,20 @@ class CMDTraceItem(_message.Message): def __init__(self, operation: _Optional[_Union[TraceOperation, str]] = ..., data: _Optional[bytes] = ...) -> None: ... class HostParams(_message.Message): - __slots__ = ("host", "credentials", "port", "device", "ip") + __slots__ = ("host", "credentials", "port", "device", "ip", "streamer_type") HOST_FIELD_NUMBER: _ClassVar[int] CREDENTIALS_FIELD_NUMBER: _ClassVar[int] PORT_FIELD_NUMBER: _ClassVar[int] DEVICE_FIELD_NUMBER: _ClassVar[int] IP_FIELD_NUMBER: _ClassVar[int] + STREAMER_TYPE_FIELD_NUMBER: _ClassVar[int] host: str credentials: Credentials port: int device: str ip: str - def __init__(self, host: _Optional[str] = ..., credentials: _Optional[_Union[Credentials, _Mapping]] = ..., port: _Optional[int] = ..., device: _Optional[str] = ..., ip: _Optional[str] = ...) -> None: ... + streamer_type: StreamerType + def __init__(self, host: _Optional[str] = ..., credentials: _Optional[_Union[Credentials, _Mapping]] = ..., port: _Optional[int] = ..., device: _Optional[str] = ..., ip: _Optional[str] = ..., streamer_type: _Optional[_Union[StreamerType, str]] = ...) -> None: ... class CMDResult(_message.Message): __slots__ = ("out", "out_str", "error", "error_str", "trace", "status") diff --git a/pkg/server/server.go b/pkg/server/server.go index 842fdad5..2af83279 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -32,6 +32,7 @@ import ( pb "github.com/annetutil/gnetcli/pkg/server/proto" "github.com/annetutil/gnetcli/pkg/streamer" "github.com/annetutil/gnetcli/pkg/streamer/ssh" + "github.com/annetutil/gnetcli/pkg/streamer/telnet" gtrace "github.com/annetutil/gnetcli/pkg/trace" ) @@ -63,13 +64,14 @@ type Server struct { } type hostParams struct { - port int - device string - creds credentials.Credentials - ip netip.Addr - proxyJump string - controlPath string - host string + port int + device string + creds credentials.Credentials + ip netip.Addr + proxyJump string + controlPath string + host string + streamerType pb.StreamerType } func makeGRPCDeviceExecError(err error) error { @@ -88,15 +90,16 @@ func makeGRPCDeviceExecError(err error) error { return rv.Err() } -func NewHostParams(creds credentials.Credentials, device string, ip netip.Addr, port int, proxyJump, controlPath, host string) hostParams { +func NewHostParams(creds credentials.Credentials, device string, ip netip.Addr, port int, proxyJump, controlPath, host string, streamerType pb.StreamerType) hostParams { return hostParams{ - port: port, - device: device, - creds: creds, - ip: ip, - proxyJump: proxyJump, - controlPath: controlPath, - host: host, + port: port, + device: device, + creds: creds, + ip: ip, + proxyJump: proxyJump, + controlPath: controlPath, + host: host, + streamerType: streamerType, } } @@ -163,9 +166,10 @@ func (m *Server) makeConnectArg(hostname string, params hostParams) (string, int return host, int(port) } -func (m *Server) makeDevice(hostname string, params hostParams, add func(op gtrace.Operation, data []byte), logger *zap.Logger) (device.Device, error) { +func (m *Server) resolveCredentials(hostname string, params hostParams) (credentials.Credentials, error) { var creds credentials.Credentials paramCreds := params.GetCredentials() + if paramCreds != nil { creds = paramCreds } else { @@ -175,35 +179,99 @@ func (m *Server) makeDevice(hostname string, params hostParams, add func(op gtra } creds = defcreds } - deviceType := params.GetDevice() - streamerOpts := []ssh.StreamerOption{ssh.WithLogger(logger), ssh.WithTrace(add)} - connHost, port := m.makeConnectArg(hostname, params) - if port > 0 { - streamerOpts = append(streamerOpts, ssh.WithPort(port)) + + return creds, nil +} + +type StreamerConfig struct { + params hostParams + creds credentials.Credentials + connHost string + port int + logger *zap.Logger +} + +func (m *Server) createStreamer(cfg StreamerConfig, add func(op gtrace.Operation, data []byte)) (streamer.Connector, error) { + switch cfg.params.streamerType { + case pb.StreamerType_StreamerType_telnet: + return m.createStreamerTelnet(cfg, add) + case pb.StreamerType_StreamerType_ssh, pb.StreamerType_StreamerType_unknown: + // Default to SSH for unknown/unspecified streamer type + return m.createStreamerSSH(cfg, add) } - if params.proxyJump != "" { - jumpHostParams, err := m.getHostParams(params.proxyJump, nil) + return nil, fmt.Errorf("unknown streamer type: %s", cfg.params.streamerType) +} + +func (m *Server) createStreamerTelnet(cfg StreamerConfig, add func(op gtrace.Operation, data []byte)) (streamer.Connector, error) { + telnetOpts := []telnet.StreamerOption{telnet.WithLogger(cfg.logger)} + if add != nil { + telnetOpts = append(telnetOpts, telnet.WithTrace(add)) + } + if cfg.port > 0 { + telnetOpts = append(telnetOpts, telnet.WithPort(cfg.port)) + } + return telnet.NewStreamer(cfg.connHost, cfg.creds, telnetOpts...), nil +} + +func (m *Server) createStreamerSSH(cfg StreamerConfig, add func(op gtrace.Operation, data []byte)) (streamer.Connector, error) { + streamerOpts := []ssh.StreamerOption{ssh.WithLogger(cfg.logger)} + if add != nil { + streamerOpts = append(streamerOpts, ssh.WithTrace(add)) + } + if cfg.port > 0 { + streamerOpts = append(streamerOpts, ssh.WithPort(cfg.port)) + } + connHost := cfg.connHost + if cfg.params.proxyJump != "" { + jumpHostParams, err := m.getHostParams(cfg.params.proxyJump, nil) if err != nil { - return nil, fmt.Errorf("unable to get host params for ssh tunnel to %s:%w", params.proxyJump, err) + return nil, fmt.Errorf("unable to get host params for ssh tunnel to %s:%w", cfg.params.proxyJump, err) } - opts := []ssh.SSHTunnelOption{ssh.SSHTunnelWithLogger(logger)} + + opts := []ssh.SSHTunnelOption{ssh.SSHTunnelWithLogger(cfg.logger)} if len(jumpHostParams.controlPath) > 0 { opts = append(opts, ssh.SSHTunnelWithControlFIle(jumpHostParams.controlPath)) } - connHost = params.host + + connHost = cfg.params.host tun := ssh.NewSSHTunnel(jumpHostParams.host, jumpHostParams.GetCredentials(), opts...) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + err = tun.CreateConnect(ctx) if err != nil { - return nil, fmt.Errorf("unable to open ssh tunnel to %s:%w", params.proxyJump, err) + return nil, fmt.Errorf("unable to open ssh tunnel to %s:%w", cfg.params.proxyJump, err) } streamerOpts = append(streamerOpts, ssh.WithSSHTunnel(tun)) } - if params.controlPath != "" { - streamerOpts = append(streamerOpts, ssh.WithSSHControlFIle(params.controlPath)) + if cfg.params.controlPath != "" { + streamerOpts = append(streamerOpts, ssh.WithSSHControlFIle(cfg.params.controlPath)) + } + return ssh.NewStreamer(connHost, cfg.creds, streamerOpts...), nil +} + +func (m *Server) makeDevice(hostname string, params hostParams, add func(op gtrace.Operation, data []byte), logger *zap.Logger) (device.Device, error) { + creds, err := m.resolveCredentials(hostname, params) + if err != nil { + return nil, fmt.Errorf("failed to resolve credentials: %w", err) } - connector := ssh.NewStreamer(connHost, creds, streamerOpts...) + + deviceType := params.GetDevice() + connHost, port := m.makeConnectArg(hostname, params) + + var connector streamer.Connector + connector, err = m.createStreamer(StreamerConfig{ + params: params, + creds: creds, + connHost: connHost, + port: port, + logger: logger, + }, add) + if err != nil { + return nil, fmt.Errorf("failed to create streamer: %w", err) + } + devFab, ok := m.deviceMaps[deviceType] if !ok { return nil, fmt.Errorf("unknown device %v", deviceType) @@ -342,26 +410,25 @@ func (m *execChatWrapper) Recv() (*pb.CMD, error) { } } -func (m *execChatWrapper) SetHeader(md metadata.MD) error { +func (m *execChatWrapper) SetHeader(_ metadata.MD) error { return errors.New("not implemented") } -func (m *execChatWrapper) SendHeader(md metadata.MD) error { +func (m *execChatWrapper) SendHeader(_ metadata.MD) error { return errors.New("not implemented") } -func (m *execChatWrapper) SetTrailer(md metadata.MD) { -} +func (m *execChatWrapper) SetTrailer(_ metadata.MD) {} func (m *execChatWrapper) Context() context.Context { return m.ctx } -func (m *execChatWrapper) SendMsg(msg interface{}) error { +func (m *execChatWrapper) SendMsg(_ interface{}) error { return errors.New("not implemented") } -func (m *execChatWrapper) RecvMsg(msg interface{}) error { +func (m *execChatWrapper) RecvMsg(_ interface{}) error { return errors.New("not implemented") } @@ -401,7 +468,7 @@ func makeNewDevice(dev *pb.Device) (*genericcli.GenericCLI, error) { return &cli, nil } -func (m *Server) AddDevice(ctx context.Context, device *pb.Device) (*pb.DeviceResult, error) { +func (m *Server) AddDevice(_ context.Context, device *pb.Device) (*pb.DeviceResult, error) { m.log.Debug("add device", zap.Any("device", device)) devName := device.GetName() m.deviceMapsMu.Lock() @@ -422,13 +489,17 @@ func (m *Server) AddDevice(ctx context.Context, device *pb.Device) (*pb.DeviceRe }, nil } -func (m *Server) SetupHostParams(ctx context.Context, cmdHostParams *pb.HostParams) (*emptypb.Empty, error) { +func (m *Server) SetupHostParams(_ context.Context, cmdHostParams *pb.HostParams) (*emptypb.Empty, error) { m.log.Debug("SetupHostParams", zap.Any("device", cmdHostParams)) ip, port, err := makeHostConnectionParams(cmdHostParams) if err != nil { return nil, err } - params := NewHostParams(nil, cmdHostParams.GetDevice(), ip, port, "", "", "") + streamerType := cmdHostParams.GetStreamerType() + if streamerType == pb.StreamerType_StreamerType_unknown { + streamerType = pb.StreamerType_StreamerType_ssh + } + params := NewHostParams(nil, cmdHostParams.GetDevice(), ip, port, "", "", "", streamerType) m.updateHostParams(cmdHostParams.GetHost(), params) return &emptypb.Empty{}, nil } @@ -473,10 +544,11 @@ func (m *Server) getHostParams(hostname string, cmdParams *pb.HostParams) (hostP credsParsed = creds } cmdHostParams = &hostParams{ - port: port, - device: cmdParams.Device, - creds: credsParsed, - ip: ip, + port: port, + device: cmdParams.Device, + creds: credsParsed, + ip: ip, + streamerType: cmdParams.GetStreamerType(), } } var res hostParams @@ -694,14 +766,6 @@ func BuildCreds(host, login, password string, enableAgent bool, sshConfig string return creds, nil } -func BuildEmptyCreds(logger *zap.Logger) credentials.Credentials { - opts := []credentials.CredentialsOption{ - credentials.WithLogger(logger), - } - creds := credentials.NewSimpleCredentials(opts...) - return creds -} - func MakeFileResult(path string, file streamer.File) *pb.FileData { res := &pb.FileData{ Path: path, diff --git a/pkg/server/server_integration_test.go b/pkg/server/server_integration_test.go index 80d666df..08477686 100644 --- a/pkg/server/server_integration_test.go +++ b/pkg/server/server_integration_test.go @@ -235,3 +235,205 @@ func newSSHServerPort(t *testing.T) (net.Listener, int32) { require.NoError(t, err) return ln, int32(v) } + +func newTelnetServerPort(t *testing.T) (net.Listener, int32) { + t.Helper() + + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { _ = ln.Close() }) + + _, telnetPortStr, err := net.SplitHostPort(ln.Addr().String()) + require.NoError(t, err) + v, err := strconv.Atoi(telnetPortStr) + require.NoError(t, err) + return ln, int32(v) +} + +// TestTelnetBasicExecution tests basic command execution via Telnet streamer +func TestTelnetBasicExecution(t *testing.T) { + if testing.Short() { + t.Skip("integration test") + } + + ln, telnetPort := newTelnetServerPort(t) + ctx := t.Context() + + const user = "telnetadmin" + const pass = "telnet-password" + + swLogger := zap.NewNop() + if testing.Verbose() { + swLogger = zap.Must(zap.NewDevelopmentConfig().Build()) + } + + go func() { + _ = gswitch.ServeTelnet(ctx, ln, gswitch.SSHServerOptions{ + Logger: swLogger, + Username: user, + Password: pass, + ConnectionErrorProb: 0, + }) + }() + + logger := zap.NewNop() + var devAuth server.Config + devAuth.DevAuth.Login = user + devAuth.DevAuth.Password = credentials.Secret(pass) + devAuth.DevAuth.UseAgent = false + + client := newGnetcliTestClient(t, devAuth, logger) + res, err := client.Exec(ctx, &pb.CMD{ + Host: "mock-telnet-sw", + Cmd: "show version", + HostParams: &pb.HostParams{ + Ip: "127.0.0.1", + Port: telnetPort, + Device: "cisco", + StreamerType: pb.StreamerType_StreamerType_telnet, + }, + }) + require.NoError(t, err) + require.Contains(t, string(res.GetOut()), "Cisco IOS Software") +} + +// TestTelnetCustomPort tests Telnet connection with custom port +func TestTelnetCustomPort(t *testing.T) { + if testing.Short() { + t.Skip("integration test") + } + + ln, telnetPort := newTelnetServerPort(t) + ctx := t.Context() + + const user = "telnetuser" + const pass = "custom-port-pass" + + swLogger := zap.NewNop() + if testing.Verbose() { + swLogger = zap.Must(zap.NewDevelopmentConfig().Build()) + } + + go func() { + _ = gswitch.ServeTelnet(ctx, ln, gswitch.SSHServerOptions{ + Logger: swLogger, + Username: user, + Password: pass, + ConnectionErrorProb: 0, + }) + }() + + logger := zap.NewNop() + var devAuth server.Config + devAuth.DevAuth.Login = user + devAuth.DevAuth.Password = credentials.Secret(pass) + devAuth.DevAuth.UseAgent = false + + client := newGnetcliTestClient(t, devAuth, logger) + + // Test that custom port is properly used + res, err := client.Exec(ctx, &pb.CMD{ + Host: "custom-port-device", + Cmd: "show version", + HostParams: &pb.HostParams{ + Ip: "127.0.0.1", + Port: telnetPort, // Using custom port + Device: "cisco", + StreamerType: pb.StreamerType_StreamerType_telnet, + }, + }) + require.NoError(t, err) + require.Contains(t, string(res.GetOut()), "Cisco IOS Software") +} + +// TestStreamerTypeSelection tests explicit streamer type selection between SSH and Telnet +func TestStreamerTypeSelection(t *testing.T) { + if testing.Short() { + t.Skip("integration test") + } + + // Setup SSH server + sshLn, sshPort := newSSHServerPort(t) + // Setup Telnet server + telnetLn, telnetPort := newTelnetServerPort(t) + + ctx := t.Context() + + const user = "multiuser" + const pass = "multi-pass" + + swLogger := zap.NewNop() + if testing.Verbose() { + swLogger = zap.Must(zap.NewDevelopmentConfig().Build()) + } + + // Start SSH server + go func() { + _ = gswitch.ServeSSH(ctx, sshLn, gswitch.SSHServerOptions{ + Logger: swLogger, + Username: user, + Password: pass, + ConnectionErrorProb: 0, + }) + }() + + // Start Telnet server + go func() { + _ = gswitch.ServeTelnet(ctx, telnetLn, gswitch.SSHServerOptions{ + Logger: swLogger, + Username: user, + Password: pass, + ConnectionErrorProb: 0, + }) + }() + + logger := zap.NewNop() + var devAuth server.Config + devAuth.DevAuth.Login = user + devAuth.DevAuth.Password = credentials.Secret(pass) + devAuth.DevAuth.UseAgent = false + + client := newGnetcliTestClient(t, devAuth, logger) + + // Test SSH streamer (default behavior - streamer_type not specified) + sshRes, err := client.Exec(ctx, &pb.CMD{ + Host: "ssh-device", + Cmd: "show version", + HostParams: &pb.HostParams{ + Ip: "127.0.0.1", + Port: sshPort, + Device: "cisco", + // StreamerType not specified - should default to SSH + }, + }) + require.NoError(t, err) + require.Contains(t, string(sshRes.GetOut()), "Cisco IOS Software") + + // Test explicit SSH streamer + sshExplicitRes, err := client.Exec(ctx, &pb.CMD{ + Host: "ssh-explicit-device", + Cmd: "show version", + HostParams: &pb.HostParams{ + Ip: "127.0.0.1", + Port: sshPort, + Device: "cisco", + StreamerType: pb.StreamerType_StreamerType_ssh, + }, + }) + require.NoError(t, err) + require.Contains(t, string(sshExplicitRes.GetOut()), "Cisco IOS Software") + + // Test Telnet streamer + telnetRes, err := client.Exec(ctx, &pb.CMD{ + Host: "telnet-device", + Cmd: "show version", + HostParams: &pb.HostParams{ + Ip: "127.0.0.1", + Port: telnetPort, + Device: "cisco", + StreamerType: pb.StreamerType_StreamerType_telnet, + }, + }) + require.NoError(t, err) + require.Contains(t, string(telnetRes.GetOut()), "Cisco IOS Software") +} diff --git a/pkg/streamer/telnet/telnet.go b/pkg/streamer/telnet/telnet.go index 1d31e5b2..0f586600 100644 --- a/pkg/streamer/telnet/telnet.go +++ b/pkg/streamer/telnet/telnet.go @@ -60,6 +60,7 @@ type Streamer struct { credentials credentials.Credentials logger *zap.Logger host string + port int addresses []net.IP conn net.Conn stdoutBuffer chan []byte @@ -102,10 +103,10 @@ func (m *Streamer) Init(ctx context.Context) error { if len(m.addresses) != 0 { endpoints = make([]string, 0, len(m.addresses)) for _, v := range m.addresses { - endpoints = append(endpoints, net.JoinHostPort(v.String(), strconv.Itoa(defaultPort))) + endpoints = append(endpoints, net.JoinHostPort(v.String(), strconv.Itoa(m.port))) } } else { - endpoints = []string{net.JoinHostPort(m.host, strconv.Itoa(defaultPort))} + endpoints = []string{net.JoinHostPort(m.host, strconv.Itoa(m.port))} } for i, v := range endpoints { @@ -136,6 +137,7 @@ func NewStreamer(host string, credentials credentials.Credentials, opts ...Strea credentials: credentials, logger: zap.NewNop(), host: host, + port: defaultPort, addresses: nil, conn: nil, stdoutBuffer: stdoutBuffer, @@ -207,6 +209,12 @@ func WithAddresses(addresses []net.IP) StreamerOption { } } +func WithPort(port int) StreamerOption { + return func(h *Streamer) { + h.port = port + } +} + func (m *Streamer) Close() { if m.conn != nil { _ = m.conn.Close()