From 71810755682bf8c1952f07b9e87201a22712bebf Mon Sep 17 00:00:00 2001 From: Matthew Rich Date: Tue, 2 Apr 2024 13:35:40 -0700 Subject: [PATCH] add network_route type --- internal/resource/network_route.go | 358 ++++++++++++++++++++++++ internal/resource/network_route_test.go | 59 ++++ 2 files changed, 417 insertions(+) create mode 100644 internal/resource/network_route.go create mode 100644 internal/resource/network_route_test.go diff --git a/internal/resource/network_route.go b/internal/resource/network_route.go new file mode 100644 index 0000000..59841f7 --- /dev/null +++ b/internal/resource/network_route.go @@ -0,0 +1,358 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package resource + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "gopkg.in/yaml.v3" + _ "io" + "net/url" + "os/exec" + "regexp" + _ "strconv" + "strings" +) + +func init() { + ResourceTypes.Register("route", func(u *url.URL) Resource { + n := NewNetworkRoute() + return n + }) +} + +/* + +ROUTE := NODE_SPEC [ INFO_SPEC ] +NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ] + [ table TABLE_ID ] [ proto RTPROTO ] + [ scope SCOPE ] [ metric METRIC ] + [ ttl-propagate { enabled | disabled } ] +INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ]... + +NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ] + [ dev STRING ] [ weight NUMBER ] NHFLAGS +FAMILY := [ inet | inet6 | mpls | bridge | link ] +OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ as [ to ] ADDRESS ] + [ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ] + [ window NUMBER ] [ cwnd NUMBER ] [ initcwnd NUMBER ] + [ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ] + [ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ] + [ features FEATURES ] [ quickack BOOL ] [ congctl NAME ] + [ pref PREF ] [ expires TIME ] [ fastopen_no_cookie BOOL ] +NHFLAGS := [ onlink | pervasive ] +PREF := [ low | medium | high ] +TIME := NUMBER[s|ms] +BOOL := [1|0] +FEATURES := ecn +ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ] +ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ] +SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup] +SEGMODE := [ encap | inline ] +SEG6LOCAL := action ACTION [ OPTIONS ] [ count ] +ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 | + End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps | + End.BM | End.S | End.AS | End.AM | End.BPF } +OPTIONS := OPTION [ OPTIONS ] +OPTION := { srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV | + table TABLEID | vrftable TABLEID | endpoint PROGNAME } +IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE +ROUTE_GET_FLAGS := [ fibmatch ] + +*/ + +type NetworkRouteType string + +const ( + NetworkRouteTypeUnicast = "unicast" + NetworkRouteTypeLocal = "local" + NetworkRouteTypeBroadcast = "broadcast" + NetworkRouteTypeMulticast = "multicast" + NetworkRouteTypeThrow = "throw" + NetworkRouteTypeUnreachable = "unreachable" + NetworkRouteTypeProhibit = "prohibit" + NetworkRouteTypeBlackhole = "blackhole" + NetworkRouteTypeNat = "nat" +) + +type NetworkRouteTableId string + +const ( + NetworkRouteTableLocal = "local" + NetworkRouteTableMain = "main" + NetworkRouteTableDefault = "default" + NetworkRouteTableAll = "all" +) + +var NetworkRouteNumber = regexp.MustCompile(`^[0-9]+$`) + +type NetworkRouteScope string + +const ( + NetworkRouteScopeHost = "host" + NetworkRouteScopeLink = "link" + NetworkRouteScopeGlobal = "global" +) + +type NetworkRouteProto string + +const ( + NetworkRouteProtoKernel = "kernel" + NetworkRouteProtoBoot = "boot" + NetworkRouteProtoStatic = "static" +) + +// Manage the state of network routes +type NetworkRoute struct { + Id string + To string `json:"to"` + Interface string `json:"interface"` + Gateway string `json:"gateway"` + Metric uint `json:"metric"` + Rtid NetworkRouteTableId `json:"rtid"` + RouteType NetworkRouteType `json:"routetype"` + Scope NetworkRouteScope `json:"scope"` + Proto NetworkRouteProto `json:"proto"` + State string `json:"state"` +} + +func NewNetworkRoute() *NetworkRoute { + return &NetworkRoute{Rtid: NetworkRouteTableMain} +} + +func (n *NetworkRoute) URI() string { + return fmt.Sprintf("route://%s", n.Id) +} + +func (n *NetworkRoute) SetURI(uri string) error { + return nil +} + +func (n *NetworkRoute) Apply() error { + + switch n.State { + case "absent": + case "present": + } + return nil +} + +func (n *NetworkRoute) LoadDecl(yamlNetworkRouteResourceDeclaration string) error { + return YamlLoadDecl(yamlNetworkRouteResourceDeclaration, n) +} + +func (n *NetworkRoute) ResolveId(ctx context.Context) string { + uri := fmt.Sprintf("%s?gateway=%s&interface=%s&rtid=%s&metric=%d&type=%s&scope=%s", + n.To, n.Gateway, n.Interface, n.Rtid, n.Metric, n.RouteType, n.Scope) + fmt.Printf("%#v\n", uri) + n.Id = hex.EncodeToString([]byte(uri)) + return n.Id +} + +func (n *NetworkRoute) Read(ctx context.Context) ([]byte, error) { + var cmdArgs []string = make([]string, 17) + cmdArgs[0] = "route" + cmdArgs[1] = "show" + if len(n.To) > 0 { + cmdArgs = append(cmdArgs, "to", n.To) + } + if len(n.Rtid) > 0 { + cmdArgs = append(cmdArgs, "table", string(n.Rtid)) + } + if len(n.Gateway) > 0 { + cmdArgs = append(cmdArgs, "via", n.Gateway) + } + if len(n.Proto) > 0 { + cmdArgs = append(cmdArgs, "protocol", string(n.Proto)) + } + if len(n.Scope) > 0 { + cmdArgs = append(cmdArgs, "scope", string(n.Scope)) + } + if len(n.RouteType) > 0 { + cmdArgs = append(cmdArgs, "type", string(n.RouteType)) + } + if len(n.Interface) > 0 { + cmdArgs = append(cmdArgs, "dev", n.Interface) + } + + readRouteTable, errRouteList := exec.Command("ip", cmdArgs...).Output() + if errRouteList != nil { + return nil, errRouteList + } + routes := strings.Split(string(readRouteTable), "\n") + if len(routes) == 1 { + fields := strings.Split(routes[0], " ") + numberOfFields := len(fields) + if numberOfFields > 1 { + n.To = fields[0] + for i := 1; i < numberOfFields; i += 2 { + n.SetField(fields[i], fields[i+1]) + } + } + } + n.ResolveId(ctx) + return yaml.Marshal(n) +} + +func (n *NetworkRoute) SetField(name string, value string) { + switch name { + case "to": + n.To = value + case "table": + n.Rtid = NetworkRouteTableId(value) + case "via": + n.Gateway = value + case "protocol": + n.Proto = NetworkRouteProto(value) + case "scope": + n.Scope = NetworkRouteScope(value) + case "type": + n.RouteType = NetworkRouteType(value) + case "dev": + n.Interface = value + } +} + +func (n *NetworkRoute) GetField(name string) string { + switch name { + case "to": + return n.To + case "table": + return string(n.Rtid) + case "via": + return n.Gateway + case "protocol": + return string(n.Proto) + case "scope": + return string(n.Scope) + case "type": + return string(n.RouteType) + case "dev": + return n.Interface + } + return "" +} + +func (n *NetworkRoute) Type() string { return "route" } + +func (n *NetworkRouteType) UnmarshalValue(value string) error { + switch value { + case string(NetworkRouteTypeUnicast), string(NetworkRouteTypeLocal), string(NetworkRouteTypeBroadcast), string(NetworkRouteTypeMulticast), string(NetworkRouteTypeThrow), string(NetworkRouteTypeUnreachable), string(NetworkRouteTypeProhibit), string(NetworkRouteTypeBlackhole), string(NetworkRouteTypeNat): + *n = NetworkRouteType(value) + return nil + default: + return errors.New("invalid NetworkRouteType value") + } +} + +func (n *NetworkRouteType) UnmarshalJSON(data []byte) error { + var s string + if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { + return unmarshalRouteTypeErr + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteType) UnmarshalYAML(value *yaml.Node) error { + var s string + if err := value.Decode(&s); err != nil { + return err + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteTableId) UnmarshalValue(value string) error { + switch value { + case NetworkRouteNumber.FindString(value): + fallthrough + case string(NetworkRouteTableLocal), string(NetworkRouteTableMain), string(NetworkRouteTableDefault), string(NetworkRouteTableAll): + *n = NetworkRouteTableId(value) + return nil + default: + return errors.New("invalid NetworkRouteTableId value") + } +} + +func (n *NetworkRouteTableId) UnmarshalJSON(data []byte) error { + var s string + if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { + return unmarshalRouteTypeErr + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteTableId) UnmarshalYAML(value *yaml.Node) error { + var s string + if err := value.Decode(&s); err != nil { + return err + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteScope) UnmarshalValue(value string) error { + switch value { + case NetworkRouteNumber.FindString(value): + fallthrough + case string(NetworkRouteScopeHost), string(NetworkRouteScopeLink), string(NetworkRouteScopeGlobal): + *n = NetworkRouteScope(value) + return nil + default: + return errors.New("invalid NetworkRouteScope value") + } +} + +func (n *NetworkRouteScope) UnmarshalJSON(data []byte) error { + var s string + if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { + return unmarshalRouteTypeErr + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteScope) UnmarshalYAML(value *yaml.Node) error { + var s string + if err := value.Decode(&s); err != nil { + return err + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteProto) UnmarshalValue(value string) error { + switch value { + case NetworkRouteNumber.FindString(value): + fallthrough + case string(NetworkRouteProtoKernel), string(NetworkRouteProtoBoot), string(NetworkRouteProtoStatic): + *n = NetworkRouteProto(value) + return nil + default: + return errors.New("invalid NetworkRouteProto value") + } +} + +func (n *NetworkRouteProto) UnmarshalJSON(data []byte) error { + var s string + if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { + return unmarshalRouteTypeErr + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRouteProto) UnmarshalYAML(value *yaml.Node) error { + var s string + if err := value.Decode(&s); err != nil { + return err + } + return n.UnmarshalValue(s) +} + +func (n *NetworkRoute) UnmarshalJSON(data []byte) error { + fmt.Printf("UnmarshalJSON %#v\n", string(data)) + panic(data) + if err := json.Unmarshal(data, n); err != nil { + return err + } + return nil +} diff --git a/internal/resource/network_route_test.go b/internal/resource/network_route_test.go new file mode 100644 index 0000000..666c64b --- /dev/null +++ b/internal/resource/network_route_test.go @@ -0,0 +1,59 @@ +// Copyright 2024 Matthew Rich . All rights reserved. + +package resource + +import ( + "context" + _ "encoding/json" + _ "fmt" + "github.com/stretchr/testify/assert" + _ "gopkg.in/yaml.v3" + _ "io" + _ "log" + _ "net/http" + _ "net/http/httptest" + _ "net/url" + _ "os" + _ "path/filepath" + _ "strings" + _ "syscall" + "testing" + _ "time" +) + +func TestNewNetworkRouteResource(t *testing.T) { + n := NewNetworkRoute() + assert.NotEqual(t, nil, n) +} + +func TestNetworkRouteApplyResourceTransformation(t *testing.T) { + n := NewNetworkRoute() + assert.NotEqual(t, nil, n) + + //e := f.Apply() + //assert.Equal(t, nil, e) +} + +func TestReadNetworkRoute(t *testing.T) { + ctx := context.Background() + + declarationAttributes := ` + to: "192.168.0.0/24" + interface: "eth0" + gateway: "192.168.0.1" + metric: 0 + routetype: "unicast" + scope: "link" + state: present +` + + testRoute := NewNetworkRoute() + e := testRoute.LoadDecl(declarationAttributes) + assert.Equal(t, nil, e) + testRoute.Apply() + r, e := testRoute.Read(ctx) + + assert.Nil(t, e) + assert.NotNil(t, r) + assert.Equal(t, NetworkRouteType("unicast"), testRoute.RouteType) +}