// 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" "gitea.rosskeen.house/rosskeen.house/machine" ) 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" yaml:"to"` Interface string `json:"interface" yaml:"interface"` Gateway string `json:"gateway" yaml:"gateway"` Metric uint `json:"metric" yaml:"metric"` Rtid NetworkRouteTableId `json:"rtid" yaml:"rtid"` RouteType NetworkRouteType `json:"routetype" yaml:"routetype"` Scope NetworkRouteScope `json:"scope" yaml:"scope"` Proto NetworkRouteProto `json:"proto" yaml:"proto"` State string `json:"state" yaml:"state"` } func NewNetworkRoute() *NetworkRoute { return &NetworkRoute{Rtid: NetworkRouteTableMain} } func (n *NetworkRoute) Clone() Resource { return &NetworkRoute { Id: n.Id, To: n.To, Interface: n.Interface, Gateway: n.Gateway, Metric: n.Metric, Rtid: n.Rtid, RouteType: n.RouteType, Scope: n.Scope, Proto: n.Proto, State: n.State, } } func (n *NetworkRoute) StateMachine() machine.Stater { return StorageMachine() } func (n *NetworkRoute) URI() string { return fmt.Sprintf("route://%s", n.Id) } func (n *NetworkRoute) SetURI(uri string) error { return nil } func (n *NetworkRoute) Validate() error { return fmt.Errorf("failed") } func (n *NetworkRoute) Apply() error { switch n.State { case "absent": case "present": } return nil } func (n *NetworkRoute) Load(r io.Reader) error { c := NewYAMLDecoder(r) return c.Decode(n) } func (n *NetworkRoute) LoadDecl(yamlResourceDeclaration string) error { c := NewYAMLStringDecoder(yamlResourceDeclaration) return c.Decode(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 { if err := json.Unmarshal(data, n); err != nil { return err } return nil }