diff --git a/plugins/inputs/all/mikrotik.go b/plugins/inputs/all/mikrotik.go new file mode 100644 index 0000000000000..64a4cdfbee5ea --- /dev/null +++ b/plugins/inputs/all/mikrotik.go @@ -0,0 +1,5 @@ +//go:build !custom || inputs || inputs.kernel + +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..26136d961bded --- /dev/null +++ b/plugins/inputs/mikrotik/README.md @@ -0,0 +1,111 @@ +# Mikrotik Input Plugin + +This plugin gathers Mikrotik's (not only) interface statistics such as bytes, +packets, run count, uptime etc + +## 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 + agent = "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 = [ + "Facebook", + "block", + "doNotGatherMetricsFromThis" + ] + + # Modules available to use. At least one shall be chosen + 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 (default: 5s) + response_timeout = "5s" +``` + +## Metrics + +For each exact module there will be different set of modules and tags based on +the JSON structure that is returned from the exact REST endpoint. You can take +a look at structure `CommonData` in `types.go`. There are mikrotik tag in that +structure marking field as tag or value. Only fields and tags that are +available via each endpoint will be passed into the accumulator. Disabled +entities will not be taken into account. + + +## Example Output + +Using this configuration: + +```toml +[[inputs.mikrotik]] + agent = "https://192.168.88.1" + + username = "admin" + password = "password" + + include_modules = [ + "interface", + "system_resourses" + ] +``` + +When run with: + +```sh +./telegraf --config telegraf.conf --input-filter nginx --test +``` + +Will produce something similar to this: + +```text +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=ether1,Disabled=false,ID=*1,MacAddress=00:11:22:33:44:B6,Name=ether1,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=true,Type=ether,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=204555898022i,FpRxPacket=151821725i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=205163168867i,RxDrop=0i,RxError=0i,RxPacket=151821670i,TxByte=16146845989i,TxDrop=0i,TxError=0i,TxPacket=60029156i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=ether2,Disabled=false,ID=*2,MacAddress=00:11:22:33:44:B7,Name=ether2,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Slave=true,Type=ether,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=ether3,Disabled=false,ID=*3,MacAddress=00:11:22:33:44:B8,Name=ether3,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=true,Slave=true,Type=ether,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=14289795840i,FpRxPacket=15388528i,FpTxByte=24893573990i,FpTxPacket=16958932i,LinkDowns=34i,RxByte=14351349952i,RxDrop=0i,RxError=0i,RxPacket=15388528i,TxByte=25737561612i,TxDrop=0i,TxError=0i,TxPacket=17651724i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=ether4,Disabled=false,ID=*4,MacAddress=00:11:22:33:44:B9,Name=ether4,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Slave=true,Type=ether,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=ether5,Disabled=false,ID=*5,MacAddress=00:11:22:33:44:BA,Name=ether5,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Slave=true,Type=ether,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,Disabled=true,ID=*13,MacAddress=11:22:33:44:55:666,Name=SomethingHidden,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Type=wlan,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=wlan1,Disabled=false,ID=*6,MacAddress=00:11:22:33:44:BB,Name=wlan1,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Slave=true,Type=wlan,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=306495714i,FpRxPacket=3415113i,FpTxByte=70507553i,FpTxPacket=48467i,LinkDowns=69i,RxByte=306495714i,RxDrop=0i,RxError=0i,RxPacket=3415113i,TxByte=7401961434i,TxDrop=0i,TxError=0i,TxPacket=5705917i,TxQueueDrop=82i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,DefaultName=wlan2,Disabled=false,ID=*7,MacAddress=00:11:22:33:44:BC,Name=wlan2,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=true,Slave=true,Type=wlan,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=40649889743i,FpRxPacket=74186211i,FpTxByte=14103365713i,FpTxPacket=14965617i,LinkDowns=4i,RxByte=40649889743i,RxDrop=0i,RxError=0i,RxPacket=74186211i,TxByte=210494811217i,TxDrop=2i,TxError=0i,TxPacket=161243464i,TxQueueDrop=346119i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Comment=VPN,Cpu=ARM,Disabled=true,ID=*18,Name=VPN,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Type=wg,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,Disabled=false,ID=*8,MacAddress=00:11:22:33:44:BB,Name=lan,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=true,Type=bridge,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=15941754904i,FpRxPacket=60288063i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=16088294995i,RxDrop=0i,RxError=0i,RxPacket=60903697i,TxByte=204668863217i,TxDrop=0i,TxError=0i,TxPacket=151995468i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,Disabled=false,ID=*14,MacAddress=00:00:00:00:00:00,Name=lo,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=true,Type=loopback,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=3818476i,RxDrop=0i,RxError=0i,RxPacket=22101i,TxByte=3818476i,TxDrop=0i,TxError=0i,TxPacket=22101i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,Disabled=true,ID=*17,MacAddress=00:11:22:33:44:55,Name=ovpn-out1,Platform=MikroTik,RouterOsVersion=7.16\ (stable),Running=false,Type=ovpn-out,currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER FpRxByte=0i,FpRxPacket=0i,FpTxByte=0i,FpTxPacket=0i,LinkDowns=0i,RxByte=0i,RxDrop=0i,RxError=0i,RxPacket=0i,TxByte=0i,TxDrop=0i,TxError=0i,TxPacket=0i,TxQueueDrop=0i 1730024835000000000 +mikrotik,ArchitectureName=arm,BoardName=hAP\ ac^2,Cpu=ARM,Platform=MikroTik,RouterOsVersion=7.16\ (stable),currentFirmware=7.15.3,firmwareType=ipq4000L,host=82xv,model=ROUTERBOARDMODEL,serialNumber=YOURSERIALNUMBER CPUFrequency=896i,CPULoad=0i,FreeHddSpace=1478656i,FreeMemory=46735360i,WriteSectSinceReboot=20576i,WriteSectTotal=48545i 1730024835000000000 +``` diff --git a/plugins/inputs/mikrotik/mikrotik.go b/plugins/inputs/mikrotik/mikrotik.go new file mode 100644 index 0000000000000..8db3e9e65d439 --- /dev/null +++ b/plugins/inputs/mikrotik/mikrotik.go @@ -0,0 +1,329 @@ +//go:generate ../../../tools/config_includer/generator +//go:generate ../../../tools/readme_config_includer/generator +package mikrotik + +import ( + _ "embed" + "encoding/json" + "errors" + "fmt" + "github.com/influxdata/telegraf/plugins/inputs" + "io" + "net/http" + "reflect" + "strings" + "sync" + "time" + + common_tls "github.com/influxdata/telegraf/plugins/common/tls" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" +) + +const metricName = "mikrotik" + +//go:embed sample.conf +var sampleConfig string + +var durationParseTags []string + +func init() { + inputs.Add("mikrotik", func() telegraf.Input { + return &Mikrotik{} + }) +} + +type Mikrotik struct { + Agent string `toml:"agent"` + IgnoreCert bool `toml:"ignore_cert,omitempty"` + + ResponseTimeout config.Duration `toml:"response_timeout"` + + IgnoreComments []string `toml:"ignore_comments"` + IncludeModules []string `toml:"include_modules"` + + Username string `toml:"username"` + Password config.Secret `toml:"password"` + + Log telegraf.Logger `toml:"-"` + + tags map[string]string + + url []string + + systemResourcesURL string + systemRouterBoardURL string + + acc telegraf.Accumulator + + common_tls.ClientConfig +} + +func (*Mikrotik) SampleConfig() string { + return sampleConfig +} + +func (h *Mikrotik) getClient() (client *http.Client, err error) { + tlsCfg, err := h.ClientConfig.TLSConfig() + if err != nil { + return client, err + } + + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsCfg, + }, + Timeout: time.Duration(h.ResponseTimeout), + } + + return client, err +} + +func (h *Mikrotik) Init() error { + if h.Username == "" { + return errors.New("username must not be empty") + } + + if len(h.IncludeModules) == 0 { + return fmt.Errorf("modules cannot be empty. Correct modules are: %s", getModuleNames()) + } + + proplist := createPropList() + h.systemResourcesURL = h.Agent + "/rest/system/resource" + h.systemRouterBoardURL = h.Agent + "/rest/system/routerboard" + + for _, data := range h.IncludeModules { + if _, ok := modules[data]; !ok { + return fmt.Errorf("module %s does not exist or has a typo. Correct modules are: %s", data, getModuleNames()) + } + + h.url = append(h.url, fmt.Sprintf("%s%s?%s", h.Agent, modules[data], proplist)) + } + + t := reflect.TypeOf(CommonData{}) + durationParseTags = []string{} + for j := 0; j < t.NumField(); j++ { + field := t.Field(j) + mikrotikTag := strings.Split(field.Tag.Get("mikrotik"), ",") + if len(mikrotikTag) != 1 && mikrotikTag[1] == "duration" { + durationParseTags = append(durationParseTags, field.Name) + } + } + return nil +} + +func (h *Mikrotik) Start(_ telegraf.Accumulator) error { + return nil +} + +func (h *Mikrotik) Gather(acc telegraf.Accumulator) error { + var wg sync.WaitGroup + + h.acc = acc + + if len(h.tags) == 0 { + err := h.getSystemTags() + if err != nil { + h.acc.AddError(fmt.Errorf("system error: %w", err)) + return err + } + } + + for _, u := range h.url { + wg.Add(1) + + go func(url string) { + defer wg.Done() + + if err := h.gatherURL(url); err != nil { + h.acc.AddError(fmt.Errorf("[url=%s]: %w", url, err)) + } + }(u) + } + + wg.Wait() + + return nil +} + +func (h *Mikrotik) getSystemTags() (err error) { + resourcesTags, err := h.getSystemResourcesForTags() + if err != nil { + return err + } + + routerBoardTags, err := h.getSystemRouterboardForTags() + + if err != nil { + return err + } + + h.tags = map[string]string{} + + for k, v := range resourcesTags { + h.tags[k] = v + } + + for k, v := range routerBoardTags { + h.tags[k] = v + } + + return err +} + +func (h *Mikrotik) getSystemResourcesForTags() (tags map[string]string, err error) { + request, err := http.NewRequest("GET", h.systemResourcesURL, nil) + if err != nil { + return tags, err + } + + err = h.setRequestAuth(request) + + if err != nil { + return tags, err + } + + binaryData, err := h.queryData(request) + + if err != nil { + return tags, err + } + + system := SystemResourcesForTags{} + err = json.Unmarshal(binaryData, &system) + + if err != nil { + return tags, err + } + + return map[string]string{ + "ArchitectureName": system.ArchitectureName, + "BoardName": system.BoardName, + "Platform": system.Platform, + "Cpu": system.CPU, + "RouterOsVersion": system.Version, + }, nil +} + +func (h *Mikrotik) getSystemRouterboardForTags() (tags map[string]string, err error) { + request, err := http.NewRequest("GET", h.systemRouterBoardURL, nil) + + if err != nil { + return tags, err + } + + err = h.setRequestAuth(request) + + if err != nil { + return tags, err + } + + binaryData, err := h.queryData(request) + + if err != nil { + return tags, err + } + + system := SystemRouterBoardForTags{} + err = json.Unmarshal(binaryData, &system) + + if err != nil { + return tags, err + } + + return map[string]string{ + "currentFirmware": system.CurrentFirmware, + "firmwareType": system.FirmwareType, + "model": system.Model, + "serialNumber": system.SerialNumber, + }, nil +} + +func (h *Mikrotik) Stop() {} + +func (h *Mikrotik) gatherURL(url string) error { + request, err := http.NewRequest("GET", url, nil) + + if err != nil { + return err + } + + if err := h.setRequestAuth(request); err != nil { + return err + } + + binaryData, err := h.queryData(request) + + if err != nil { + return err + } + + timestamp := time.Now() + common, err := binToCommon(binaryData) + + if err != nil { + return err + } + + parsedData, err := parse(common, h.IgnoreComments) + + if err != nil { + h.acc.AddError(err) + } + + for _, point := range parsedData { + for n, v := range h.tags { + point.Tags[n] = v + } + + h.acc.AddFields(metricName, point.Values, point.Tags, timestamp) + } + + return nil +} + +func (h *Mikrotik) setRequestAuth(request *http.Request) error { + if h.Username == "" { + return nil + } + + password, err := h.Password.Get() + if err != nil { + return fmt.Errorf("getting password failed: %w", err) + } + defer password.Destroy() + + request.SetBasicAuth(h.Username, password.String()) + + return nil +} +func (h *Mikrotik) queryData(request *http.Request) (data []byte, err error) { + client, err := h.getClient() + if err != nil { + return data, err + } + + resp, err := client.Do(request) + + if err != nil { + return data, err + } + + defer resp.Body.Close() + defer client.CloseIdleConnections() + + responseHasSuccessCode := false + + if resp.StatusCode == http.StatusOK { + responseHasSuccessCode = true + } + + if !responseHasSuccessCode { + return nil, fmt.Errorf("received status code %d (%s), expected 200", + resp.StatusCode, + http.StatusText(resp.StatusCode)) + } + + return io.ReadAll(resp.Body) +} diff --git a/plugins/inputs/mikrotik/mikrotik_test.go b/plugins/inputs/mikrotik/mikrotik_test.go new file mode 100644 index 0000000000000..4964568077048 --- /dev/null +++ b/plugins/inputs/mikrotik/mikrotik_test.go @@ -0,0 +1,240 @@ +package mikrotik + +import ( + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" +) + +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 { + require.Equal(t, parseUptimeIntoDuration(uptime), result) + } +} + +func TestMikrotikBaseTagsCollection(t *testing.T) { + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + Log: testutil.Logger{}, + IncludeModules: []string{"interface"}, + } + + var acc testutil.Accumulator + require.NoError(t, plugin.Init()) + require.NoError(t, acc.GatherError(plugin.Gather)) + + plugin = &Mikrotik{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + Log: testutil.Logger{}, + IncludeModules: []string{"interface"}, + } + + require.NoError(t, plugin.Init()) + 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{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + Log: testutil.Logger{}, + IncludeModules: []string{ + "interface", + }, + } + + var acc testutil.Accumulator + + require.NoError(t, plugin.Init()) + 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{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + 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, 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{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + Log: testutil.Logger{}, + IncludeModules: []string{ + "interfaces", + }, + } + require.Error(t, plugin.Init()) + plugin.IncludeModules = []string{} + require.Error(t, plugin.Init()) +} + +func TestMikrotikDataIsCorrect(t *testing.T) { + requiredFields := map[string]int64{ + "TxPacket": 56850710, + "TxError": 0, + "FpRxPacket": 144047662, + "FpTxByte": 0, + "RxByte": 194632346152, + "RxError": 0, + "RxPacket": 144047662, + "FpRxByte": 194056155504, + "TxQueueDrop": 0, + "RxDrop": 0, + "TxByte": 15355309685, + "TxDrop": 0, + "FpTxPacket": 0, + } + + requiredTags := map[string]string{ + "Platform": "MikroTik", + "firmwareType": "ipq4000L", + "Name": "ether1", + "serialNumber": "123456789", + "model": "RBD52G-5HacD2HnD", + "ID": "*1", + "Type": "ether", + "MacAddress": "00:11:22:33:44:55", + "BoardName": "hAP", + "Running": "true", + "ArchitectureName": "arm", + "Cpu": "ARM", + "currentFirmware": "7.15.3", + "DefaultName": "ether1", + "RouterOsVersion": "7.16 (stable)", + "Disabled": "false", + } + + fakeServer := httptest.NewServer(http.HandlerFunc(generateHandler())) + defer fakeServer.Close() + plugin := &Mikrotik{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + Log: testutil.Logger{}, + IncludeModules: []string{ + "interface", + }, + } + require.NoError(t, plugin.Init()) + 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], int64(fields[k].(int))) + delete(requiredFields, k) + } + require.Empty(t, requiredFields) + + tags := acc.Metrics[0].Tags + for k := range tags { + 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{ + Agent: fakeServer.URL, + Username: "testOfTheDer", + 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"}, + IgnoreComments: []string{"ignoreThis"}, + } + + var acc testutil.Accumulator + require.NoError(t, plugin.Init()) + 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..eedeb04c92f77 --- /dev/null +++ b/plugins/inputs/mikrotik/modules.go @@ -0,0 +1,34 @@ +package mikrotik + +import ( + "slices" + "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_nat": "/rest/ip/firewall/nat", + "ip_firewall_mangle": "/rest/ip/firewall/mangle", + "ipv6_firewall_connection": "/rest/ipv6/firewall/connection", + "ipv6_firewall_filter": "/rest/ipv6/firewall/filter", + "ipv6_firewall_nat": "/rest/ipv6/firewall/nat", + "ipv6_firewall_mangle": "/rest/ipv6/firewall/mangle", + "system_script": "/rest/system/script", + "system_resourses": "/rest/system/resource", +} + +func getModuleNames() string { + moduleNames := []string{} + for k := range modules { + moduleNames = append(moduleNames, k) + } + + slices.Sort(moduleNames) + + return strings.Join(moduleNames, ", ") +} diff --git a/plugins/inputs/mikrotik/parsers.go b/plugins/inputs/mikrotik/parsers.go new file mode 100644 index 0000000000000..b5bf078133439 --- /dev/null +++ b/plugins/inputs/mikrotik/parsers.go @@ -0,0 +1,124 @@ +package mikrotik + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "time" +) + +type parsedPoint struct { + Tags map[string]string + Values map[string]interface{} +} + +func parse(data Common, ignoreComments []string) (points []parsedPoint, err error) { + var errorList []string + + data = filterCommon(data, basicCommentAndDisableFilter(ignoreComments)) + + for i := range data { + tags := map[string]string{} + values := map[string]interface{}{} + t := reflect.TypeOf(data[i]) + for j := 0; j < t.NumField(); j++ { + field := t.Field(j) + mikrotikTag := strings.Split(field.Tag.Get("mikrotik"), ",")[0] + commonValues := reflect.ValueOf(data[i]) + tags, values, err = dataCreator(field.Name, commonValues.FieldByName(field.Name).String(), mikrotikTag, tags, values) + + if err != nil { + errorList = append(errorList, err.Error()) + } + } + + points = append(points, parsedPoint{Tags: tags, Values: values}) + } + + if len(errorList) != 0 { + err = fmt.Errorf("%s", strings.Join(errorList, " ||| ")) + } + + return points, err +} + +func parseUptimeIntoDuration(uptime string) time.Duration { + re := regexp.MustCompile(`^(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$`) + matches := re.FindStringSubmatch(uptime) + if matches == nil { + return 0 + } + + toInt := func(s string) int { + if s == "" { + return 0 + } + i, err := strconv.Atoi(s) + if err != nil { + return 0 + } + return i + } + + weeks := toInt(matches[1]) + days := toInt(matches[2]) + hours := toInt(matches[3]) + minutes := toInt(matches[4]) + seconds := toInt(matches[5]) + + return time.Duration(weeks*7*24)*time.Hour + + time.Duration(days*24)*time.Hour + + time.Duration(hours)*time.Hour + + time.Duration(minutes)*time.Minute + + time.Duration(seconds)*time.Second +} + +func shallWeParseTime(name string) bool { + for _, tag := range durationParseTags { + if tag == name { + return true + } + } + return false +} + +func dataCreator(name, value, dataType string, tags map[string]string, values map[string]interface{}) (map[string]string, map[string]interface{}, error) { + if value != "" { + switch dataType { + case "tag": + tags[name] = value + case "value": + if shallWeParseTime(name) { + values[name] = parseUptimeIntoDuration(value) + } else if strings.Contains(value, ",") { + rxTxValues := strings.Split(value, ",") + v, err := strconv.Atoi(rxTxValues[0]) + + if err != nil { + return tags, values, err + } + + values[name+"_tx"] = v + v, err = strconv.Atoi(rxTxValues[1]) + + if err != nil { + return tags, values, err + } + + values[name+"_rx"] = v + } else { + v, err := strconv.Atoi(value) + + if err != nil { + return tags, values, err + } + + values[name] = v + } + } + } + + return tags, values, nil +} diff --git a/plugins/inputs/mikrotik/sample.conf b/plugins/inputs/mikrotik/sample.conf new file mode 100644 index 0000000000000..53e8e104f49b2 --- /dev/null +++ b/plugins/inputs/mikrotik/sample.conf @@ -0,0 +1,42 @@ +[[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 + agent = "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 = [ + "Facebook", + "block", + "doNotGatherMetricsFromThis" + ] + + # Modules available to use. At least one shall be chosen + 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 (default: 5s) + 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 100644 index 0000000000000..7cf01d35916af --- /dev/null +++ b/plugins/inputs/mikrotik/tools.go @@ -0,0 +1,68 @@ +package mikrotik + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +func createPropList() string { + var propList []string + var commonData CommonData + t := reflect.TypeOf(commonData) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + jsonTag := field.Tag.Get("json") + if jsonTag != "" { + propList = append(propList, strings.Split(jsonTag, ",")[0]) + } + } + + return ".proplist=%" + strings.Join(propList, ",") +} + +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 + } +} + +func filterCommon(data Common, f func(CommonData) bool) Common { + for i := 0; i < len(data); { + if !f(data[i]) { + data = append(data[:i], data[i+1:]...) + } else { + i++ + } + } + + return data +} diff --git a/plugins/inputs/mikrotik/types.go b/plugins/inputs/mikrotik/types.go new file mode 100644 index 0000000000000..59e5982763bbe --- /dev/null +++ b/plugins/inputs/mikrotik/types.go @@ -0,0 +1,92 @@ +package mikrotik + +type SystemResourcesForTags struct { + ArchitectureName string `json:"architecture-name"` + BoardName string `json:"board-name"` + CPU string `json:"cpu"` + Platform string `json:"platform"` + Version string `json:"version"` +} + +type SystemRouterBoardForTags struct { + CurrentFirmware string `json:"current-firmware"` + FirmwareType string `json:"firmware-type"` + Model string `json:"model"` + SerialNumber string `json:"serial-number"` +} + +type Common []CommonData + +type CommonData struct { + ID string `json:".id,omitempty" mikrotik:"tag"` + DefaultName string `json:"default-name,omitempty" mikrotik:"tag"` + Disabled string `json:"disabled,omitempty" mikrotik:"tag"` + MacAddress string `json:"mac-address,omitempty" mikrotik:"tag"` + Running string `json:"running,omitempty" mikrotik:"tag"` + Type string `json:"type,omitempty" mikrotik:"tag"` + Slave string `json:"slave,omitempty" mikrotik:"tag"` + Comment string `json:"comment,omitempty" mikrotik:"tag"` + Interface string `json:"interface,omitempty" mikrotik:"tag"` + Name string `json:"name,omitempty" mikrotik:"tag"` + InterfaceType string `json:"interface-type,omitempty" mikrotik:"tag"` + MasterInterface string `json:"master-interface,omitempty" mikrotik:"tag"` + Ssid string `json:"ssid,omitempty" mikrotik:"tag"` + LastIP string `json:"last-ip,omitempty" mikrotik:"tag"` + Status string `json:"status,omitempty" mikrotik:"tag"` + SrcAddress string `json:"src-address,omitempty" mikrotik:"tag"` + DstAddress string `json:"dst-address,omitempty" mikrotik:"tag"` + Protocol string `json:"protocol,omitempty" mikrotik:"tag"` + Action string `json:"action,omitempty" mikrotik:"tag"` + Chain string `json:"chain,omitempty" mikrotik:"tag"` + OutInterface string `json:"out-interface,omitempty" mikrotik:"tag"` + Owner string `json:"owner,omitempty" mikrotik:"tag"` + ConnectionState string `json:"connection-state,omitempty" mikrotik:"tag"` + InInterface string `json:"in-interface,omitempty" mikrotik:"tag"` + DstPort string `json:"dst-port,omitempty" mikrotik:"tag"` + SrcPort string `json:"src-port,omitempty" mikrotik:"tag"` + EndpointAddress string `json:"endpoint-address,omitempty" mikrotik:"tag"` + + RxByte string `json:"rx-byte,omitempty" mikrotik:"value"` + LinkDowns string `json:"link-downs,omitempty" mikrotik:"value"` + CPUFrequency string `json:"cpu-frequency,omitempty" mikrotik:"value"` + RxDrop string `json:"rx-drop,omitempty" mikrotik:"value"` + RxError string `json:"rx-error,omitempty" mikrotik:"value"` + RxPacket string `json:"rx-packet,omitempty" mikrotik:"value"` + TxByte string `json:"tx-byte,omitempty" mikrotik:"value"` + TxDrop string `json:"tx-drop,omitempty" mikrotik:"value"` + TxError string `json:"tx-error,omitempty" mikrotik:"value"` + TxPacket string `json:"tx-packet,omitempty" mikrotik:"value"` + TxQueueDrop string `json:"tx-queue-drop,omitempty" mikrotik:"value"` + FpRxByte string `json:"fp-rx-byte,omitempty" mikrotik:"value"` + FpRxPacket string `json:"fp-rx-packet,omitempty" mikrotik:"value"` + FpTxByte string `json:"fp-tx-byte,omitempty" mikrotik:"value"` + FpTxPacket string `json:"fp-tx-packet,omitempty" mikrotik:"value"` + Rx string `json:"rx,omitempty" mikrotik:"value"` + Tx string `json:"tx,omitempty" mikrotik:"value"` + Distance string `json:"distance,omitempty" mikrotik:"value"` + Bytes string `json:"bytes,omitempty" mikrotik:"value"` + FrameBytes string `json:"frame-bytes,omitempty" mikrotik:"value"` + Frames string `json:"frames,omitempty" mikrotik:"value"` + HwFrameBytes string `json:"hw-frame-bytes,omitempty" mikrotik:"value"` + HwFrames string `json:"hw-frames,omitempty" mikrotik:"value"` + Packets string `json:"packets,omitempty" mikrotik:"value"` + TxFramesTimedOut string `json:"tx-frames-timed-out,omitempty" mikrotik:"value"` + Uptime string `json:"uptime,omitempty" mikrotik:"value,duration"` + LastSeen string `json:"last-seen,omitempty" mikrotik:"value,duration"` + OrigBytes string `json:"orig-bytes,omitempty" mikrotik:"value"` + OrigFasttrackBytes string `json:"orig-fasttrack-bytes,omitempty" mikrotik:"value"` + OrigFasttrackPackets string `json:"orig-fasttrack-packets,omitempty" mikrotik:"value"` + OrigPackets string `json:"orig-packets,omitempty" mikrotik:"value"` + OrigRate string `json:"orig-rate,omitempty" mikrotik:"value"` + ReplBytes string `json:"repl-bytes,omitempty" mikrotik:"value"` + ReplFasttrackBytes string `json:"repl-fasttrack-bytes,omitempty" mikrotik:"value"` + ReplFasttrackPackets string `json:"repl-fasttrack-packets,omitempty" mikrotik:"value"` + ReplPackets string `json:"repl-packets,omitempty" mikrotik:"value"` + ReplRate string `json:"repl-rate,omitempty" mikrotik:"value"` + RunCount string `json:"run-count,omitempty" mikrotik:"value"` + CPULoad string `json:"cpu-load,omitempty" mikrotik:"value"` + FreeHddSpace string `json:"free-hdd-space,omitempty" mikrotik:"value"` + FreeMemory string `json:"free-memory,omitempty" mikrotik:"value"` + WriteSectSinceReboot string `json:"write-sect-since-reboot,omitempty" mikrotik:"value"` + WriteSectTotal string `json:"write-sect-total,omitempty" mikrotik:"value"` +}