jx/internal/resource/network_route.go
Matthew Rich 1117882ced
Some checks are pending
Lint / golangci-lint (push) Waiting to run
Declarative Tests / test (push) Waiting to run
update resources to common uri handling
2024-10-09 22:26:39 +00:00

551 lines
15 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package resource
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"net/url"
"regexp"
_ "strconv"
"strings"
"gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/codec"
"decl/internal/data"
"decl/internal/folio"
)
const (
NetworkRouteTypeName TypeName = "route"
)
func init() {
folio.DocumentRegistry.ResourceTypes.Register([]string{"route"}, func(u *url.URL) data.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 {
*Common `json:",inline" yaml:",inline"`
stater machine.Stater `json:"-" yaml:"-"`
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"`
CreateCommand *Command `yaml:"-" json:"-"`
ReadCommand *Command `yaml:"-" json:"-"`
UpdateCommand *Command `yaml:"-" json:"-"`
DeleteCommand *Command `yaml:"-" json:"-"`
config data.ConfigurationValueGetter
Resources data.ResourceMapper `json:"-" yaml:"-"`
}
func NewNetworkRoute() *NetworkRoute {
n := &NetworkRoute{Rtid: NetworkRouteTableMain}
n.Common = NewCommon(NetworkRouteTypeName, false)
n.Common.NormalizePath = n.NormalizePath
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
return n
}
func (n *NetworkRoute) NormalizePath() error {
return nil
}
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
n.Resources = resources
}
func (n *NetworkRoute) Clone() data.Resource {
newn := &NetworkRoute {
Common: n.Common,
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,
}
newn.CreateCommand, newn.ReadCommand, newn.UpdateCommand, newn.DeleteCommand = n.NewCRUD()
return newn
}
func (n *NetworkRoute) StateMachine() machine.Stater {
if n.stater == nil {
n.stater = StorageMachine(n)
}
return n.stater
}
func (n *NetworkRoute) Notify(m *machine.EventMessage) {
ctx := context.Background()
switch m.On {
case machine.ENTERSTATEEVENT:
switch m.Dest {
case "start_create":
if e := n.Create(ctx); e == nil {
if triggerErr := n.stater.Trigger("created"); triggerErr == nil {
return
}
}
n.Common.State = "absent"
case "present":
n.Common.State = "present"
}
case machine.EXITSTATEEVENT:
}
}
func (n *NetworkRoute) Create(ctx context.Context) error {
_, err := n.CreateCommand.Execute(n)
if n.CreateCommand.Extractor != nil {
if err != nil {
return n.CreateCommand.Extractor([]byte(err.Error()), n)
}
}
return nil
}
func (n *NetworkRoute) Update(ctx context.Context) error {
return n.Create(ctx)
}
func (n *NetworkRoute) Delete(ctx context.Context) error {
return nil
}
func (n *NetworkRoute) URI() string {
return fmt.Sprintf("route://%s", n.Id)
}
func (n *NetworkRoute) SetURI(uri string) error {
return nil
}
func (n *NetworkRoute) UseConfig(config data.ConfigurationValueGetter) {
n.config = config
}
func (n *NetworkRoute) Validate() error {
return fmt.Errorf("failed")
}
func (n *NetworkRoute) Apply() error {
switch n.Common.State {
case "absent":
case "present":
}
return nil
}
func (n *NetworkRoute) Load(docData []byte, f codec.Format) (err error) {
err = f.StringDecoder(string(docData)).Decode(n)
return
}
func (n *NetworkRoute) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
err = f.Decoder(r).Decode(n)
return
}
func (n *NetworkRoute) LoadString(docData string, f codec.Format) (err error) {
err = f.StringDecoder(docData).Decode(n)
return
}
func (n *NetworkRoute) LoadDecl(yamlResourceDeclaration string) error {
return n.LoadString(yamlResourceDeclaration, codec.FormatYaml)
}
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) {
out, err := n.ReadCommand.Execute(n)
if err != nil {
return nil, err
}
exErr := n.ReadCommand.Extractor(out, n)
if exErr != nil {
return nil, exErr
}
/*
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
}
func (n *NetworkRoute) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
return NewNetworkRouteCreateCommand(), NewNetworkRouteReadCommand(), NewNetworkRouteUpdateCommand(), NewNetworkRouteDeleteCommand()
}
func NewNetworkRouteCreateCommand() *Command {
c := NewCommand()
c.Path = "ip"
c.Args = []CommandArg{
CommandArg("route"),
CommandArg("add"),
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
CommandArg("{{ if .Metric }}metric {{ .Metric }}{{ end }}"),
}
return c
}
func NewNetworkRouteReadCommand() *Command {
c := NewCommand()
c.Path = "ip"
c.Args = []CommandArg{
CommandArg("route"),
CommandArg("show"),
CommandArg("{{ if .To }}to {{ .To }}{{ end }}"),
CommandArg("{{ if .Rtid }}table {{ .Rtid }}{{ end }}"),
CommandArg("{{ if .Gateway }}via {{ .Gateway }}{{ end }}"),
CommandArg("{{ if .Proto }}protocol {{ .Proto }}{{ end }}"),
CommandArg("{{ if .Scope }}scope {{ .Scope }}{{ end }}"),
CommandArg("{{ if .RouteType }}type {{ .RouteType }}{{ end }}"),
CommandArg("{{ if .Interface }}dev {{ .Interface }}{{ end }}"),
}
c.Extractor = func(out []byte, target any) error {
n := target.(*NetworkRoute)
routes := strings.Split(string(out), "\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.Common.State = "present"
} else {
n.Common.State = "absent"
}
return nil
}
return c
}
func NewNetworkRouteUpdateCommand() *Command {
c := NewCommand()
c.Path = "ip"
c.Args = []CommandArg{
CommandArg("del"),
CommandArg("{{ .Name }}"),
}
return c
}
func NewNetworkRouteDeleteCommand() *Command {
return nil
}