blob: f4c348b46f57aecdba1a83a9caf8661efb7a06e8 [file] [log] [blame]
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +01001package main
2
3import (
4 "context"
5 "flag"
Serge Bazanskic1515cb2018-10-04 10:36:29 +01006 "fmt"
Serge Bazanskic1515cb2018-10-04 10:36:29 +01007 "reflect"
8 "strconv"
9 "strings"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010010
Serge Bazanski2353f202018-10-25 12:31:20 +010011 "code.hackerspace.pl/hscloud/go/mirko"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010012 "github.com/golang/glog"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010013 "github.com/ziutek/telnet"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010014 "google.golang.org/grpc/codes"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010015 "google.golang.org/grpc/status"
16
Sergiusz Bazanski61594bb2019-07-21 15:20:51 +020017 pb "code.hackerspace.pl/hscloud/dc/m6220-proxy/proto"
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +020018 dpb "code.hackerspace.pl/hscloud/dc/proto"
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010019)
20
21var (
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010022 flagSwitchAddress string
23 flagSwitchUsername string
24 flagSwitchPassword string
25)
26
27func init() {
28 flag.Set("logtostderr", "true")
29}
30
31type service struct {
32 connectionSemaphore chan int
33}
34
35func (s *service) connect() (*cliClient, error) {
36 s.connectionSemaphore <- 1
37 conn, err := telnet.Dial("tcp", flagSwitchAddress)
38 if err != nil {
39 <-s.connectionSemaphore
40 return nil, err
41 }
42
43 cli := newCliClient(conn, flagSwitchUsername, flagSwitchPassword)
44 return cli, nil
45}
46
47func (s *service) disconnect() {
48 <-s.connectionSemaphore
49}
50
51func (s *service) RunCommand(ctx context.Context, req *pb.RunCommandRequest) (*pb.RunCommandResponse, error) {
52 if req.Command == "" {
53 return nil, status.Error(codes.InvalidArgument, "command cannot be null")
54 }
55
56 cli, err := s.connect()
57 if err != nil {
Serge Bazanskic1515cb2018-10-04 10:36:29 +010058 return nil, status.Error(codes.Unavailable, "could not connect to switch")
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +010059 }
60 defer s.disconnect()
61
62 lines, effective, err := cli.runCommand(ctx, req.Command)
63 if err != nil {
64 return nil, err
65 }
66 res := &pb.RunCommandResponse{
67 EffectiveCommand: effective,
68 Lines: lines,
69 }
70 return res, nil
71}
72
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +020073func (s *service) parseInterfaceStatus(res *dpb.GetPortsResponse, lines []string) error {
Serge Bazanskic1515cb2018-10-04 10:36:29 +010074 if len(lines) < 4 {
75 return fmt.Errorf("need at least 4 lines of output, got %d", len(lines))
76 }
77 if lines[0] != "" {
78 return fmt.Errorf("expected first line to be empty, is %q", lines[0])
79 }
80 header1parts := strings.Fields(lines[1])
81 if want := []string{"Port", "Description", "Duplex", "Speed", "Neg", "Link", "Flow", "Control"}; !reflect.DeepEqual(want, header1parts) {
82 return fmt.Errorf("expected header1 to be %v, got %v", want, header1parts)
83 }
84
85 header2parts := strings.Fields(lines[2])
86 if want := []string{"State", "Status"}; !reflect.DeepEqual(want, header2parts) {
87 return fmt.Errorf("expected header2 to be %v, got %v", want, header2parts)
88 }
89
90 if lines[3][0] != '-' {
91 return fmt.Errorf("expected header3 to start with -, got %q", lines[3])
92 }
93
94 for _, line := range lines[4:] {
95 parts := strings.Fields(line)
96 if len(parts) < 6 {
97 break
98 }
99 portName := parts[0]
100 if strings.HasPrefix(portName, "Gi") && strings.HasPrefix(portName, "Ti") {
101 break
102 }
103
104 speedStr := parts[len(parts)-4]
105 stateStr := parts[len(parts)-2]
106
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200107 port := &dpb.SwitchPort{
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100108 Name: portName,
109 }
110 if speedStr == "100" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200111 port.Speed = dpb.SwitchPort_SPEED_100M
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100112 } else if speedStr == "1000" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200113 port.Speed = dpb.SwitchPort_SPEED_1G
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100114 } else if speedStr == "10000" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200115 port.Speed = dpb.SwitchPort_SPEED_10G
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100116 }
117 if stateStr == "Up" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200118 port.LinkState = dpb.SwitchPort_LINKSTATE_UP
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100119 } else if stateStr == "Down" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200120 port.LinkState = dpb.SwitchPort_LINKSTATE_DOWN
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100121 }
122
123 res.Ports = append(res.Ports, port)
124 }
125
126 return nil
127}
128
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200129func (s *service) parseInterfaceConfig(port *dpb.SwitchPort, lines []string) error {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100130 glog.Infof("%+v", port)
131 for _, line := range lines {
132 glog.Infof("%s: %q", port.Name, line)
133 parts := strings.Fields(line)
134 if len(parts) < 1 {
135 continue
136 }
137
138 if len(parts) >= 2 && parts[0] == "switchport" {
139 if parts[1] == "mode" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200140 if port.PortMode != dpb.SwitchPort_PORTMODE_INVALID {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100141 return fmt.Errorf("redefinition of switchport mode")
142 }
143 if parts[2] == "access" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200144 port.PortMode = dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100145 } else if parts[2] == "trunk" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200146 port.PortMode = dpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100147 } else if parts[2] == "general" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200148 port.PortMode = dpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100149 } else {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200150 port.PortMode = dpb.SwitchPort_PORTMODE_MANGLED
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100151 }
152 }
153
154 if parts[1] == "access" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200155 if port.PortMode == dpb.SwitchPort_PORTMODE_INVALID {
156 port.PortMode = dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100157 }
158 if len(parts) > 3 && parts[2] == "vlan" {
159 vlan, err := strconv.Atoi(parts[3])
160 if err != nil {
161 return fmt.Errorf("invalid vlan: %q", parts[3])
162 }
163 port.VlanNative = int32(vlan)
164 }
165 }
166
167 if parts[1] == "trunk" {
168 if len(parts) >= 5 && parts[2] == "allowed" && parts[3] == "vlan" {
169 vlans := strings.Split(parts[4], ",")
170 for _, vlan := range vlans {
171 vlanNum, err := strconv.Atoi(vlan)
172 if err != nil {
173 return fmt.Errorf("invalid vlan: %q", parts[3])
174 }
175 port.VlanTagged = append(port.VlanTagged, int32(vlanNum))
176 }
177 }
178 }
179 } else if len(parts) >= 2 && parts[0] == "mtu" {
180 mtu, err := strconv.Atoi(parts[1])
181 if err != nil {
182 return fmt.Errorf("invalid mtu: %q", parts[3])
183 }
184 port.Mtu = int32(mtu)
185 } else if len(parts) >= 2 && parts[0] == "spanning-tree" && parts[1] == "portfast" {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200186 port.SpanningTreeMode = dpb.SwitchPort_SPANNING_TREE_MODE_PORTFAST
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100187 }
188 }
189
190 // no mode -> access
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200191 if port.PortMode == dpb.SwitchPort_PORTMODE_INVALID {
192 port.PortMode = dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100193 }
194
195 // apply defaults
196 if port.Mtu == 0 {
197 port.Mtu = 1500
198 }
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200199 if port.SpanningTreeMode == dpb.SwitchPort_SPANNING_TREE_MODE_INVALID {
200 port.SpanningTreeMode = dpb.SwitchPort_SPANNING_TREE_MODE_AUTO_PORTFAST
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100201 }
202
203 // sanitize
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200204 if port.PortMode == dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100205 port.VlanTagged = []int32{}
206 port.Prefixes = []string{}
207 if port.VlanNative == 0 {
208 port.VlanNative = 1
209 }
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200210 } else if port.PortMode == dpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100211 port.VlanNative = 0
212 port.Prefixes = []string{}
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200213 } else if port.PortMode == dpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100214 port.Prefixes = []string{}
215 if port.VlanNative == 0 {
216 port.VlanNative = 1
217 }
218 }
219 return nil
220}
221
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200222func (s *service) GetPorts(ctx context.Context, req *dpb.GetPortsRequest) (*dpb.GetPortsResponse, error) {
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100223 cli, err := s.connect()
224 if err != nil {
225 return nil, status.Error(codes.Unavailable, "could not connect to switch")
226 }
227 defer s.disconnect()
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200228 res := &dpb.GetPortsResponse{}
Serge Bazanskic1515cb2018-10-04 10:36:29 +0100229
230 statusLines, _, err := cli.runCommand(ctx, "show interface status")
231 if err != nil {
232 return nil, status.Error(codes.Unavailable, "could not get interface status from switch")
233 }
234
235 err = s.parseInterfaceStatus(res, statusLines)
236 if err != nil {
237 return nil, status.Errorf(codes.Unavailable, "could not parse interface status from switch: %v", err)
238 }
239
240 for _, port := range res.Ports {
241 configLines, _, err := cli.runCommand(ctx, "show run interface "+port.Name)
242 if err != nil {
243 return nil, status.Error(codes.Unavailable, "could not get interface config from switch")
244 }
245 err = s.parseInterfaceConfig(port, configLines)
246 if err != nil {
247 return nil, status.Errorf(codes.Unavailable, "could not parse interface config from switch: %v", err)
248 }
249 }
250
251 return res, nil
252}
253
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100254func main() {
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100255 flag.StringVar(&flagSwitchAddress, "switch_address", "127.0.0.1:23", "Telnet address of M6220")
256 flag.StringVar(&flagSwitchUsername, "switch_username", "admin", "Switch login username")
257 flag.StringVar(&flagSwitchPassword, "switch_password", "admin", "Switch login password")
258 flag.Parse()
259
260 s := &service{
261 connectionSemaphore: make(chan int, 1),
262 }
263
Serge Bazanski0f04bfa2018-10-14 08:47:38 -0700264 m := mirko.New()
265 if err := m.Listen(); err != nil {
266 glog.Exitf("Listen(): %v", err)
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100267 }
268
Serge Bazanski0f04bfa2018-10-14 08:47:38 -0700269 pb.RegisterM6220ProxyServer(m.GRPC(), s)
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200270 dpb.RegisterSwitchControlServer(m.GRPC(), s)
Serge Bazanski0f04bfa2018-10-14 08:47:38 -0700271
272 if err := m.Serve(); err != nil {
273 glog.Exitf("Serve(): %v", err)
274 }
Sergiusz Bazanskiff5af692018-08-29 19:20:46 +0100275
276 select {}
277}