Merge branch 'master' of /home/q3k/Projects/hscloud/go/src/code.hackerspace.pl/q3k/m6220-proxy

master
Serge Bazanski 2018-10-25 12:27:01 +01:00
commit b33646bdb9
6 changed files with 741 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
arista-proxy
*swp

243
m6220-proxy/cli.go Normal file
View File

@ -0,0 +1,243 @@
package main
import (
"context"
"fmt"
"strings"
"github.com/golang/glog"
"github.com/ziutek/telnet"
"golang.org/x/net/trace"
)
type cliClient struct {
conn *telnet.Conn
username string
password string
loggedIn bool
promptHostname string
}
func newCliClient(c *telnet.Conn, username, password string) *cliClient {
return &cliClient{
conn: c,
username: username,
password: password,
}
}
func (c *cliClient) readUntil(ctx context.Context, delims ...string) (string, error) {
chStr := make(chan string, 1)
chErr := make(chan error, 1)
go func() {
s, err := c.conn.ReadUntil(delims...)
if err != nil {
chErr <- err
return
}
chStr <- string(s)
}()
select {
case <-ctx.Done():
return "", fmt.Errorf("context done")
case err := <-chErr:
c.trace(ctx, "readUntil failed: %v", err)
return "", err
case s := <-chStr:
c.trace(ctx, "readUntil <- %q", s)
return s, nil
}
}
func (c *cliClient) readString(ctx context.Context, delim byte) (string, error) {
chStr := make(chan string, 1)
chErr := make(chan error, 1)
go func() {
s, err := c.conn.ReadString(delim)
if err != nil {
chErr <- err
return
}
chStr <- s
}()
select {
case <-ctx.Done():
return "", fmt.Errorf("context done")
case err := <-chErr:
c.trace(ctx, "readString failed: %v", err)
return "", err
case s := <-chStr:
c.trace(ctx, "readString <- %q", s)
return s, nil
}
}
func (c *cliClient) writeLine(ctx context.Context, s string) error {
n, err := c.conn.Write([]byte(s + "\n"))
if got, want := n, len(s)+1; got != want {
err = fmt.Errorf("wrote %d bytes out of %d", got, want)
}
if err != nil {
c.trace(ctx, "writeLine failed: %v", err)
return err
}
c.trace(ctx, "writeLine -> %q", s)
return nil
}
func (c *cliClient) trace(ctx context.Context, f string, parts ...interface{}) {
tr, ok := trace.FromContext(ctx)
if !ok {
fmted := fmt.Sprintf(f, parts...)
glog.Infof("[no trace] %s", fmted)
return
}
tr.LazyPrintf(f, parts...)
}
func (c *cliClient) logIn(ctx context.Context) error {
if c.loggedIn {
return nil
}
// Provide username.
prompt, err := c.readString(ctx, ':')
if err != nil {
return fmt.Errorf("could not read username prompt: %v", err)
}
if !strings.HasSuffix(prompt, "User:") {
return fmt.Errorf("invalid username prompt: %v", err)
}
if err := c.writeLine(ctx, c.username); err != nil {
return fmt.Errorf("could not write username: %v")
}
// Provide password.
prompt, err = c.readString(ctx, ':')
if err != nil {
return fmt.Errorf("could not read password prompt: %v", err)
}
if !strings.HasSuffix(prompt, "Password:") {
return fmt.Errorf("invalid password prompt: %v", err)
}
if err := c.writeLine(ctx, c.password); err != nil {
return fmt.Errorf("could not write password: %v")
}
// Get unprivileged prompt.
prompt, err = c.readString(ctx, '>')
if err != nil {
return fmt.Errorf("could not read unprivileged prompt: %v", err)
}
parts := strings.Split(prompt, "\r\n")
c.promptHostname = strings.TrimSuffix(parts[len(parts)-1], ">")
// Enable privileged mode.
if err := c.writeLine(ctx, "enable"); err != nil {
return fmt.Errorf("could not write enable: %v")
}
// Provide password (again)
prompt, err = c.readString(ctx, ':')
if err != nil {
return fmt.Errorf("could not read password prompt: %v", err)
}
if !strings.HasSuffix(prompt, "Password:") {
return fmt.Errorf("invalid password prompt: %v", err)
}
if err := c.writeLine(ctx, c.password); err != nil {
return fmt.Errorf("could not write password: %v")
}
// Get privileged prompt.
prompt, err = c.readString(ctx, '#')
if err != nil {
return fmt.Errorf("could not read privileged prompt: %v", err)
}
if !strings.HasSuffix(prompt, c.promptHostname+"#") {
return fmt.Errorf("unexpected privileged prompt: %v", prompt)
}
// Disable pager.
if err := c.writeLine(ctx, "terminal length 0"); err != nil {
return fmt.Errorf("could not diable pager: %v", err)
}
prompt, err = c.readString(ctx, '#')
if err != nil {
return fmt.Errorf("could not disable pager: %v", err)
}
if !strings.HasSuffix(prompt, c.promptHostname+"#") {
return fmt.Errorf("unexpected privileged prompt: %v", prompt)
}
// Success!
c.loggedIn = true
c.trace(ctx, "logged into %v", c.promptHostname)
return nil
}
func (c *cliClient) runCommand(ctx context.Context, command string) ([]string, string, error) {
if err := c.logIn(ctx); err != nil {
return nil, "", fmt.Errorf("could not log in: %v", err)
}
// First, synchronize to prompt.
attempts := 3
for {
c.writeLine(ctx, "")
line, err := c.readString(ctx, '\n')
if err != nil {
return nil, "", fmt.Errorf("while synchronizing to prompt: %v", err)
}
line = strings.Trim(line, "\r\n")
if strings.HasSuffix(line, c.promptHostname+"#") {
break
}
attempts -= 1
if attempts == 0 {
return nil, "", fmt.Errorf("could not find prompt, last result %q", line)
}
}
// Send comand.
c.writeLine(ctx, command)
// First, read until prompt again.
if _, err := c.readUntil(ctx, c.promptHostname+"#"); err != nil {
return nil, "", fmt.Errorf("could not get command hostname echo: %v", err)
}
loopback, err := c.readUntil(ctx, "\r\n")
if err != nil {
return nil, "", fmt.Errorf("could not get command loopback: %v", err)
}
loopback = strings.Trim(loopback, "\r\n")
c.trace(ctx, "effective command: %q", loopback)
// Read until we have a standalone prompt with no newline afterwards.
data, err := c.readUntil(ctx, c.promptHostname+"#")
if err != nil {
return nil, "", fmt.Errorf("could not get command results: %v", err)
}
lines := []string{}
for _, line := range strings.Split(data, "\r\n") {
if line == c.promptHostname+"#" {
break
}
lines = append(lines, line)
}
c.trace(ctx, "command %q returned lines: %v", command, lines)
return lines, loopback, nil
}

277
m6220-proxy/main.go Normal file
View File

@ -0,0 +1,277 @@
package main
import (
"context"
"flag"
"fmt"
"reflect"
"strconv"
"strings"
"code.hackerspace.pl/q3k/mirko"
"github.com/golang/glog"
"github.com/ziutek/telnet"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "code.hackerspace.pl/q3k/m6220-proxy/proto"
tpb "code.hackerspace.pl/q3k/topo/proto/control"
)
var (
flagSwitchAddress string
flagSwitchUsername string
flagSwitchPassword string
)
func init() {
flag.Set("logtostderr", "true")
}
type service struct {
connectionSemaphore chan int
}
func (s *service) connect() (*cliClient, error) {
s.connectionSemaphore <- 1
conn, err := telnet.Dial("tcp", flagSwitchAddress)
if err != nil {
<-s.connectionSemaphore
return nil, err
}
cli := newCliClient(conn, flagSwitchUsername, flagSwitchPassword)
return cli, nil
}
func (s *service) disconnect() {
<-s.connectionSemaphore
}
func (s *service) RunCommand(ctx context.Context, req *pb.RunCommandRequest) (*pb.RunCommandResponse, error) {
if req.Command == "" {
return nil, status.Error(codes.InvalidArgument, "command cannot be null")
}
cli, err := s.connect()
if err != nil {
return nil, status.Error(codes.Unavailable, "could not connect to switch")
}
defer s.disconnect()
lines, effective, err := cli.runCommand(ctx, req.Command)
if err != nil {
return nil, err
}
res := &pb.RunCommandResponse{
EffectiveCommand: effective,
Lines: lines,
}
return res, nil
}
func (s *service) parseInterfaceStatus(res *tpb.GetPortsResponse, lines []string) error {
if len(lines) < 4 {
return fmt.Errorf("need at least 4 lines of output, got %d", len(lines))
}
if lines[0] != "" {
return fmt.Errorf("expected first line to be empty, is %q", lines[0])
}
header1parts := strings.Fields(lines[1])
if want := []string{"Port", "Description", "Duplex", "Speed", "Neg", "Link", "Flow", "Control"}; !reflect.DeepEqual(want, header1parts) {
return fmt.Errorf("expected header1 to be %v, got %v", want, header1parts)
}
header2parts := strings.Fields(lines[2])
if want := []string{"State", "Status"}; !reflect.DeepEqual(want, header2parts) {
return fmt.Errorf("expected header2 to be %v, got %v", want, header2parts)
}
if lines[3][0] != '-' {
return fmt.Errorf("expected header3 to start with -, got %q", lines[3])
}
for _, line := range lines[4:] {
parts := strings.Fields(line)
if len(parts) < 6 {
break
}
portName := parts[0]
if strings.HasPrefix(portName, "Gi") && strings.HasPrefix(portName, "Ti") {
break
}
speedStr := parts[len(parts)-4]
stateStr := parts[len(parts)-2]
port := &tpb.SwitchPort{
Name: portName,
}
if speedStr == "100" {
port.Speed = tpb.SwitchPort_SPEED_100M
} else if speedStr == "1000" {
port.Speed = tpb.SwitchPort_SPEED_1G
} else if speedStr == "10000" {
port.Speed = tpb.SwitchPort_SPEED_10G
}
if stateStr == "Up" {
port.LinkState = tpb.SwitchPort_LINKSTATE_UP
} else if stateStr == "Down" {
port.LinkState = tpb.SwitchPort_LINKSTATE_DOWN
}
res.Ports = append(res.Ports, port)
}
return nil
}
func (s *service) parseInterfaceConfig(port *tpb.SwitchPort, lines []string) error {
glog.Infof("%+v", port)
for _, line := range lines {
glog.Infof("%s: %q", port.Name, line)
parts := strings.Fields(line)
if len(parts) < 1 {
continue
}
if len(parts) >= 2 && parts[0] == "switchport" {
if parts[1] == "mode" {
if port.PortMode != tpb.SwitchPort_PORTMODE_INVALID {
return fmt.Errorf("redefinition of switchport mode")
}
if parts[2] == "access" {
port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
} else if parts[2] == "trunk" {
port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED
} else if parts[2] == "general" {
port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC
} else {
port.PortMode = tpb.SwitchPort_PORTMODE_MANGLED
}
}
if parts[1] == "access" {
if port.PortMode == tpb.SwitchPort_PORTMODE_INVALID {
port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
}
if len(parts) > 3 && parts[2] == "vlan" {
vlan, err := strconv.Atoi(parts[3])
if err != nil {
return fmt.Errorf("invalid vlan: %q", parts[3])
}
port.VlanNative = int32(vlan)
}
}
if parts[1] == "trunk" {
if len(parts) >= 5 && parts[2] == "allowed" && parts[3] == "vlan" {
vlans := strings.Split(parts[4], ",")
for _, vlan := range vlans {
vlanNum, err := strconv.Atoi(vlan)
if err != nil {
return fmt.Errorf("invalid vlan: %q", parts[3])
}
port.VlanTagged = append(port.VlanTagged, int32(vlanNum))
}
}
}
} else if len(parts) >= 2 && parts[0] == "mtu" {
mtu, err := strconv.Atoi(parts[1])
if err != nil {
return fmt.Errorf("invalid mtu: %q", parts[3])
}
port.Mtu = int32(mtu)
} else if len(parts) >= 2 && parts[0] == "spanning-tree" && parts[1] == "portfast" {
port.SpanningTreeMode = tpb.SwitchPort_SPANNING_TREE_MODE_PORTFAST
}
}
// no mode -> access
if port.PortMode == tpb.SwitchPort_PORTMODE_INVALID {
port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
}
// apply defaults
if port.Mtu == 0 {
port.Mtu = 1500
}
if port.SpanningTreeMode == tpb.SwitchPort_SPANNING_TREE_MODE_INVALID {
port.SpanningTreeMode = tpb.SwitchPort_SPANNING_TREE_MODE_AUTO_PORTFAST
}
// sanitize
if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED {
port.VlanTagged = []int32{}
port.Prefixes = []string{}
if port.VlanNative == 0 {
port.VlanNative = 1
}
} else if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED {
port.VlanNative = 0
port.Prefixes = []string{}
} else if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC {
port.Prefixes = []string{}
if port.VlanNative == 0 {
port.VlanNative = 1
}
}
return nil
}
func (s *service) GetPorts(ctx context.Context, req *tpb.GetPortsRequest) (*tpb.GetPortsResponse, error) {
cli, err := s.connect()
if err != nil {
return nil, status.Error(codes.Unavailable, "could not connect to switch")
}
defer s.disconnect()
res := &tpb.GetPortsResponse{}
statusLines, _, err := cli.runCommand(ctx, "show interface status")
if err != nil {
return nil, status.Error(codes.Unavailable, "could not get interface status from switch")
}
err = s.parseInterfaceStatus(res, statusLines)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "could not parse interface status from switch: %v", err)
}
for _, port := range res.Ports {
configLines, _, err := cli.runCommand(ctx, "show run interface "+port.Name)
if err != nil {
return nil, status.Error(codes.Unavailable, "could not get interface config from switch")
}
err = s.parseInterfaceConfig(port, configLines)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "could not parse interface config from switch: %v", err)
}
}
return res, nil
}
func main() {
flag.StringVar(&flagSwitchAddress, "switch_address", "127.0.0.1:23", "Telnet address of M6220")
flag.StringVar(&flagSwitchUsername, "switch_username", "admin", "Switch login username")
flag.StringVar(&flagSwitchPassword, "switch_password", "admin", "Switch login password")
flag.Parse()
s := &service{
connectionSemaphore: make(chan int, 1),
}
m := mirko.New()
if err := m.Listen(); err != nil {
glog.Exitf("Listen(): %v", err)
}
pb.RegisterM6220ProxyServer(m.GRPC(), s)
tpb.RegisterSwitchControlServer(m.GRPC(), s)
if err := m.Serve(); err != nil {
glog.Exitf("Serve(): %v", err)
}
select {}
}

View File

@ -0,0 +1,3 @@
//go:generate protoc -I.. ../proxy.proto --go_out=plugins=grpc:.
package proto

View File

@ -0,0 +1,203 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proxy.proto
package proto
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type RunCommandRequest struct {
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RunCommandRequest) Reset() { *m = RunCommandRequest{} }
func (m *RunCommandRequest) String() string { return proto.CompactTextString(m) }
func (*RunCommandRequest) ProtoMessage() {}
func (*RunCommandRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_700b50b08ed8dbaf, []int{0}
}
func (m *RunCommandRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RunCommandRequest.Unmarshal(m, b)
}
func (m *RunCommandRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RunCommandRequest.Marshal(b, m, deterministic)
}
func (m *RunCommandRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RunCommandRequest.Merge(m, src)
}
func (m *RunCommandRequest) XXX_Size() int {
return xxx_messageInfo_RunCommandRequest.Size(m)
}
func (m *RunCommandRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RunCommandRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RunCommandRequest proto.InternalMessageInfo
func (m *RunCommandRequest) GetCommand() string {
if m != nil {
return m.Command
}
return ""
}
type RunCommandResponse struct {
EffectiveCommand string `protobuf:"bytes,1,opt,name=effective_command,json=effectiveCommand,proto3" json:"effective_command,omitempty"`
Lines []string `protobuf:"bytes,2,rep,name=lines,proto3" json:"lines,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RunCommandResponse) Reset() { *m = RunCommandResponse{} }
func (m *RunCommandResponse) String() string { return proto.CompactTextString(m) }
func (*RunCommandResponse) ProtoMessage() {}
func (*RunCommandResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_700b50b08ed8dbaf, []int{1}
}
func (m *RunCommandResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RunCommandResponse.Unmarshal(m, b)
}
func (m *RunCommandResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RunCommandResponse.Marshal(b, m, deterministic)
}
func (m *RunCommandResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RunCommandResponse.Merge(m, src)
}
func (m *RunCommandResponse) XXX_Size() int {
return xxx_messageInfo_RunCommandResponse.Size(m)
}
func (m *RunCommandResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RunCommandResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RunCommandResponse proto.InternalMessageInfo
func (m *RunCommandResponse) GetEffectiveCommand() string {
if m != nil {
return m.EffectiveCommand
}
return ""
}
func (m *RunCommandResponse) GetLines() []string {
if m != nil {
return m.Lines
}
return nil
}
func init() {
proto.RegisterType((*RunCommandRequest)(nil), "proto.RunCommandRequest")
proto.RegisterType((*RunCommandResponse)(nil), "proto.RunCommandResponse")
}
func init() { proto.RegisterFile("proxy.proto", fileDescriptor_700b50b08ed8dbaf) }
var fileDescriptor_700b50b08ed8dbaf = []byte{
// 165 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x28, 0xca, 0xaf,
0xa8, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0xba, 0x5c, 0x82, 0x41,
0xa5, 0x79, 0xce, 0xf9, 0xb9, 0xb9, 0x89, 0x79, 0x29, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25,
0x42, 0x12, 0x5c, 0xec, 0xc9, 0x10, 0x11, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x18, 0x57,
0x29, 0x9c, 0x4b, 0x08, 0x59, 0x79, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x36, 0x97, 0x60,
0x6a, 0x5a, 0x5a, 0x6a, 0x72, 0x49, 0x66, 0x59, 0x6a, 0x3c, 0xaa, 0x4e, 0x01, 0xb8, 0x04, 0x54,
0x93, 0x90, 0x08, 0x17, 0x6b, 0x4e, 0x66, 0x5e, 0x6a, 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0x67,
0x10, 0x84, 0x63, 0xe4, 0xcf, 0xc5, 0xe5, 0x6b, 0x66, 0x64, 0x64, 0x10, 0x00, 0x72, 0xa2, 0x90,
0x23, 0x17, 0x17, 0xc2, 0x1a, 0x21, 0x09, 0x88, 0x93, 0xf5, 0x30, 0x1c, 0x2a, 0x25, 0x89, 0x45,
0x06, 0xe2, 0xa6, 0x24, 0x36, 0xb0, 0x8c, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x39, 0x6d, 0xab,
0xdd, 0xf5, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// M6220ProxyClient is the client API for M6220Proxy service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type M6220ProxyClient interface {
RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (*RunCommandResponse, error)
}
type m6220ProxyClient struct {
cc *grpc.ClientConn
}
func NewM6220ProxyClient(cc *grpc.ClientConn) M6220ProxyClient {
return &m6220ProxyClient{cc}
}
func (c *m6220ProxyClient) RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (*RunCommandResponse, error) {
out := new(RunCommandResponse)
err := c.cc.Invoke(ctx, "/proto.M6220Proxy/RunCommand", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// M6220ProxyServer is the server API for M6220Proxy service.
type M6220ProxyServer interface {
RunCommand(context.Context, *RunCommandRequest) (*RunCommandResponse, error)
}
func RegisterM6220ProxyServer(s *grpc.Server, srv M6220ProxyServer) {
s.RegisterService(&_M6220Proxy_serviceDesc, srv)
}
func _M6220Proxy_RunCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RunCommandRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(M6220ProxyServer).RunCommand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.M6220Proxy/RunCommand",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(M6220ProxyServer).RunCommand(ctx, req.(*RunCommandRequest))
}
return interceptor(ctx, in, info, handler)
}
var _M6220Proxy_serviceDesc = grpc.ServiceDesc{
ServiceName: "proto.M6220Proxy",
HandlerType: (*M6220ProxyServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "RunCommand",
Handler: _M6220Proxy_RunCommand_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proxy.proto",
}

15
m6220-proxy/proxy.proto Normal file
View File

@ -0,0 +1,15 @@
syntax = "proto3";
package proto;
message RunCommandRequest {
string command = 1;
};
message RunCommandResponse {
string effective_command = 1;
repeated string lines = 2;
};
service M6220Proxy {
rpc RunCommand(RunCommandRequest) returns (RunCommandResponse);
};