├── .gitignore ├── Makefile ├── README.md ├── bandwidth_types.go ├── cmd ├── bandwidth │ └── bandwidth.go └── bare │ └── bare.go ├── interface.go ├── mikrotik.sublime-project ├── routerboard.go └── snmp ├── interface_type.go ├── interfaces_bandwidth.go ├── routerboard.go ├── routerboard_methods.go ├── snmp.go └── snmp_debug.go /.gitignore: -------------------------------------------------------------------------------- 1 | /bandwidth 2 | /custom/* 3 | /bin/* 4 | mikrotik.sublime-workspace 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | ## Defines 3 | SRC = cmd/bandwidth/*.go 4 | GOPKG = github.com/ErebusBat/mikrotik/cmd/bandwidth 5 | EXEFILE = bandwidth 6 | 7 | ## Input and Output Variables 8 | GIT_VER := $(shell git describe --always --dirty) 9 | DATE_STAMP := $(shell date +%Y%m%d-%H%M%S) 10 | 11 | # Use equals (as opposed to colon-equals) so that they are re-evaluated 12 | # for each target, each time 13 | BUILDTAG = $(DATE_STAMP)-$(GIT_VER) 14 | PLATARCH = $(GOOS)-$(GOARCH) 15 | OUTDIR = bin/$(BUILDTAG)/$(PLATARCH) 16 | GOLDFLAGS = -ldflags '-X main.BUILD_TAG $(BUILDTAG)' 17 | GOMAKE = GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o "$(OUTDIR)/$(EXEFILE)$(EXEEXT)" $(GOLDFLAGS) $(SRC) 18 | ZIPFILE = $(OUTDIR)/../$(PLATARCH)-$(BUILDTAG).zip 19 | ZIPCMD = zip -j "$(ZIPFILE)" "$(OUTDIR)/$(EXEFILE)$(EXEEXT)" 20 | 21 | ################################################################################ 22 | # Generic Targets 23 | ################################################################################ 24 | 25 | default: build 26 | 27 | # .PHONY: default mac 28 | # .PHONY: build 29 | 30 | build: mac 31 | 32 | clean: 33 | rm -dfr bin/* 34 | go clean -i -x 35 | 36 | uninstall: 37 | go clean -i -x $(GOPKG) 38 | 39 | all: mac win linux 40 | 41 | deps: 42 | go get -u -f $(GOPKG) 43 | 44 | install: deps goinst 45 | goinst: 46 | go install $(GOLDFLAGS) $(GOPKG) 47 | 48 | ################################################################################ 49 | # Mac OSX Targets 50 | ################################################################################ 51 | mac: mac64 52 | mac64: GOOS=darwin 53 | mac64: GOARCH=amd64 54 | mac64: 55 | mkdir -p $(OUTDIR) 56 | $(GOMAKE) 57 | $(ZIPCMD) 58 | 59 | ################################################################################ 60 | # Windows Targets 61 | ################################################################################ 62 | win: win64 win32 63 | win64: GOOS=windows 64 | win64: GOARCH=amd64 65 | win64: EXEEXT=.exe 66 | win64: 67 | mkdir -p $(OUTDIR) 68 | $(GOMAKE) 69 | $(ZIPCMD) 70 | 71 | win32: GOOS=windows 72 | win32: GOARCH=386 73 | win32: EXEEXT=.exe 74 | win32: 75 | mkdir -p $(OUTDIR) 76 | $(GOMAKE) 77 | $(ZIPCMD) 78 | 79 | 80 | ################################################################################ 81 | # Linux Targets 82 | ################################################################################ 83 | linux: linux64 linux32 84 | linux64: GOOS=linux 85 | linux64: GOARCH=amd64 86 | linux64: 87 | mkdir -p $(OUTDIR) 88 | $(GOMAKE) 89 | $(ZIPCMD) 90 | 91 | linux32: GOOS=linux 92 | linux32: GOARCH=386 93 | linux32: 94 | mkdir -p $(OUTDIR) 95 | $(GOMAKE) 96 | $(ZIPCMD) 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mikrotik 2 | Golang Bandwidth monitoring utility for Mikrotik RouterOS Devices 3 | 4 | This library reads [RouterOS](http://routerboard.com/) devices (primarily Mikrotik routerboads). 5 | Currently only SNMP and the functions to support bandwidth monitoring are supported. 6 | 7 | This library is built using [gosnmp](https://github.com/alouca/gosnmp). However it should be abstracted enough that if 8 | one were so inclined you could implement a [raw API](http://wiki.mikrotik.com/wiki/Manual:API) client. 9 | 10 | Currently there only exists the `bandwidth` tool (See [bandwidth.go](https://github.com/ErebusBat/mikrotik/blob/master/cmd/bandwidth.go)) which consumes the library and reports interface statistics at a given interval. 11 | 12 | ## Installation ## 13 | 14 | ```sh 15 | go get -u -v github.com/ErebusBat/mikrotik/ 16 | 17 | # Optional... compile the include tool / sample 18 | go build cmd/*.go 19 | ``` 20 | 21 | ## Download ## 22 | 23 | You can download pre-built binaries from the [v0.1 Release](https://github.com/ErebusBat/mikrotik/releases/tag/v0.1) page. 24 | 25 | ## Usage ## 26 | 27 | The tool tries to have intelligent defaults, so if your SNMP community is `public` and the interface name you want to monitor is the standard `ether1` then you just need to specify a host: 28 | 29 | ``` 30 | $ ./bandwidth -h 192.168.0.1 31 | 32 | # Help 33 | $ ./bandwidth --help 34 | Usage of ./bandwidth: 35 | -c="public": SNMP Community Name 36 | -h="127.0.0.7": Mikrotik IP 37 | -i="ether1": Mikrotik Interface Name 38 | -list=false: Lists all known interfaces and exits 39 | -s=1s: Sample Interval 40 | ``` 41 | 42 | ## Example ## 43 | 44 | If you want to use the library in your own tools then you can do something like 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "log" 51 | "time" 52 | 53 | "github.com/ErebusBat/mikrotik/snmp" 54 | ) 55 | 56 | func main() { 57 | rb := snmp.Connect("10.0.1.250", "public") 58 | iface, err := rb.FindInterfaceByName("UPSTREAM") 59 | if err != nil { 60 | log.Fatalf("ERROR: %v", err) 61 | } 62 | sampleChannel := iface.MonitorBandwidth(time.Second * 5) 63 | for sample := range sampleChannel { 64 | log.Printf("%s tx/rx %s/%s", 65 | iface.FullName(), 66 | sample.TX().BitsString(), 67 | sample.RX().BitsString(), 68 | ) 69 | } 70 | } 71 | 72 | ``` 73 | 74 | ## Contributing ## 75 | 76 | Contributions welcome! Please fork the repository and open a pull request 77 | with your changes. 78 | 79 | ## License ## 80 | 81 | This is free software, licensed under the Apache License, Version 2.0. 82 | -------------------------------------------------------------------------------- /bandwidth_types.go: -------------------------------------------------------------------------------- 1 | package mikrotik 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | // . "github.com/ErebusBat/mikrotik/core" 9 | ) 10 | 11 | // Single point in time sample of the interface state 12 | type InterfaceBandwidthSample struct { 13 | Interface RbInterface 14 | IsRx bool 15 | Date time.Time 16 | ByteCount int64 17 | } 18 | 19 | // Calculates the delta between two samples 20 | // The older sample should be the receiver and 21 | // the newer sample should be the parameter 22 | func (first InterfaceBandwidthSample) Delta(second InterfaceBandwidthSample) (sample InterfaceBandwidthSampleDelta) { 23 | if !first.Interface.Equals(second.Interface) || first.IsRx != second.IsRx { 24 | return sample 25 | } 26 | sample.Interface = first.Interface 27 | sample.IsRx = first.IsRx 28 | sample.Duration = second.Date.Sub(first.Date) 29 | sample.Delta = second.ByteCount - first.ByteCount 30 | sample.Bits = sample.Delta * 8 31 | 32 | return sample 33 | } 34 | 35 | // InterfaceBandwidthSampleDelta is a comparison of a rx OR tx sample over time 36 | type InterfaceBandwidthSampleDelta struct { 37 | Interface RbInterface 38 | IsRx bool 39 | Duration time.Duration 40 | Delta int64 41 | Bits int64 42 | } 43 | 44 | // Returns a humanized string representing the bits, i.e. 123.4 Mbps 45 | func (d InterfaceBandwidthSampleDelta) BitsString() string { 46 | var bits float64 = float64(d.Bits) / d.Duration.Seconds() 47 | template := "%.1f" 48 | 49 | if bits >= 1000000000 { 50 | bits = bits / float64(1000000000) 51 | template += " Gbps" 52 | } else if bits >= 1000000 { 53 | bits = bits / float64(1000000) 54 | template += " Mbps" 55 | } else if bits >= 1000 { 56 | bits = bits / float64(1000) 57 | template += " Kbps" 58 | } 59 | return fmt.Sprintf(template, bits) 60 | } 61 | 62 | // Returns a string representing the sample, i.e. RB450G-ether1 tx/rx 541.8 Kbps/595.3 Kbps 63 | func (d InterfaceBandwidthSampleDelta) String() string { 64 | var rxtx string = "tx" 65 | if d.IsRx { 66 | rxtx = "rx" 67 | } 68 | log.Printf("%#v\n", d) 69 | return fmt.Sprintf("%s-%s %10s", 70 | d.Interface.FullName(), 71 | rxtx, 72 | d.BitsString(), 73 | ) 74 | } 75 | 76 | // InterfaceRxTxSample is a 'complete' (both rx and tx) picture of interface bandwidth 77 | type InterfaceRxTxSample [2]InterfaceBandwidthSampleDelta 78 | 79 | // Returns the RX Sample 80 | func (s InterfaceRxTxSample) RX() InterfaceBandwidthSampleDelta { 81 | if !s[0].IsRx { 82 | panic(fmt.Sprintf("Non RX interface in RX Slot\n:%#v", s)) 83 | } 84 | return s[0] 85 | } 86 | 87 | // Returns the TX Sample 88 | func (s InterfaceRxTxSample) TX() InterfaceBandwidthSampleDelta { 89 | if s[1].IsRx { 90 | panic("Non TX interface in TX Slot") 91 | } 92 | return s[1] 93 | } 94 | -------------------------------------------------------------------------------- /cmd/bandwidth/bandwidth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/ErebusBat/mikrotik" 11 | "github.com/ErebusBat/mikrotik/snmp" 12 | ) 13 | 14 | // Custom type/consts to make our action routing code easier to read 15 | type cliAction int 16 | 17 | const ( 18 | unknown cliAction = iota 19 | monitorBandwidth 20 | printInterfaces 21 | printVersion 22 | ) 23 | 24 | // Set during build with 25 | // -ldflags "-X main.BUILD_TAG $(git describe --always --dirty)" 26 | var BUILD_TAG string = "?" 27 | 28 | // Configuration Struct 29 | type AppConfig struct { 30 | Routerboard mikrotik.Routerboard 31 | SampleInterval time.Duration 32 | InterfaceName string 33 | 34 | action cliAction 35 | dumpInterfaces bool 36 | } 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | // ENTRY POINT 40 | //////////////////////////////////////////////////////////////////////////////// 41 | 42 | func main() { 43 | log.Printf("ErebusBat/mikrotik starting %s-%s-%s\n", runtime.GOOS, runtime.GOARCH, BUILD_TAG) 44 | app := parseConfig() 45 | 46 | switch app.action { 47 | case monitorBandwidth: 48 | app.actionMonitorBandwidth() 49 | case printInterfaces: 50 | app.actionPrintKnownInterfaces() 51 | case printVersion: 52 | // Version already 53 | default: 54 | log.Fatalf("Unknown action %#v?!?! ", app.action) 55 | } 56 | } 57 | 58 | //////////////////////////////////////////////////////////////////////////////// 59 | // ACTIONS 60 | //////////////////////////////////////////////////////////////////////////////// 61 | 62 | // Prints all known interfaces to the console 63 | func (cfg *AppConfig) actionPrintKnownInterfaces() { 64 | log.Println("Interfaces") 65 | ifaces, err := cfg.Routerboard.GetInterfaces() 66 | if err != nil { 67 | log.Fatalf("ERROR: %v", err) 68 | } 69 | header := fmt.Sprintf("%5s %-23s %s", 70 | "idx", 71 | " Full Oid", 72 | "Name") 73 | log.Println(header) 74 | log.Println("--------------------------------------------------------------------------------") 75 | for _, iface := range ifaces { 76 | log.Println(" ", iface) 77 | } 78 | } 79 | 80 | // action: monitors the bandwidth of given interface and prints to console 81 | func (cfg *AppConfig) actionMonitorBandwidth() { 82 | iface := cfg.mustFindInterface() 83 | var sampleInterval time.Duration = cfg.SampleInterval 84 | log.Printf("Sampling bandwidth every %s\n", sampleInterval) 85 | bandwidthSample := iface.MonitorBandwidth(sampleInterval) 86 | for sample := range bandwidthSample { 87 | if &sample == nil { 88 | return 89 | } 90 | // log.Printf("%#v\n\n", sample) 91 | log.Printf("%s tx/rx %s/%s", 92 | iface.FullName(), 93 | sample.TX().BitsString(), 94 | sample.RX().BitsString(), 95 | ) 96 | } 97 | } 98 | 99 | //////////////////////////////////////////////////////////////////////////////// 100 | // HELPERS 101 | //////////////////////////////////////////////////////////////////////////////// 102 | 103 | // Reads the configuration, inits objects, and returns the AppConfig 104 | func parseConfig() *AppConfig { 105 | var ( 106 | host, community string 107 | ) 108 | cfg := new(AppConfig) 109 | 110 | var isVersion bool = false 111 | 112 | cfg.action = monitorBandwidth 113 | flag.StringVar(&host, "h", "127.0.0.7", "Mikrotik IP") 114 | flag.StringVar(&community, "c", "public", "SNMP Community Name") 115 | 116 | flag.DurationVar(&cfg.SampleInterval, "s", time.Second, "Sample Interval") 117 | flag.StringVar(&cfg.InterfaceName, "i", "ether1", "Mikrotik Interface Name") 118 | 119 | // Non operational flags 120 | flag.BoolVar(&cfg.dumpInterfaces, "list", false, "Lists all known interfaces and exits") 121 | flag.BoolVar(&isVersion, "v", false, "Report version and exit") 122 | flag.Parse() 123 | 124 | if isVersion { 125 | cfg.action = printVersion 126 | return cfg 127 | } 128 | 129 | cfg.Routerboard = snmp.Connect(host, community) 130 | 131 | // Print RB banner (so it is on all output) 132 | banner, err := cfg.Routerboard.GetSystemBanner() 133 | if err != nil { 134 | log.Fatalf("ERROR: %v", err) 135 | } 136 | log.Println("Connected to", banner) 137 | 138 | // Now check non-operational status 139 | 140 | if cfg.dumpInterfaces { 141 | cfg.action = printInterfaces 142 | } 143 | 144 | return cfg 145 | } 146 | 147 | // helper: returns system name or logs fatal error 148 | func (cfg *AppConfig) mustGetSystemName() string { 149 | sysName, err := cfg.Routerboard.GetSystemName() 150 | if err != nil { 151 | log.Fatalf("ERROR: %v", err) 152 | } 153 | return sysName 154 | } 155 | 156 | // helper: returns interface or logs fatal error 157 | func (cfg *AppConfig) mustFindInterface() mikrotik.RbInterface { 158 | // Call sysName first so we exit on that 159 | sysName := cfg.mustGetSystemName() 160 | iface, err := cfg.Routerboard.FindInterfaceByName(cfg.InterfaceName) 161 | if err != nil { 162 | log.Fatalf("ERROR: %v (maybe try --list)", err) 163 | } 164 | log.Printf("%s Found %s Interface at %d\n", 165 | sysName, 166 | iface.Name(), 167 | iface.Index(), 168 | ) 169 | return iface 170 | } 171 | -------------------------------------------------------------------------------- /cmd/bare/bare.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/ErebusBat/mikrotik/snmp" 8 | ) 9 | 10 | func main() { 11 | rb := snmp.Connect("10.0.1.250", "public") 12 | iface, err := rb.FindInterfaceByName("UPSTREAM") 13 | if err != nil { 14 | log.Fatalf("ERROR: %v", err) 15 | } 16 | sampleChannel := iface.MonitorBandwidth(time.Second * 5) 17 | for sample := range sampleChannel { 18 | log.Printf("%s tx/rx %s/%s", 19 | iface.FullName(), 20 | sample.TX().BitsString(), 21 | sample.RX().BitsString(), 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package mikrotik 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type RbInterface interface { 9 | fmt.Stringer 10 | Bandwidther 11 | Routerboarder 12 | 13 | Index() int 14 | Name() string 15 | FullName() string 16 | Equals(rhs RbInterface) bool 17 | } 18 | 19 | type Bandwidther interface { 20 | GetBytesRX() (ifBytes int64, err error) 21 | GetBytesTX() (ifBytes int64, err error) 22 | SampleBytes(isRx bool) (sample InterfaceBandwidthSample, err error) 23 | MonitorBandwidth(sampleSize time.Duration) (resultsChan chan InterfaceRxTxSample) 24 | } 25 | -------------------------------------------------------------------------------- /mikrotik.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "./", 6 | "folder_exclude_patterns": [ 7 | "bin" 8 | ], 9 | "file_exclude_patterns": [ 10 | "*.sublime-*" 11 | ,".gitignore" 12 | ,"bandwidth" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /routerboard.go: -------------------------------------------------------------------------------- 1 | package mikrotik 2 | 3 | import ( 4 | "time" 5 | 6 | // Include package reference so that go get will pulldependencies 7 | _ "github.com/alouca/gosnmp" 8 | ) 9 | 10 | type Routerboarder interface { 11 | Routerboard() Routerboard 12 | } 13 | 14 | type Routerboard interface { 15 | Routerboarder 16 | 17 | // Does initial setup in order to communicate to RB 18 | Initialize() 19 | 20 | // Removes any cached information 21 | FlushCaches() 22 | 23 | // Returns a time.Duration representing how long the RB has been running 24 | GetSystemUptime() (uptime time.Duration, err error) 25 | 26 | // Retuns the system description, i.e. RouterOS RB450G 27 | GetSystemDescription() (sysDesc string, err error) 28 | 29 | // Retuns the system name 30 | GetSystemName() (sysDesc string, err error) 31 | 32 | // Retuns a string comprised of the system name, description, and uptime 33 | GetSystemBanner() (banner string, err error) 34 | 35 | // Interface Methods 36 | GetInterfaces() (ifaces []RbInterface, err error) 37 | FindInterfaceByName(ifName string) (iface RbInterface, err error) 38 | } 39 | -------------------------------------------------------------------------------- /snmp/interface_type.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | . "github.com/ErebusBat/mikrotik" 9 | ) 10 | 11 | type MtInterface struct { 12 | _index int 13 | _name string 14 | _rb SnmpRouterboard 15 | _snmpOid string 16 | } 17 | 18 | func (iff MtInterface) Index() int { 19 | return iff._index 20 | } 21 | func (iff MtInterface) Name() string { 22 | return iff._name 23 | } 24 | func (iff MtInterface) Routerboard() Routerboard { 25 | return iff._rb 26 | } 27 | func (iff MtInterface) SnmpOid() string { 28 | return iff._snmpOid 29 | } 30 | 31 | func (first MtInterface) Equals(second RbInterface) bool { 32 | return first.Name() == second.Name() 33 | } 34 | func (iff MtInterface) String() string { 35 | idx := fmt.Sprintf(".%d", iff.Index) 36 | return fmt.Sprintf("%3s %-23s %s", idx, iff._snmpOid, iff._name) 37 | } 38 | 39 | // Returns the System + Interface Name 40 | func (iff MtInterface) FullName() string { 41 | sysName, _ := iff.Routerboard().GetSystemName() 42 | return fmt.Sprintf("%s-%s", 43 | sysName, 44 | iff._name, 45 | ) 46 | } 47 | 48 | // Takes a base OID (like bytes TX/RX) and returns that contatinated with 49 | // the interfaces index 50 | func (iff MtInterface) GetOidForInterface(oidBase string) string { 51 | oidIdx := strconv.Itoa(iff._index) 52 | if !strings.HasSuffix(oidBase, ".") { 53 | oidBase += "." 54 | } 55 | return oidBase + oidIdx 56 | } 57 | 58 | // Returns the total bytes received on the interface 59 | func (iff MtInterface) GetBytesRX() (ifBytes int64, err error) { 60 | pdu, err := iff._rb.SnmpGetPDU(iff.GetOidForInterface(SnmpOidIfBytesRX)) 61 | if err != nil { 62 | return -1, err 63 | } 64 | return pdu.Value.(int64), nil 65 | } 66 | 67 | // Returns the total bytes transmitted on the interface 68 | func (iff MtInterface) GetBytesTX() (ifBytes int64, err error) { 69 | pdu, err := iff._rb.SnmpGetPDU(iff.GetOidForInterface(SnmpOidIfBytesTX)) 70 | if err != nil { 71 | return -1, err 72 | } 73 | return pdu.Value.(int64), nil 74 | } 75 | -------------------------------------------------------------------------------- /snmp/interfaces_bandwidth.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "time" 5 | 6 | . "github.com/ErebusBat/mikrotik" 7 | ) 8 | 9 | // Samples bandwidth for the given duration and returns the delta. 10 | func (iface MtInterface) SampleBandwidth(isRx bool, sampleSize time.Duration) (InterfaceBandwidthSampleDelta, error) { 11 | var err error 12 | 13 | // Slice to store samples in 14 | samples := make([]InterfaceBandwidthSample, 2) 15 | for x := 0; x < 2; x++ { 16 | samples[x], err = iface.SampleBytes(isRx) 17 | if err != nil { 18 | return InterfaceBandwidthSampleDelta{}, err 19 | } 20 | 21 | // Don't sleep on the last iteration 22 | if x < 1 { 23 | time.Sleep(sampleSize) 24 | } 25 | } 26 | 27 | delta := samples[0].Delta(samples[1]) 28 | return delta, nil 29 | } 30 | 31 | // Monitors both tx and rx bandwidth at the given period, post results to the returned channel 32 | func (iface MtInterface) MonitorBandwidth(sampleSize time.Duration) (resultsChan chan InterfaceRxTxSample) { 33 | rxSample := make(chan InterfaceBandwidthSampleDelta) 34 | txSample := make(chan InterfaceBandwidthSampleDelta) 35 | 36 | clock := time.NewTicker(sampleSize) 37 | rxClock := make(chan time.Time) // We use Time so one could use a Ticker if need be 38 | txClock := make(chan time.Time) // We use Time so one could use a Ticker if need be 39 | resultsChan = make(chan InterfaceRxTxSample) 40 | 41 | // Start the monitor functions 42 | go iface.MonitorBandwidthRxTx(true, rxClock, rxSample) 43 | go iface.MonitorBandwidthRxTx(false, txClock, txSample) 44 | 45 | // Clock Loop 46 | go func() { 47 | // We do it this way (as opposed to using the clock.C directly) so that they 48 | // do not block on each other 49 | for tickTime := range clock.C { 50 | // Post to the rx and tx clocks as fast as possible 51 | go func() { rxClock <- tickTime }() 52 | go func() { txClock <- tickTime }() 53 | } 54 | }() 55 | 56 | // Loop forevar! 57 | go func() { 58 | for { 59 | var samples InterfaceRxTxSample 60 | samples[0] = <-rxSample 61 | samples[1] = <-txSample 62 | resultsChan <- samples 63 | } 64 | }() 65 | 66 | // Return channel to caller so they can do something useful with it. 67 | return resultsChan 68 | } 69 | 70 | // Monitors either the RX or the TX bandwidth, posting deltas to the given chanel 71 | func (iface MtInterface) MonitorBandwidthRxTx(isRx bool, freqClock chan time.Time, results chan InterfaceBandwidthSampleDelta) { 72 | samples := make([]InterfaceBandwidthSample, 2) 73 | // next_idx := 0 74 | sampleCount := 0 75 | // for _ = range freqClock { 76 | for { 77 | sample, err := iface.SampleBytes(isRx) 78 | if err != nil { 79 | results <- InterfaceBandwidthSampleDelta{Delta: -1} 80 | } 81 | 82 | // Find our storage index 83 | this_idx := sampleCount % 2 84 | last_idx := 1 - this_idx // Toogle 0/1 85 | samples[this_idx] = sample 86 | sampleCount += 1 87 | 88 | // If we have enough samples then calculate the delta 89 | // and post it to the channel 90 | if sampleCount >= 2 { 91 | prevSample := samples[last_idx] 92 | delta := prevSample.Delta(sample) 93 | results <- delta 94 | } 95 | 96 | // Block/Check that the channel is still open, if not then exit 97 | if _, stillRunning := <-freqClock; !stillRunning { 98 | break 99 | } 100 | } 101 | } 102 | 103 | // Samples Interface for bytes RX or bytes TX depending on flag 104 | func (iff MtInterface) SampleBytes(isRx bool) (sample InterfaceBandwidthSample, err error) { 105 | // log.Printf("SampleBytes %t %#v\n", isRx, iff.FullName()) 106 | sample.Interface = iff 107 | sample.IsRx = isRx 108 | var bytes int64 109 | if isRx { 110 | bytes, err = iff.GetBytesRX() 111 | } else { 112 | bytes, err = iff.GetBytesTX() 113 | } 114 | // Capture the date as close as possible to return 115 | sample.Date = time.Now().Round(time.Second) 116 | 117 | // Now do err checking 118 | if err != nil { 119 | return InterfaceBandwidthSample{}, err 120 | } 121 | sample.ByteCount = bytes 122 | 123 | // log.Printf(" Got: %#v\n", sample) 124 | return sample, nil 125 | } 126 | -------------------------------------------------------------------------------- /snmp/routerboard.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/alouca/gosnmp" 8 | 9 | . "github.com/ErebusBat/mikrotik" 10 | ) 11 | 12 | type SnmpRouterboard interface { 13 | Routerboard 14 | 15 | GetOidStringValCached(oid string) (val string, err error) 16 | SnmpGetPDU(oid string) (gosnmp.SnmpPDU, error) 17 | SnmpGetPDUList(oid string) ([]gosnmp.SnmpPDU, error) 18 | } 19 | 20 | // http://wiki.mikrotik.com/wiki/Munin_Monitoring#mikrotikifrate 21 | type MikrotikSnmp struct { 22 | SnmpRouterboard 23 | 24 | Host string 25 | Community string 26 | 27 | // Caches 28 | cacheInterfaces []RbInterface 29 | cacheStrings map[string]string 30 | } 31 | 32 | func (rb *MikrotikSnmp) Routerboard() Routerboard { 33 | return rb 34 | } 35 | 36 | // Setups up object to be ready to works (caches, etc) 37 | func (rb *MikrotikSnmp) Initialize() { 38 | rb.FlushCaches() 39 | } 40 | 41 | // Removes any cached information 42 | func (rb *MikrotikSnmp) FlushCaches() { 43 | rb.cacheInterfaces = make([]RbInterface, 0, 5) 44 | rb.cacheStrings = make(map[string]string, 0) 45 | } 46 | 47 | // Returns a time.Duration representing how long the RB has been running 48 | func (rb *MikrotikSnmp) GetSystemUptime() (uptime time.Duration, err error) { 49 | pdu, err := rb.SnmpGetPDU(SnmpOidUptime) 50 | if err != nil { 51 | return time.Duration(0), err 52 | } 53 | upSeconds := pdu.Value.(int) / 100 54 | upSecsParseString := fmt.Sprintf("%ds", upSeconds) 55 | uptime, err = time.ParseDuration(upSecsParseString) 56 | if err != nil { 57 | return time.Duration(0), err 58 | } 59 | return uptime, nil 60 | } 61 | 62 | // Retuns the system description, i.e. RouterOS RB450G 63 | func (rb *MikrotikSnmp) GetSystemDescription() (sysDesc string, err error) { 64 | return rb.GetOidStringValCached(SnmpOidDescription) 65 | } 66 | 67 | // Retuns the system name 68 | func (rb *MikrotikSnmp) GetSystemName() (sysDesc string, err error) { 69 | return rb.GetOidStringValCached(SnmpOidName) 70 | } 71 | 72 | // Retuns a string comprised of the system name, description, and uptime 73 | func (rb *MikrotikSnmp) GetSystemBanner() (banner string, err error) { 74 | 75 | sysName, err := rb.GetSystemName() 76 | if err != nil { 77 | return "", err 78 | } 79 | 80 | sysDesc, err := rb.GetSystemDescription() 81 | if err != nil { 82 | return "", err 83 | } 84 | 85 | sysUptime, err := rb.GetSystemUptime() 86 | if err != nil { 87 | return "", err 88 | } 89 | sysUpDays := 0 90 | if sysUptime.Hours() >= 24 { 91 | sysUpDays = int(sysUptime.Hours()/float64(24) + 0.5) 92 | } 93 | 94 | return fmt.Sprintf("%s %s (Uptime ~%d days: %s)", sysName, sysDesc, sysUpDays, sysUptime), nil 95 | } 96 | -------------------------------------------------------------------------------- /snmp/routerboard_methods.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | . "github.com/ErebusBat/mikrotik" 9 | ) 10 | 11 | // Returns an array of the known interfaces 12 | func (rb *MikrotikSnmp) GetInterfaces() (ifaces []RbInterface, err error) { 13 | // Check caches first 14 | if len(rb.cacheInterfaces) > 0 { 15 | // log.Println("Returning cached interfaces") 16 | return rb.cacheInterfaces, nil 17 | } 18 | 19 | snmpList, err := rb.SnmpGetPDUList(SnmpOidInterfaces) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | ifaces = make([]RbInterface, len(snmpList)) 25 | for x, snmpIface := range snmpList { 26 | oidParts := strings.Split(snmpIface.Name, ".") 27 | // oidIndex, err := 0, error(nil) 28 | oidIndex, err := strconv.Atoi(oidParts[len(oidParts)-1]) 29 | if err != nil { 30 | return nil, err 31 | } 32 | iface := MtInterface{ 33 | _index: oidIndex, 34 | _name: snmpIface.Value.(string), 35 | _rb: rb, 36 | _snmpOid: snmpIface.Name, 37 | } 38 | // log.Printf("Found interface: %s\n", iface) 39 | ifaces[x] = iface 40 | } 41 | rb.cacheInterfaces = ifaces 42 | return ifaces, nil 43 | } 44 | 45 | // Returns the given RbInterface, searching by name (exact match) 46 | func (rb *MikrotikSnmp) FindInterfaceByName(ifName string) (iface RbInterface, err error) { 47 | if ifaces, err := rb.GetInterfaces(); err == nil { 48 | for _, iface := range ifaces { 49 | if iface.Name() == ifName { 50 | return iface, nil 51 | } 52 | } 53 | return nil, fmt.Errorf("Interface %s not found", ifName) 54 | } 55 | return nil, err 56 | } 57 | -------------------------------------------------------------------------------- /snmp/snmp.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "github.com/alouca/gosnmp" 5 | ) 6 | 7 | const ( 8 | SnmpOidDescription = ".1.3.6.1.2.1.1.1.0" 9 | SnmpOidUptime = ".1.3.6.1.2.1.1.3.0" 10 | SnmpOidName = ".1.3.6.1.2.1.1.5.0" 11 | SnmpOidInterfaces = ".1.3.6.1.2.1.2.2.1.2" 12 | SnmpOidIfBytesRX = ".1.3.6.1.2.1.31.1.1.1.6." 13 | SnmpOidIfBytesTX = ".1.3.6.1.2.1.31.1.1.1.10." 14 | ) 15 | 16 | var ( 17 | zeroValPdu = gosnmp.SnmpPDU{} 18 | ) 19 | 20 | // Sets up a Snmp Routerboard Connections 21 | func Connect(host, community string) SnmpRouterboard { 22 | rb := &MikrotikSnmp{ 23 | Host: host, 24 | Community: community, 25 | } 26 | rb.Initialize() 27 | return rb 28 | } 29 | 30 | // Looks up the given OID value, casts it to a string and caches it. Returns 31 | // cached value early if it exists 32 | func (rb *MikrotikSnmp) GetOidStringValCached(oid string) (val string, err error) { 33 | if cacheVal, foundInCache := rb.cacheStrings[oid]; foundInCache { 34 | return cacheVal, nil 35 | } 36 | 37 | pdu, err := rb.SnmpGetPDU(oid) 38 | if err != nil { 39 | return "", err 40 | } 41 | 42 | // Cast val, cache it and return it 43 | val = pdu.Value.(string) 44 | rb.cacheStrings[oid] = val 45 | return val, nil 46 | } 47 | 48 | func (rb *MikrotikSnmp) SnmpGetPDU(oid string) (gosnmp.SnmpPDU, error) { 49 | s, err := gosnmp.NewGoSNMP(rb.Host, rb.Community, gosnmp.Version2c, 5) 50 | if err != nil { 51 | return zeroValPdu, err 52 | } 53 | resp, err := s.Get(oid) 54 | if err != nil { 55 | return zeroValPdu, err 56 | } 57 | 58 | if len(resp.Variables) > 0 { 59 | return resp.Variables[0], nil 60 | } 61 | return zeroValPdu, nil 62 | } 63 | 64 | func (rb *MikrotikSnmp) SnmpGetPDUList(oid string) ([]gosnmp.SnmpPDU, error) { 65 | s, err := gosnmp.NewGoSNMP(rb.Host, rb.Community, gosnmp.Version2c, 5) 66 | if err != nil { 67 | return nil, err 68 | } 69 | resp, err := s.Walk(oid) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return resp, nil 75 | } 76 | -------------------------------------------------------------------------------- /snmp/snmp_debug.go: -------------------------------------------------------------------------------- 1 | package snmp 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/alouca/gosnmp" 7 | ) 8 | 9 | func (rb *MikrotikSnmp) DumpOID(walk bool, oid string) { 10 | s, err := gosnmp.NewGoSNMP(rb.Host, rb.Community, gosnmp.Version2c, 5) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | if walk { 16 | resp, err := s.Walk(oid) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | dumpSnmpPDU(resp) 21 | } else { 22 | resp, err := s.Get(oid) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | dumpSnmpPacket(resp) 27 | } 28 | } 29 | 30 | func dumpSnmpPacket(packet *gosnmp.SnmpPacket) { 31 | for _, v := range packet.Variables { 32 | log.Printf("Response: n=%s : v=%v : t=%s \n", 33 | v.Name, 34 | v.Value, 35 | v.Type.String(), 36 | ) 37 | } 38 | } 39 | func dumpSnmpPDU(slice []gosnmp.SnmpPDU) { 40 | // log.Printf("Response: %#v\n\n", slice) 41 | for idx, val := range slice { 42 | log.Printf("[%d] %#v\n", idx, val) 43 | } 44 | } 45 | --------------------------------------------------------------------------------