blob: ed3520a19fe6dbf4e5e0786758918ad4503b8046 [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 Bazanski0f04bfa2018-10-14 08:47:38 -070011 "code.hackerspace.pl/q3k/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
17 pb "code.hackerspace.pl/q3k/m6220-proxy/proto"
Serge Bazanskic1515cb2018-10-04 10:36:29 +010018 tpb "code.hackerspace.pl/q3k/topo/proto/control"
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
Serge Bazanskic1515cb2018-10-04 10:36:29 +010073func (s *service) parseInterfaceStatus(res *tpb.GetPortsResponse, lines []string) error {
74 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
107 port := &tpb.SwitchPort{
108 Name: portName,
109 }
110 if speedStr == "100" {
111 port.Speed = tpb.SwitchPort_SPEED_100M
112 } else if speedStr == "1000" {
113 port.Speed = tpb.SwitchPort_SPEED_1G
114 } else if speedStr == "10000" {
115 port.Speed = tpb.SwitchPort_SPEED_10G
116 }
117 if stateStr == "Up" {
118 port.LinkState = tpb.SwitchPort_LINKSTATE_UP
119 } else if stateStr == "Down" {
120 port.LinkState = tpb.SwitchPort_LINKSTATE_DOWN
121 }
122
123 res.Ports = append(res.Ports, port)
124 }
125
126 return nil
127}
128
129func (s *service) parseInterfaceConfig(port *tpb.SwitchPort, lines []string) error {
130 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" {
140 if port.PortMode != tpb.SwitchPort_PORTMODE_INVALID {
141 return fmt.Errorf("redefinition of switchport mode")
142 }
143 if parts[2] == "access" {
144 port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
145 } else if parts[2] == "trunk" {
146 port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED
147 } else if parts[2] == "general" {
148 port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC
149 } else {
150 port.PortMode = tpb.SwitchPort_PORTMODE_MANGLED
151 }
152 }
153
154 if parts[1] == "access" {
155 if port.PortMode == tpb.SwitchPort_PORTMODE_INVALID {
156 port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
157 }
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" {
186 port.SpanningTreeMode = tpb.SwitchPort_SPANNING_TREE_MODE_PORTFAST
187 }
188 }
189
190 // no mode -> access
191 if port.PortMode == tpb.SwitchPort_PORTMODE_INVALID {
192 port.PortMode = tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED
193 }
194
195 // apply defaults
196 if port.Mtu == 0 {
197 port.Mtu = 1500
198 }
199 if port.SpanningTreeMode == tpb.SwitchPort_SPANNING_TREE_MODE_INVALID {
200 port.SpanningTreeMode = tpb.SwitchPort_SPANNING_TREE_MODE_AUTO_PORTFAST
201 }
202
203 // sanitize
204 if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED {
205 port.VlanTagged = []int32{}
206 port.Prefixes = []string{}
207 if port.VlanNative == 0 {
208 port.VlanNative = 1
209 }
210 } else if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED {
211 port.VlanNative = 0
212 port.Prefixes = []string{}
213 } else if port.PortMode == tpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC {
214 port.Prefixes = []string{}
215 if port.VlanNative == 0 {
216 port.VlanNative = 1
217 }
218 }
219 return nil
220}
221
222func (s *service) GetPorts(ctx context.Context, req *tpb.GetPortsRequest) (*tpb.GetPortsResponse, error) {
223 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()
228 res := &tpb.GetPortsResponse{}
229
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)
270 tpb.RegisterSwitchControlServer(m.GRPC(), s)
271
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}