Skip to content

Commit

Permalink
feat(input.mikrotik): Add Mikrotik plugin (#16080)
Browse files Browse the repository at this point in the history
  • Loading branch information
s-r-engineer committed Nov 11, 2024
1 parent b4fdd52 commit cf065e2
Show file tree
Hide file tree
Showing 24 changed files with 1,404 additions and 0 deletions.
5 changes: 5 additions & 0 deletions plugins/inputs/all/mikrotik.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.mikrotik

package all

import _ "github.com/influxdata/telegraf/plugins/inputs/mikrotik" // register plugin
90 changes: 90 additions & 0 deletions plugins/inputs/mikrotik/README.md
Original file line number Diff line number Diff line change
@@ -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 <!-- @/docs/includes/plugin_config.md -->

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
```
224 changes: 224 additions & 0 deletions plugins/inputs/mikrotik/mikrotik.go
Original file line number Diff line number Diff line change
@@ -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)}
})
}
Loading

0 comments on commit cf065e2

Please sign in to comment.