diff --git a/.gitignore b/.gitignore index 045c22e..d9568ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -arista-proxy *swp diff --git a/m6220-proxy/cli.go b/m6220-proxy/cli.go new file mode 100644 index 0000000..b9642cf --- /dev/null +++ b/m6220-proxy/cli.go @@ -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 +} diff --git a/m6220-proxy/main.go b/m6220-proxy/main.go new file mode 100644 index 0000000..ed3520a --- /dev/null +++ b/m6220-proxy/main.go @@ -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 {} +} diff --git a/m6220-proxy/proto/generate.go b/m6220-proxy/proto/generate.go new file mode 100644 index 0000000..fc6193d --- /dev/null +++ b/m6220-proxy/proto/generate.go @@ -0,0 +1,3 @@ +//go:generate protoc -I.. ../proxy.proto --go_out=plugins=grpc:. + +package proto diff --git a/m6220-proxy/proto/proxy.pb.go b/m6220-proxy/proto/proxy.pb.go new file mode 100644 index 0000000..148b878 --- /dev/null +++ b/m6220-proxy/proto/proxy.pb.go @@ -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", +} diff --git a/m6220-proxy/proxy.proto b/m6220-proxy/proxy.proto new file mode 100644 index 0000000..d35a2c3 --- /dev/null +++ b/m6220-proxy/proxy.proto @@ -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); +};