From fb8544129e450ffb4c6d9f759eb1e8c1974d3710 Mon Sep 17 00:00:00 2001 From: Pavel Perminov Date: Mon, 11 Nov 2024 21:02:43 +0200 Subject: [PATCH] feat(input.mikrotik): Add Mikrotik plugin (#16080) --- plugins/inputs/all/mikrotik.go | 5 + plugins/inputs/mikrotik/README.md | 90 ++++++ plugins/inputs/mikrotik/mikrotik.go | 224 +++++++++++++++ plugins/inputs/mikrotik/mikrotik_test.go | 256 ++++++++++++++++++ plugins/inputs/mikrotik/modules.go | 31 +++ plugins/inputs/mikrotik/parsers.go | 103 +++++++ plugins/inputs/mikrotik/sample.conf | 41 +++ .../inputs/mikrotik/testData/interface.json | 87 ++++++ .../testData/interface_wireguard_peers.json | 21 ++ .../interface_wireless_registration.json | 72 +++++ .../testData/ip_dhcp_server_lease.json | 45 +++ .../testData/ip_firewall_connection.json | 60 ++++ .../mikrotik/testData/ip_firewall_filter.json | 53 ++++ .../mikrotik/testData/ip_firewall_mangle.json | 15 + .../mikrotik/testData/ip_firewall_nat.json | 15 + .../testData/ipv6_firewall_connection.json | 18 ++ .../testData/ipv6_firewall_filter.json | 27 ++ .../testData/ipv6_firewall_mangle.json | 14 + .../mikrotik/testData/ipv6_firewall_nat.json | 14 + .../mikrotik/testData/system_resourses.json | 19 ++ .../mikrotik/testData/system_routerboard.json | 10 + .../mikrotik/testData/system_script.json | 26 ++ plugins/inputs/mikrotik/tools.go | 47 ++++ plugins/inputs/mikrotik/types.go | 111 ++++++++ 24 files changed, 1404 insertions(+) create mode 100644 plugins/inputs/all/mikrotik.go create mode 100644 plugins/inputs/mikrotik/README.md create mode 100755 plugins/inputs/mikrotik/mikrotik.go create mode 100755 plugins/inputs/mikrotik/mikrotik_test.go create mode 100644 plugins/inputs/mikrotik/modules.go create mode 100755 plugins/inputs/mikrotik/parsers.go create mode 100644 plugins/inputs/mikrotik/sample.conf create mode 100644 plugins/inputs/mikrotik/testData/interface.json create mode 100644 plugins/inputs/mikrotik/testData/interface_wireguard_peers.json create mode 100644 plugins/inputs/mikrotik/testData/interface_wireless_registration.json create mode 100644 plugins/inputs/mikrotik/testData/ip_dhcp_server_lease.json create mode 100644 plugins/inputs/mikrotik/testData/ip_firewall_connection.json create mode 100644 plugins/inputs/mikrotik/testData/ip_firewall_filter.json create mode 100644 plugins/inputs/mikrotik/testData/ip_firewall_mangle.json create mode 100644 plugins/inputs/mikrotik/testData/ip_firewall_nat.json create mode 100644 plugins/inputs/mikrotik/testData/ipv6_firewall_connection.json create mode 100644 plugins/inputs/mikrotik/testData/ipv6_firewall_filter.json create mode 100644 plugins/inputs/mikrotik/testData/ipv6_firewall_mangle.json create mode 100644 plugins/inputs/mikrotik/testData/ipv6_firewall_nat.json create mode 100644 plugins/inputs/mikrotik/testData/system_resourses.json create mode 100644 plugins/inputs/mikrotik/testData/system_routerboard.json create mode 100644 plugins/inputs/mikrotik/testData/system_script.json create mode 100755 plugins/inputs/mikrotik/tools.go create mode 100644 plugins/inputs/mikrotik/types.go diff --git a/plugins/inputs/all/mikrotik.go b/plugins/inputs/all/mikrotik.go new file mode 100644 index 0000000000000..7afd31b39539b --- /dev/null +++ b/plugins/inputs/all/mikrotik.go @@ -0,0 +1,5 @@ +//go:build !custom || inputs || inputs.mikrotik + +package all + +import _ "github.com/influxdata/telegraf/plugins/inputs/mikrotik" // register plugin diff --git a/plugins/inputs/mikrotik/README.md b/plugins/inputs/mikrotik/README.md new file mode 100644 index 0000000000000..95f6a2d20df8c --- /dev/null +++ b/plugins/inputs/mikrotik/README.md @@ -0,0 +1,90 @@ +# Mikrotik Input Plugin + +This plugin gathers metrics from [Mikrotik's RouterOS][mikrotik] such as +interface statistics, uptime etc + +[mikrotik]: https://mikrotik.com/software + +## Global configuration options + +In addition to the plugin-specific configuration settings, plugins support +additional global and plugin configuration settings. These settings are used to +modify metrics, tags, and field or create aliases and configure ordering, etc. +See the [CONFIGURATION.md][CONFIGURATION.md] for more details. + +[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins + +## Configuration + +```toml @sample.conf +[[inputs.mikrotik]] + ## Mikrotik's address to query. Make sure that REST API is enabled: https://help.mikrotik.com/docs/spaces/ROS/pages/47579162/REST+API + address = "https://192.168.88.1" + + ## User to use. Read access rights will be enough + username = "admin" + password = "password" + + ## Mikrotik's entities whose comments contain this strings will be ignored + # ignore_comments = [ + # "block", + # "doNotGatherMetricsFromThis" + # ] + + ## Modules available to use (default: system_resourses) + # include_modules = [ + # "interface", + # "interface_wireguard_peers", + # "interface_wireless_registration", + # "ip_dhcp_server_lease", + # "ip_firewall_connection", + # "ip_firewall_filter", + # "ip_firewall_nat", + # "ip_firewall_mangle", + # "ipv6_firewall_connection", + # "ipv6_firewall_filter", + # "ipv6_firewall_nat", + # "ipv6_firewall_mangle", + # "system_script", + # "system_resourses" + # ] + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false + + ## HTTP response timeout + # response_timeout = "5s" +``` + +## Metrics + +For each specific module, a unique set of metrics and tags will be provided +based on the JSON structure returned by the REST endpoint. You can refer to +the `tagFields` and `valueFields` lists in `types.go` for a full set of +tags and values. + +When querying Mikrotik, all available fields across different metrics +will be requested. However, Mikrotik’s design only returns fields that are +present in the current module’s response, ignoring any fields that don’t +apply to the specific endpoint. Disabled entities in Mikrotik are +automatically excluded from the response. + +## Example Output + +```text +mikrotik,.id=*1,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=ether1,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:B6,model=RBD52G-5HacD2HnD,name=ether1,platform=MikroTik,running=true,serial-number=SERIALNUMBER,source-module=interface,type=ether,version=7.16\ (stable) fp-rx-byte=23815497595i,fp-rx-packet=18015083i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=1i,rx-byte=23887557927i,rx-drop=0i,rx-error=0i,rx-packet=18015083i,tx-byte=1129765037i,tx-drop=0i,tx-error=0i,tx-packet=5384706i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*2,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=ether2,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:B7,model=RBD52G-5HacD2HnD,name=ether2,platform=MikroTik,running=false,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=ether,version=7.16\ (stable) fp-rx-byte=0i,fp-rx-packet=0i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=0i,rx-byte=0i,rx-drop=0i,rx-error=0i,rx-packet=0i,tx-byte=0i,tx-drop=0i,tx-error=0i,tx-packet=0i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*3,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=ether3,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:B8,model=RBD52G-5HacD2HnD,name=ether3,platform=MikroTik,running=true,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=ether,version=7.16\ (stable) fp-rx-byte=91438550i,fp-rx-packet=1244518i,fp-tx-byte=4403947442i,fp-tx-packet=2914053i,link-downs=1i,rx-byte=96416622i,rx-drop=0i,rx-error=0i,rx-packet=1244518i,tx-byte=4422341564i,tx-drop=0i,tx-error=0i,tx-packet=2929010i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*4,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=ether4,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:B9,model=RBD52G-5HacD2HnD,name=ether4,platform=MikroTik,running=false,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=ether,version=7.16\ (stable) fp-rx-byte=0i,fp-rx-packet=0i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=0i,rx-byte=0i,rx-drop=0i,rx-error=0i,rx-packet=0i,tx-byte=0i,tx-drop=0i,tx-error=0i,tx-packet=0i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*5,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=ether5,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:BA,model=RBD52G-5HacD2HnD,name=ether5,platform=MikroTik,running=false,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=ether,version=7.16\ (stable) fp-rx-byte=0i,fp-rx-packet=0i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=0i,rx-byte=0i,rx-drop=0i,rx-error=0i,rx-packet=0i,tx-byte=0i,tx-drop=0i,tx-error=0i,tx-packet=0i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*6,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=wlan1,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:BB,model=RBD52G-5HacD2HnD,name=wlan1,platform=MikroTik,running=false,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=wlan,version=7.16\ (stable) fp-rx-byte=1984519i,fp-rx-packet=8740i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=12i,rx-byte=1984519i,rx-drop=0i,rx-error=0i,rx-packet=8740i,tx-byte=17087451i,tx-drop=0i,tx-error=0i,tx-packet=47921i,tx-queue-drop=1i 1730320979000000000 +mikrotik,.id=*7,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,default-name=wlan2,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:BC,model=RBD52G-5HacD2HnD,name=wlan2,platform=MikroTik,running=true,serial-number=SERIALNUMBER,slave=true,source-module=interface,type=wlan,version=7.16\ (stable) fp-rx-byte=5525090211i,fp-rx-packet=8360162i,fp-tx-byte=87942233i,fp-tx-packet=1222212i,link-downs=0i,rx-byte=5525090211i,rx-drop=0i,rx-error=0i,rx-packet=8360162i,tx-byte=23832544176i,tx-drop=0i,tx-error=0i,tx-packet=19198103i,tx-queue-drop=52532i 1730320979000000000 +mikrotik,.id=*8,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=11:22:33:44:55:BB,model=RBD52G-5HacD2HnD,name=lan,platform=MikroTik,running=true,serial-number=SERIALNUMBER,source-module=interface,type=bridge,version=7.16\ (stable) fp-rx-byte=1107864899i,fp-rx-packet=5393891i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=0i,rx-byte=1124747646i,rx-drop=0i,rx-error=0i,rx-packet=5463554i,tx-byte=23815054209i,tx-drop=0i,tx-error=0i,tx-packet=18013585i,tx-queue-drop=0i 1730320979000000000 +mikrotik,.id=*14,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,disabled=false,firmware-type=ipq4000L,host=localhost,mac-address=00:00:00:00:00:00,model=RBD52G-5HacD2HnD,name=lo,platform=MikroTik,running=true,serial-number=SERIALNUMBER,source-module=interface,type=loopback,version=7.16\ (stable) fp-rx-byte=0i,fp-rx-packet=0i,fp-tx-byte=0i,fp-tx-packet=0i,link-downs=0i,rx-byte=491147i,rx-drop=0i,rx-error=0i,rx-packet=2839i,tx-byte=491147i,tx-drop=0i,tx-error=0i,tx-packet=2839i,tx-queue-drop=0i 1730320979000000000 +mikrotik,architecture-name=arm,board-name=hAP\ ac^2,cpu=ARM,current-firmware=7.15.3,firmware-type=ipq4000L,host=localhost,model=RBD52G-5HacD2HnD,platform=MikroTik,serial-number=SERIALNUMBER,source-module=system_resourses,version=7.16\ (stable) cpu-frequency=672i,cpu-load=0i,free-hdd-space=1482752i,free-memory=55685120i,total-memory=134217728i,uptime=85201i,write-sect-since-reboot=2344i,write-sect-total=30313i 1730320979000000000 + +``` diff --git a/plugins/inputs/mikrotik/mikrotik.go b/plugins/inputs/mikrotik/mikrotik.go new file mode 100755 index 0000000000000..2680dc896541a --- /dev/null +++ b/plugins/inputs/mikrotik/mikrotik.go @@ -0,0 +1,224 @@ +//go:generate ../../../tools/readme_config_includer/generator +package mikrotik + +import ( + _ "embed" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync" + "time" + + "github.com/influxdata/telegraf/plugins/inputs" + + common_tls "github.com/influxdata/telegraf/plugins/common/tls" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" +) + +//go:embed sample.conf +var sampleConfig string + +type Mikrotik struct { + Address string `toml:"address"` + IgnoreCert bool `toml:"ignore_cert,omitempty"` + ResponseTimeout config.Duration `toml:"response_timeout"` + IgnoreComments []string `toml:"ignore_comments"` + IncludeModules []string `toml:"include_modules"` + Username config.Secret `toml:"username"` + Password config.Secret `toml:"password"` + Log telegraf.Logger `toml:"-"` + common_tls.ClientConfig + + tags map[string]string + + url []mikrotikEndpoint + + client *http.Client + + systemTagsURL []string +} + +func (*Mikrotik) SampleConfig() string { + return sampleConfig +} + +func (h *Mikrotik) Start() error { + return h.getSystemTags() +} + +func (h *Mikrotik) Init() error { + if h.Username.Empty() { + return errors.New("mikrotik init -> username must be present") + } + + if len(h.IncludeModules) == 0 { + h.IncludeModules = append(h.IncludeModules, "system_resourses") + } + + mainPropList, systemResourcesPropList, systemRouterBoardPropList := createPropLists() + + h.systemTagsURL = []string{ + h.Address + "/rest/system/resource?" + systemResourcesPropList, + h.Address + "/rest/system/routerboard?" + systemRouterBoardPropList, + } + + for _, selectedModule := range h.IncludeModules { + if _, ok := modules[selectedModule]; !ok { + return fmt.Errorf("mikrotik init -> module %s does not exist or has a typo. Correct modules are: %s", selectedModule, getModuleNames()) + } + h.url = append(h.url, mikrotikEndpoint{name: selectedModule, url: fmt.Sprintf("%s%s?%s", h.Address, modules[selectedModule], mainPropList)}) + } + + ignoreCommentsFunction = basicCommentAndDisableFilter(h.IgnoreComments) + + return h.getClient() +} + +func (h *Mikrotik) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + for _, u := range h.url { + wg.Add(1) + go func(url mikrotikEndpoint) { + defer wg.Done() + + if err := h.gatherURL(url, acc); err != nil { + acc.AddError(fmt.Errorf("gather -> %w", err)) + } + }(u) + } + wg.Wait() + + return nil +} + +func (h *Mikrotik) getClient() (err error) { + tlsCfg, err := h.ClientConfig.TLSConfig() + if err != nil { + return fmt.Errorf("getClient -> %w", err) + } + + h.client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsCfg, + }, + Timeout: time.Duration(h.ResponseTimeout), + } + + return nil +} + +func (h *Mikrotik) getSystemTags() error { + h.tags = make(map[string]string) + for _, tagURL := range h.systemTagsURL { + request, err := http.NewRequest("GET", tagURL, nil) + if err != nil { + return fmt.Errorf("getSystemTags -> %w", err) + } + + err = h.setRequestAuth(request) + if err != nil { + return fmt.Errorf("getSystemTags -> %w", err) + } + + binaryData, err := h.queryData(request) + if err != nil { + return fmt.Errorf("getSystemTags -> %w", err) + } + + err = json.Unmarshal(binaryData, &h.tags) + if err != nil { + return fmt.Errorf("getSystemTags -> %w", err) + } + } + return nil +} + +func (h *Mikrotik) gatherURL(endpoint mikrotikEndpoint, acc telegraf.Accumulator) error { + request, err := http.NewRequest("GET", endpoint.url, nil) + if err != nil { + return fmt.Errorf("gatherURL -> %w", err) + } + + err = h.setRequestAuth(request) + if err != nil { + return fmt.Errorf("gatherURL -> %w", err) + } + + binaryData, err := h.queryData(request) + if err != nil { + return fmt.Errorf("gatherURL -> %w", err) + } + + timestamp := time.Now() + + result, err := binToCommon(binaryData) + if err != nil { + return fmt.Errorf("gatherURL -> %w", err) + } + + parsedData, err := parse(result) + if err != nil { + return fmt.Errorf("gatherURL -> %w", err) + } + + for _, point := range parsedData { + point.Tags["source-module"] = endpoint.name + for n, v := range h.tags { + point.Tags[n] = v + } + acc.AddFields("mikrotik", point.Fields, point.Tags, timestamp) + } + + return nil +} + +func (h *Mikrotik) setRequestAuth(request *http.Request) error { + username, err := h.Username.Get() + if err != nil { + return fmt.Errorf("setRequestAuth: username -> %w", err) + } + defer username.Destroy() + + password, err := h.Password.Get() + if err != nil { + return fmt.Errorf("setRequestAuth: pasword -> %w", err) + } + defer password.Destroy() + + request.SetBasicAuth(username.String(), password.String()) + + return nil +} + +func (h *Mikrotik) queryData(request *http.Request) (data []byte, err error) { + resp, err := h.client.Do(request) + if err != nil { + return data, fmt.Errorf("queryData -> %w", err) + } + + defer resp.Body.Close() + defer h.client.CloseIdleConnections() + + if resp.StatusCode != http.StatusOK { + return data, fmt.Errorf("queryData -> received status code %d (%s), expected 200", + resp.StatusCode, + http.StatusText(resp.StatusCode)) + } + + data, err = io.ReadAll(resp.Body) + if err != nil { + return data, fmt.Errorf("queryData -> %w", err) + } + + return data, err +} + +func init() { + inputs.Add("mikrotik", func() telegraf.Input { + return &Mikrotik{ResponseTimeout: config.Duration(time.Second * 5)} + }) +} diff --git a/plugins/inputs/mikrotik/mikrotik_test.go b/plugins/inputs/mikrotik/mikrotik_test.go new file mode 100755 index 0000000000000..5359ed45dabb0 --- /dev/null +++ b/plugins/inputs/mikrotik/mikrotik_test.go @@ -0,0 +1,256 @@ +package mikrotik + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +var username = config.NewSecret([]byte("testOfTheMikrotik")) + +func generateHandler() func(http.ResponseWriter, *http.Request) { + crackers := map[string]string{} + for k, v := range modules { + crackers[v] = k + } + crackers["/rest/system/routerboard"] = "system_routerboard" + return func(w http.ResponseWriter, r *http.Request) { + filePath := "./testData/" + crackers[r.URL.Path] + ".json" + data, err := os.ReadFile(filePath) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + } else { + _, err = w.Write(data) + if err != nil { + panic(err) + } + } + } +} + +func TestUptimeConverter(t *testing.T) { + values := map[string]time.Duration{ + "1d0h0m0s": time.Duration(24) * time.Hour, + "0d0m0s": 0, + "1s0m0s": 0, + "0d0a0s": 0, + "2m29s": time.Duration(29)*time.Second + time.Duration(2)*time.Minute, + "23m35s": time.Duration(35)*time.Second + time.Duration(23)*time.Minute, + "1d0h52m0s": time.Duration(24)*time.Hour + time.Duration(52)*time.Minute, + "12s": time.Duration(12) * time.Second, + "121d12h12m0s": (time.Duration(24*121) * time.Hour) + + (time.Duration(12) * time.Hour) + + (time.Duration(12) * time.Minute) + + (time.Duration(0) * time.Second), + } + for uptime, result := range values { + d, err := parseUptimeIntoDuration(uptime) + require.NoError(t, err) + require.Equal(t, d, int64(result/time.Second)) + } +} + +func TestMikrotikBaseTagsCollection(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Username: username, + Log: testutil.Logger{}, + IncludeModules: []string{"interface"}, + } + + var acc testutil.Accumulator + require.NoError(t, plugin.Init()) + require.NoError(t, acc.GatherError(plugin.Gather)) + + plugin = &Mikrotik{ + Address: fakeServer.URL, + Log: testutil.Logger{}, + Username: username, + IncludeModules: []string{"interface"}, + } + + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start()) + require.NoError(t, acc.GatherError(plugin.Gather)) + var metric = acc.Metrics[0] + require.Equal(t, "mikrotik", metric.Measurement) +} + +func TestMikrotikCheckCorrectAmountOfMetricsWithOneEmptyAndOneDisabled(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Log: testutil.Logger{}, + Username: username, + IncludeModules: []string{ + "interface", + }, + } + + var acc testutil.Accumulator + + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start()) + require.NoError(t, acc.GatherError(plugin.Gather)) + require.Len(t, acc.Metrics, 2) +} + +func TestMikrotikAllDataPoints(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Username: username, + Log: testutil.Logger{}, + IncludeModules: []string{ + "interface", + "interface_wireguard_peers", + "interface_wireless_registration", + "ip_dhcp_server_lease", + "ip_firewall_connection", + "ip_firewall_filter", + "ip_firewall_nat", + "ip_firewall_mangle", + "ipv6_firewall_connection", + "ipv6_firewall_filter", + "ipv6_firewall_nat", + "ipv6_firewall_mangle", + "system_script", + "system_resourses", + }, + } + + var acc testutil.Accumulator + + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start()) + require.NoError(t, acc.GatherError(plugin.Gather)) + require.Len(t, acc.Metrics, 16) +} + +func TestMikrotikConfigurationErrors(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Username: username, + Log: testutil.Logger{}, + IncludeModules: []string{ + "interfacessss", + }, + } + require.True(t, strings.Contains(plugin.Init().Error(), "mikrotik init ->")) + plugin.IncludeModules = []string{} + require.NoError(t, plugin.Init()) +} + +func TestMikrotikDataIsCorrect(t *testing.T) { + requiredFields := map[string]int64{ + "tx-packet": 56850710, + "tx-error": 0, + "fp-rx-packet": 144047662, + "fp-tx-byte": 0, + "rx-byte": 194632346152, + "rx-error": 0, + "rx-packet": 144047662, + "fp-rx-byte": 194056155504, + "tx-queue-drop": 0, + "rx-drop": 0, + "tx-byte": 15355309685, + "tx-drop": 0, + "fp-tx-packet": 0, + } + + requiredTags := map[string]string{ + ".id": "*1", + "architecture-name": "arm", + "board-name": "hAP", + "cpu": "ARM", + "current-firmware": "7.15.3", + "default-name": "ether1", + "disabled": "false", + "firmware-type": "ipq4000L", + "mac-address": "00:11:22:33:44:55", + "model": "RBD52G-5HacD2HnD", + "name": "ether1", + "platform": "MikroTik", + "version": "7.16 (stable)", + "running": "true", + "serial-number": "123456789", + "type": "ether", + } + + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Log: testutil.Logger{}, + Username: username, + IncludeModules: []string{ + "interface", + }, + } + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start()) + var acc testutil.Accumulator + require.NoError(t, acc.GatherError(plugin.Gather)) + require.Len(t, acc.Metrics, 2) + fields := acc.Metrics[0].Fields + for k := range fields { + require.Equal(t, requiredFields[k], fields[k]) + delete(requiredFields, k) + } + require.Empty(t, requiredFields) + + tags := acc.Metrics[0].Tags + for k := range tags { + // this is a workaround to not filter tags in + // getSystemTags because the issue is only in tests + if _, ok := requiredTags[k]; ok { + require.Equal(t, requiredTags[k], tags[k]) + delete(requiredTags, k) + } + } + require.Empty(t, requiredTags) +} + +func TestMikrotikCommentExclusion(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Address: fakeServer.URL, + Log: testutil.Logger{}, + Username: username, + IncludeModules: []string{"interface", + "interface_wireguard_peers", + "interface_wireless_registration", + "ip_dhcp_server_lease", + "ip_firewall_connection", + "ip_firewall_filter", + "ip_firewall_nat", + "ip_firewall_mangle", + "ipv6_firewall_connection", + "ipv6_firewall_filter", + "ipv6_firewall_nat", + "ipv6_firewall_mangle", + "system_script", + "system_resourses"}, + IgnoreComments: []string{"ignoreThis"}, + } + + var acc testutil.Accumulator + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start()) + require.NoError(t, acc.GatherError(plugin.Gather)) + require.Len(t, acc.Metrics, 15) +} diff --git a/plugins/inputs/mikrotik/modules.go b/plugins/inputs/mikrotik/modules.go new file mode 100644 index 0000000000000..f6f1b64dec3b8 --- /dev/null +++ b/plugins/inputs/mikrotik/modules.go @@ -0,0 +1,31 @@ +package mikrotik + +import ( + "strings" +) + +var modules = map[string]string{ + "interface": "/rest/interface", + "interface_wireguard_peers": "/rest/interface/wireguard/peers", + "interface_wireless_registration": "/rest/interface/wireless/registration-table", + "ip_dhcp_server_lease": "/rest/ip/dhcp-server/lease", + "ip_firewall_connection": "/rest/ip/firewall/connection", + "ip_firewall_filter": "/rest/ip/firewall/filter", + "ip_firewall_mangle": "/rest/ip/firewall/mangle", + "ip_firewall_nat": "/rest/ip/firewall/nat", + "ipv6_firewall_connection": "/rest/ipv6/firewall/connection", + "ipv6_firewall_filter": "/rest/ipv6/firewall/filter", + "ipv6_firewall_mangle": "/rest/ipv6/firewall/mangle", + "ipv6_firewall_nat": "/rest/ipv6/firewall/nat", + "system_resourses": "/rest/system/resource", + "system_script": "/rest/system/script", +} + +func getModuleNames() string { + moduleNames := []string{} + for k := range modules { + moduleNames = append(moduleNames, k) + } + + return strings.Join(moduleNames, ", ") +} diff --git a/plugins/inputs/mikrotik/parsers.go b/plugins/inputs/mikrotik/parsers.go new file mode 100755 index 0000000000000..a3e6b134e24fa --- /dev/null +++ b/plugins/inputs/mikrotik/parsers.go @@ -0,0 +1,103 @@ +package mikrotik + +import ( + "errors" + "fmt" + "regexp" + "slices" + "strconv" + "strings" + "time" +) + +func parse(data common) (points []parsedPoint, err error) { + var errorList []error + + for i := range data { + if !ignoreCommentsFunction(data[i]) { + continue + } + tags := make(map[string]string) + fields := make(map[string]interface{}) + for _, tagName := range tagFields { + if data[i][tagName] != "" { + tags[tagName] = data[i][tagName] + } + } + for _, fieldName := range valueFields { + if data[i][fieldName] != "" { + if slices.Contains(durationParseFieldNames, fieldName) { + fields[fieldName], err = parseUptimeIntoDuration(data[i][fieldName]) + if err != nil { + errorList = append(errorList, err) + } + } else if strings.Contains(data[i][fieldName], ",") { + rxTxValues := strings.Split(data[i][fieldName], ",") + fields[fieldName+"_tx"], err = strconv.ParseInt(rxTxValues[0], 10, 64) + if err != nil { + errorList = append(errorList, err) + } + fields[fieldName+"_rx"], err = strconv.ParseInt(rxTxValues[1], 10, 64) + if err != nil { + errorList = append(errorList, err) + } + } else { + fields[fieldName], err = strconv.ParseInt(data[i][fieldName], 10, 64) + if err != nil { + errorList = append(errorList, err) + } + } + } + } + + points = append(points, parsedPoint{Tags: tags, Fields: fields}) + } + + return points, errors.Join(errorList...) +} + +func parseUptimeIntoDuration(uptime string) (int64, error) { + re := regexp.MustCompile(`^(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$`) + matches := re.FindStringSubmatch(uptime) + if matches == nil { + return 0, nil + } + var duration time.Duration + if matches[1] != "" { + weeks, err := strconv.ParseInt(matches[1], 10, 64) + if err != nil { + return 0, fmt.Errorf("parseUptimeIntoDuration -> %w", err) + } + duration += time.Duration(weeks*7*24) * time.Hour + } + if matches[2] != "" { + days, err := strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return 0, fmt.Errorf("parseUptimeIntoDuration -> %w", err) + } + duration += time.Duration(days*24) * time.Hour + } + if matches[3] != "" { + hours, err := strconv.ParseInt(matches[3], 10, 64) + if err != nil { + return 0, fmt.Errorf("parseUptimeIntoDuration -> %w", err) + } + duration += time.Duration(hours) * time.Hour + } + if matches[4] != "" { + minutes, err := strconv.ParseInt(matches[4], 10, 64) + if err != nil { + return 0, fmt.Errorf("parseUptimeIntoDuration -> %w", err) + } + duration += time.Duration(minutes) * time.Minute + } + if matches[5] != "" { + seconds, err := strconv.ParseInt(matches[5], 10, 64) + if err != nil { + return 0, fmt.Errorf("parseUptimeIntoDuration -> %w", err) + } + duration += time.Duration(seconds) * time.Second + } + + return int64(duration / time.Second), nil +} diff --git a/plugins/inputs/mikrotik/sample.conf b/plugins/inputs/mikrotik/sample.conf new file mode 100644 index 0000000000000..7813a3c9e808c --- /dev/null +++ b/plugins/inputs/mikrotik/sample.conf @@ -0,0 +1,41 @@ +[[inputs.mikrotik]] + ## Mikrotik's address to query. Make sure that REST API is enabled: https://help.mikrotik.com/docs/spaces/ROS/pages/47579162/REST+API + address = "https://192.168.88.1" + + ## User to use. Read access rights will be enough + username = "admin" + password = "password" + + ## Mikrotik's entities whose comments contain this strings will be ignored + # ignore_comments = [ + # "block", + # "doNotGatherMetricsFromThis" + # ] + + ## Modules available to use (default: system_resourses) + # include_modules = [ + # "interface", + # "interface_wireguard_peers", + # "interface_wireless_registration", + # "ip_dhcp_server_lease", + # "ip_firewall_connection", + # "ip_firewall_filter", + # "ip_firewall_nat", + # "ip_firewall_mangle", + # "ipv6_firewall_connection", + # "ipv6_firewall_filter", + # "ipv6_firewall_nat", + # "ipv6_firewall_mangle", + # "system_script", + # "system_resourses" + # ] + + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false + + ## HTTP response timeout + # response_timeout = "5s" \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/interface.json b/plugins/inputs/mikrotik/testData/interface.json new file mode 100644 index 0000000000000..82c4edb1c46a7 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/interface.json @@ -0,0 +1,87 @@ +[ + { + ".id": "*1", + "actual-mtu": "1500", + "default-name": "ether1", + "disabled": "false", + "fp-rx-byte": "194056155504", + "fp-rx-packet": "144047662", + "fp-tx-byte": "0", + "fp-tx-packet": "0", + "l2mtu": "1598", + "last-link-up-time": "2024-10-19 21:54:32", + "link-downs": "0", + "mac-address": "00:11:22:33:44:55", + "max-l2mtu": "9214", + "mtu": "1500", + "name": "ether1", + "running": "true", + "rx-byte": "194632346152", + "rx-drop": "0", + "rx-error": "0", + "rx-packet": "144047662", + "tx-byte": "15355309685", + "tx-drop": "0", + "tx-error": "0", + "tx-packet": "56850710", + "tx-queue-drop": "0", + "type": "ether" + }, + { + ".id": "*2", + "actual-mtu": "1500", + "default-name": "ether2", + "disabled": "false", + "fp-rx-byte": "0", + "fp-rx-packet": "0", + "fp-tx-byte": "0", + "fp-tx-packet": "0", + "l2mtu": "1598", + "link-downs": "0", + "mac-address": "00:11:22:33:44:55", + "max-l2mtu": "9214", + "mtu": "1500", + "name": "ether2", + "running": "false", + "rx-byte": "0", + "rx-drop": "0", + "rx-error": "0", + "rx-packet": "0", + "slave": "true", + "tx-byte": "0", + "tx-drop": "0", + "tx-error": "0", + "tx-packet": "0", + "tx-queue-drop": "0", + "type": "ether" + }, + { + ".id": "*3", + "actual-mtu": "1500", + "default-name": "ether2", + "disabled": "true", + "fp-rx-byte": "0", + "fp-rx-packet": "0", + "fp-tx-byte": "0", + "fp-tx-packet": "0", + "l2mtu": "1598", + "link-downs": "0", + "mac-address": "00:11:22:33:44:55", + "max-l2mtu": "9214", + "mtu": "1500", + "name": "ether2", + "running": "false", + "rx-byte": "0", + "rx-drop": "0", + "rx-error": "0", + "rx-packet": "0", + "slave": "true", + "tx-byte": "0", + "tx-drop": "0", + "tx-error": "0", + "tx-packet": "0", + "tx-queue-drop": "0", + "type": "ether" + }, + {} +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/interface_wireguard_peers.json b/plugins/inputs/mikrotik/testData/interface_wireguard_peers.json new file mode 100644 index 0000000000000..5b3cb7dc0b200 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/interface_wireguard_peers.json @@ -0,0 +1,21 @@ +[ + { + ".id": "*3", + "allowed-address": "0.0.0.0/0", + "client-endpoint": "", + "comment": "VPN", + "current-endpoint-address": "", + "current-endpoint-port": "0", + "disabled": "true", + "dynamic": "false", + "endpoint-address": "vpnserver.com", + "endpoint-port": "51820", + "interface": "VPN", + "name": "VPN", + "preshared-key": "", + "private-key": "", + "public-key": "eLZ/dnifnemopkf,ewl,fewp+EFQ=", + "rx": "0", + "tx": "0" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/interface_wireless_registration.json b/plugins/inputs/mikrotik/testData/interface_wireless_registration.json new file mode 100644 index 0000000000000..ed227975e1815 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/interface_wireless_registration.json @@ -0,0 +1,72 @@ +[ + { + ".id": "*43", + "802.1x-port-enabled": "true", + "ap": "false", + "authentication-type": "wpa2-psk", + "bridge": "false", + "bytes": "5919271457,2570329671", + "distance": "1", + "encryption": "aes-ccm", + "frame-bytes": "5966510100,2591643394", + "frames": "4605377,3017060", + "group-encryption": "aes-ccm", + "hw-frame-bytes": "8050977059,2727095657", + "hw-frames": "5507895,3609841", + "interface": "wlan2", + "last-activity": "1s530ms", + "last-ip": "10.10.10.252", + "mac-address": "00:11:22:33:44:55", + "management-protection": "false", + "p-throughput": "720576", + "packets": "6387817,5106449", + "rx-rate": "585Mbps-80MHz/2S/SGI", + "signal-strength": "-62@6Mbps", + "signal-strength-ch0": "-64", + "signal-strength-ch1": "-70", + "signal-to-noise": "45", + "strength-at-rates": "-62@6Mbps 3s300ms,-62@24Mbps 1s530ms", + "tx-ccq": "89", + "tx-frames-timed-out": "0", + "tx-rate": "866.6Mbps-80MHz/2S/SGI", + "tx-rate-set": "OFDM:6-54 BW:1x-4x SGI:1x-4x HT:0-15 VHTMCS:SS1=0-9,SS2=0-9", + "uptime": "6d4h45m3s", + "wds": "false", + "wmm-enabled": "true" + }, + { + ".id": "*110", + "802.1x-port-enabled": "true", + "ap": "false", + "authentication-type": "wpa2-psk", + "bridge": "false", + "bytes": "87190487,2813226", + "distance": "1", + "encryption": "aes-ccm", + "frame-bytes": "88123037,2773585", + "frames": "41221,11896", + "group-encryption": "aes-ccm", + "hw-frame-bytes": "171999993,3361761", + "hw-frames": "75171,16578", + "interface": "wlan2", + "last-activity": "3s70ms", + "last-ip": "10.10.10.38", + "mac-address": "00:11:22:33:44:55", + "management-protection": "false", + "p-throughput": "236498", + "packets": "83315,13030", + "rx-rate": "6Mbps", + "signal-strength": "-56@6Mbps", + "signal-strength-ch0": "-59", + "signal-strength-ch1": "-59", + "signal-to-noise": "51", + "strength-at-rates": "-56@6Mbps 3s90ms,-57@24Mbps 3s70ms", + "tx-ccq": "92", + "tx-frames-timed-out": "0", + "tx-rate": "780Mbps-80MHz/2S/SGI", + "tx-rate-set": "OFDM:6-54 BW:1x-4x SGI:1x-4x HT:0-15 VHTMCS:SS1=0-9,SS2=0-9", + "uptime": "2h32m50s", + "wds": "false", + "wmm-enabled": "true" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ip_dhcp_server_lease.json b/plugins/inputs/mikrotik/testData/ip_dhcp_server_lease.json new file mode 100644 index 0000000000000..823505d8206a8 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ip_dhcp_server_lease.json @@ -0,0 +1,45 @@ +[ + { + ".id": "*5", + "active-address": "10.10.10.252", + "active-client-id": "1:5c", + "active-mac-address": "00:11:22:33:44:55", + "active-server": "lan_dhcp", + "address": "10.10.10.252", + "address-lists": "", + "blocked": "false", + "client-id": "1:00:11:22:33:44:55", + "comment": "mac mini", + "dhcp-option": "", + "disabled": "false", + "dynamic": "false", + "expires-after": "21m57s", + "host-name": "Mac-mini", + "last-seen": "8m3s", + "mac-address": "00:11:22:33:44:55", + "radius": "false", + "server": "lan_dhcp", + "status": "bound" + }, + { + ".id": "*9", + "active-address": "10.10.10.253", + "active-client-id": "1:d0:39:57:c0:94:ed", + "active-mac-address": "00:11:22:33:44:55", + "active-server": "lan_dhcp", + "address": "10.10.10.253", + "address-lists": "", + "blocked": "false", + "comment": "", + "dhcp-option": "", + "disabled": "false", + "dynamic": "false", + "expires-after": "28m58s", + "host-name": "82xv", + "last-seen": "1m2s", + "mac-address": "00:11:22:33:44:55", + "radius": "false", + "server": "lan_dhcp", + "status": "bound" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ip_firewall_connection.json b/plugins/inputs/mikrotik/testData/ip_firewall_connection.json new file mode 100644 index 0000000000000..2636eae3b58f4 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ip_firewall_connection.json @@ -0,0 +1,60 @@ +[ + { + ".id": "*3937", + "assured": "true", + "confirmed": "true", + "dst-address": "166.166.166.166:443", + "dstnat": "false", + "dying": "false", + "expected": "false", + "fasttrack": "false", + "hw-offload": "false", + "orig-bytes": "22878", + "orig-fasttrack-bytes": "0", + "orig-fasttrack-packets": "0", + "orig-packets": "257", + "orig-rate": "0", + "protocol": "tcp", + "repl-bytes": "29128", + "repl-fasttrack-bytes": "0", + "repl-fasttrack-packets": "0", + "repl-packets": "146", + "repl-rate": "0", + "reply-dst-address": "166.166.166.166:63309", + "reply-src-address": "166.166.166.166:443", + "seen-reply": "true", + "src-address": "10.10.10.252:63309", + "srcnat": "true", + "tcp-state": "established", + "timeout": "23h59m56s" + }, + { + ".id": "*3938", + "assured": "true", + "confirmed": "true", + "dst-address": "166.166.166.166:993", + "dstnat": "false", + "dying": "false", + "expected": "false", + "fasttrack": "false", + "hw-offload": "false", + "orig-bytes": "6385", + "orig-fasttrack-bytes": "0", + "orig-fasttrack-packets": "0", + "orig-packets": "51", + "orig-rate": "0", + "protocol": "tcp", + "repl-bytes": "14997", + "repl-fasttrack-bytes": "0", + "repl-fasttrack-packets": "0", + "repl-packets": "56", + "repl-rate": "0", + "reply-dst-address": "166.166.166.166:63394", + "reply-src-address": "166.166.166.166:993", + "seen-reply": "true", + "src-address": "10.10.10.252:63394", + "srcnat": "true", + "tcp-state": "established", + "timeout": "23h55m12s" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ip_firewall_filter.json b/plugins/inputs/mikrotik/testData/ip_firewall_filter.json new file mode 100644 index 0000000000000..a09aa2d7c2652 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ip_firewall_filter.json @@ -0,0 +1,53 @@ +[ + { + ".id": "*37", + "action": "accept", + "bytes": "0", + "chain": "forward", + "comment": "VPN", + "disabled": "true", + "dynamic": "false", + "invalid": "false", + "out-interface": "VPN", + "packets": "0", + "src-address": "10.10.10.0/24" + }, + { + ".id": "*38", + "action": "accept", + "bytes": "0", + "chain": "forward", + "comment": "VPN", + "connection-state": "established,related", + "disabled": "true", + "dst-address": "10.10.10.0/24", + "dynamic": "false", + "in-interface": "VPN", + "invalid": "false", + "packets": "0" + }, + { + ".id": "*39", + "action": "drop", + "bytes": "0", + "chain": "forward", + "comment": "VPN", + "disabled": "true", + "dynamic": "false", + "in-interface": "VPN", + "invalid": "false", + "packets": "0" + }, + { + ".id": "*3A", + "action": "drop", + "bytes": "0", + "chain": "forward", + "comment": "VPN", + "disabled": "true", + "dynamic": "false", + "invalid": "false", + "packets": "0", + "src-address": "10.10.10.0/24" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ip_firewall_mangle.json b/plugins/inputs/mikrotik/testData/ip_firewall_mangle.json new file mode 100644 index 0000000000000..86cfc30ebeac9 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ip_firewall_mangle.json @@ -0,0 +1,15 @@ +[ + { + ".id": "*1", + "action": "accept", + "bytes": "0", + "chain": "prerouting", + "disabled": "true", + "dynamic": "false", + "invalid": "false", + "log": "false", + "comment": "ignoreThis", + "log-prefix": "", + "packets": "0" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ip_firewall_nat.json b/plugins/inputs/mikrotik/testData/ip_firewall_nat.json new file mode 100644 index 0000000000000..e7bb2f657c101 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ip_firewall_nat.json @@ -0,0 +1,15 @@ +[ + { + ".id": "*4", + "action": "masquerade", + "bytes": "130683465", + "chain": "srcnat", + "disabled": "false", + "dynamic": "false", + "invalid": "false", + "log": "false", + "log-prefix": "", + "out-interface": "ether1", + "packets": "475842" + } +] diff --git a/plugins/inputs/mikrotik/testData/ipv6_firewall_connection.json b/plugins/inputs/mikrotik/testData/ipv6_firewall_connection.json new file mode 100644 index 0000000000000..196a23c675ea8 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ipv6_firewall_connection.json @@ -0,0 +1,18 @@ +[ + { + ".id": "*4", + "assured": "false", + "dst-address": "fe80::5", + "dstnat": "false", + "icmp-code": "0", + "icmp-id": "19943", + "icmp-type": "128", + "protocol": "icmpv6", + "reply-dst-address": "fe80::1", + "reply-src-address": "fe80::2", + "seen-reply": "true", + "src-address": "fe80::3", + "srcnat": "false", + "timeout": "30s" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ipv6_firewall_filter.json b/plugins/inputs/mikrotik/testData/ipv6_firewall_filter.json new file mode 100644 index 0000000000000..37245b3b7f288 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ipv6_firewall_filter.json @@ -0,0 +1,27 @@ +[ + { + ".id": "*1", + "action": "drop", + "bytes": "0", + "chain": "forward", + "disabled": "false", + "dynamic": "false", + "invalid": "false", + "log": "false", + "log-prefix": "", + "packets": "0" + }, + { + ".id": "*2", + "comment": "ignoreThis", + "action": "drop", + "bytes": "7236", + "chain": "input", + "disabled": "false", + "dynamic": "false", + "invalid": "false", + "log": "false", + "log-prefix": "", + "packets": "44" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ipv6_firewall_mangle.json b/plugins/inputs/mikrotik/testData/ipv6_firewall_mangle.json new file mode 100644 index 0000000000000..3cb90ac55aa63 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ipv6_firewall_mangle.json @@ -0,0 +1,14 @@ +[ + { + ".id": "*1", + "action": "accept", + "bytes": "192", + "chain": "prerouting", + "disabled": "false", + "dynamic": "false", + "invalid": "false", + "log": "false", + "log-prefix": "", + "packets": "2" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/ipv6_firewall_nat.json b/plugins/inputs/mikrotik/testData/ipv6_firewall_nat.json new file mode 100644 index 0000000000000..421cb381cf72c --- /dev/null +++ b/plugins/inputs/mikrotik/testData/ipv6_firewall_nat.json @@ -0,0 +1,14 @@ +[ + { + ".id": "*1", + "action": "accept", + "bytes": "0", + "chain": "srcnat", + "disabled": "false", + "dynamic": "false", + "invalid": "false", + "log": "false", + "log-prefix": "", + "packets": "0" + } +] diff --git a/plugins/inputs/mikrotik/testData/system_resourses.json b/plugins/inputs/mikrotik/testData/system_resourses.json new file mode 100644 index 0000000000000..3fc1c5721f6ff --- /dev/null +++ b/plugins/inputs/mikrotik/testData/system_resourses.json @@ -0,0 +1,19 @@ +{ + "architecture-name": "arm", + "board-name": "hAP", + "build-time": "2024-09-20 13:00:27", + "cpu": "ARM", + "cpu-count": "4", + "cpu-frequency": "672", + "cpu-load": "0", + "factory-software": "6.44", + "free-hdd-space": "1495040", + "free-memory": "50106368", + "platform": "MikroTik", + "total-hdd-space": "16777216", + "total-memory": "134217728", + "uptime": "1w1h10m14s", + "version": "7.16 (stable)", + "write-sect-since-reboot": "17390", + "write-sect-total": "45359" +} \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/system_routerboard.json b/plugins/inputs/mikrotik/testData/system_routerboard.json new file mode 100644 index 0000000000000..d7678b70cd60f --- /dev/null +++ b/plugins/inputs/mikrotik/testData/system_routerboard.json @@ -0,0 +1,10 @@ +{ + "board-name": "hAP", + "current-firmware": "7.15.3", + "factory-firmware": "6.44", + "firmware-type": "ipq4000L", + "model": "RBD52G-5HacD2HnD", + "routerboard": "true", + "serial-number": "123456789", + "upgrade-firmware": "7.16" +} \ No newline at end of file diff --git a/plugins/inputs/mikrotik/testData/system_script.json b/plugins/inputs/mikrotik/testData/system_script.json new file mode 100644 index 0000000000000..076fa8c86f0e6 --- /dev/null +++ b/plugins/inputs/mikrotik/testData/system_script.json @@ -0,0 +1,26 @@ +[ + { + ".id": "*1", + ".nextid": "*2", + "dont-require-permissions": "false", + "invalid": "false", + "last-started": "2024-10-11 19:26:05", + "name": "toggle", + "owner": "admin", + "policy": "read,write", + "run-count": "22", + "source": ":put 1" + }, + { + ".id": "*2", + ".nextid": "*3", + "dont-require-permissions": "false", + "invalid": "false", + "last-started": "2024-10-12 11:18:55", + "name": "restart", + "owner": "admin", + "policy": "write", + "run-count": "6", + "source": ":put 2" + } +] \ No newline at end of file diff --git a/plugins/inputs/mikrotik/tools.go b/plugins/inputs/mikrotik/tools.go new file mode 100755 index 0000000000000..7bcb476e83442 --- /dev/null +++ b/plugins/inputs/mikrotik/tools.go @@ -0,0 +1,47 @@ +package mikrotik + +import ( + "encoding/json" + "fmt" + "strings" +) + +var ignoreCommentsFunction func(commonData) bool + +func createPropLists() (metricsPropList string, resourcesPropList string, routerboardPropList string) { + metricsPropList = ".proplist=" + strings.Join(append(tagFields, valueFields...), ",") + resourcesPropList = ".proplist=" + strings.Join(systemResources, ",") + routerboardPropList = ".proplist=" + strings.Join(systemRouterBoard, ",") + return metricsPropList, resourcesPropList, routerboardPropList +} + +func binToCommon(b []byte) (c common, err error) { + errCommon := json.Unmarshal(b, &c) + if errCommon == nil { + return c, nil + } + + cd := commonData{} + err = json.Unmarshal(b, &cd) + if err != nil { + return c, fmt.Errorf("data could not be unmarshalled neither into common nor into commonData structures. %w %w", errCommon, err) + } + c = append(c, cd) + + return c, err +} + +func basicCommentAndDisableFilter(commentsToIgnore []string) func(commonData) bool { + return func(c commonData) bool { + if c["disabled"] == "true" { + return false + } + for _, comment := range commentsToIgnore { + if strings.Contains(c["comment"], comment) { + return false + } + } + + return true + } +} diff --git a/plugins/inputs/mikrotik/types.go b/plugins/inputs/mikrotik/types.go new file mode 100644 index 0000000000000..fe176c617ce50 --- /dev/null +++ b/plugins/inputs/mikrotik/types.go @@ -0,0 +1,111 @@ +package mikrotik + +type common []commonData + +type commonData map[string]string + +var tagFields = []string{ + ".id", + "action", + "chain", + "comment", + "connection-state", + "default-name", + "disabled", + "dst-address", + "dst-port", + "endpoint-address", + "in-interface", + "interface", + "interface-type", + "last-ip", + "mac-address", + "master-interface", + "name", + "out-interface", + "owner", + "protocol", + "running", + "slave", + "src-address", + "src-port", + "ssid", + "status", + "type", +} + +var valueFields = []string{ + "bytes", + "cpu-frequency", + "cpu-load", + "distance", + "fp-rx-byte", + "fp-rx-packet", + "fp-tx-byte", + "fp-tx-packet", + "frame-bytes", + "frames", + "free-hdd-space", + "free-memory", + "hw-frame-bytes", + "hw-frames", + "last-seen", + "link-downs", + "orig-bytes", + "orig-fasttrack-bytes", + "orig-fasttrack-packets", + "orig-packets", + "orig-rate", + "packets", + "repl-bytes", + "repl-fasttrack-bytes", + "repl-fasttrack-packets", + "repl-packets", + "repl-rate", + "run-count", + "rx", + "rx-byte", + "rx-drop", + "rx-error", + "rx-packet", + "total-memory", + "tx", + "tx-byte", + "tx-drop", + "tx-error", + "tx-frames-timed-out", + "tx-packet", + "tx-queue-drop", + "uptime", + "write-sect-since-reboot", + "write-sect-total", +} + +var durationParseFieldNames = []string{ + "last-seen", + "uptime", +} + +var systemResources = []string{ + "architecture-name", + "board-name", + "cpu", + "platform", + "version", +} + +var systemRouterBoard = []string{ + "current-firmware", + "firmware-type", + "model", + "serial-number", +} + +type parsedPoint struct { + Tags map[string]string + Fields map[string]interface{} +} + +type mikrotikEndpoint struct { + name, url string +}