17 Commits
v0.1 ... 0.0.7

Author SHA1 Message Date
ad9b3625e1 fix anno
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-26 18:28:12 +01:00
56aec29c76 add cluster switch to config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-26 14:39:08 +01:00
85124d028d use 64bit counter
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-25 16:10:53 +01:00
4ed32f8314 fix struct
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 15:24:56 +01:00
4222e19573 skip diff value
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 15:22:29 +01:00
11e63155bc diff default value
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 15:18:29 +01:00
353a3780c6 anno
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 15:00:40 +01:00
f407198c5b fix config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 14:35:41 +01:00
cf4c3d9ed0 ci script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-01-25 14:31:06 +01:00
1c19117170 diff values 2024-01-25 14:22:12 +01:00
0f89fcf325 fix dockerfile 2024-01-25 12:45:49 +01:00
1ce208d38b modified readme 2024-01-25 12:40:25 +01:00
5db2f64a54 modified readme 2024-01-25 12:39:35 +01:00
5b77a6a059 my own modifications, part 1 2024-01-25 12:35:27 +01:00
b68e929c57 add incomplete ci script 2024-01-23 16:15:26 +01:00
89089f429c add Dockerfile 2024-01-23 16:12:50 +01:00
5021794990 adding desk UPS to the mix along with its external temperature probe 2019-11-20 08:36:50 -05:00
25 changed files with 618 additions and 531 deletions

View File

@ -1,61 +0,0 @@
name: Build and Test
on:
push:
branches:
- master
pull_request:
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Lint Go Code
run: |
export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
go get -u golang.org/x/lint/golint
make lint
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Run Unit tests
run: |
export PATH=$PATH:$(go env GOPATH)/bin
make test-coverage
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Build Everything
run: |
export PATH=$PATH:$(go env GOPATH)/bin
make build

View File

@ -1,25 +0,0 @@
name: Release
on:
create:
tags:
- v*
jobs:
release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Validates GO releaser config
uses: docker://goreleaser/goreleaser:latest
with:
args: check
- name: Create release on GitHub
uses: docker://goreleaser/goreleaser:latest
with:
args: release
env:
GITHUB_TOKEN: ${{secrets.GORELEASER_GITHUB_TOKEN}}

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
build
.DS_Store
snmp-mqtt
src/smq/smq

View File

@ -1,13 +0,0 @@
env:
- GO111MODULE=on
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- arm
- amd64

28
.woodpecker.yml Normal file
View File

@ -0,0 +1,28 @@
steps:
build:
image: plugins/kaniko
settings:
repo: gitea.hottis.de/wn/snmp-mqtt
registry:
from_secret: container_registry
tags: latest,${CI_COMMIT_SHA},${CI_COMMIT_TAG}
username:
from_secret: container_registry_username
password:
from_secret: container_registry_password
dockerfile: Dockerfile
when:
- event: [push, tag]
deploy:
image: portainer/kubectl-shell:latest
secrets:
- source: kube_config
target: KUBE_CONFIG_CONTENT
commands:
- export IMAGE_TAG=$CI_COMMIT_TAG
- printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- ./deployment/deploy.sh
when:
- event: tag

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM golang:1.21-alpine as builder
RUN mkdir -p /go/src
COPY ./src /go/src
WORKDIR /go/src/smq
RUN go build -a -installsuffix nocgo -o smq snmp-mqtt.go
FROM scratch
ENV SNMP_MQTT_CONF ""
COPY --from=builder /go/src/smq ./
ENTRYPOINT ["./smq"]

View File

@ -1,45 +0,0 @@
PROJECT_NAME := "snmp-mqtt"
PKG := "github.com/dchote/$(PROJECT_NAME)"
PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
.PHONY: all dep lint vet test test-coverage build clean
all: build
dep: ## Get the dependencies
@echo Installing dependencies
@go mod download
lint: ## Lint Golang files
@golint -set_exit_status ${PKG_LIST}
vet: ## Run go vet
@go vet ${PKG_LIST}
test: ## Run unittests
@go test -short ${PKG_LIST}
test-coverage: ## Run tests with coverage
@go test -short -coverprofile cover.out -covermode=atomic ${PKG_LIST}
@cat cover.out >> coverage.txt
build: dep ## Build the binary file
@echo Building native binary
@go build -i -o build/snmp-mqtt $(PKG)
linux: build
@echo Building Linux binary
@env CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-linkmode external -extldflags -static" -o build/snmp-mqtt
raspi: build
@echo Building Rasperry Pi Linux binary
@env GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build -o build/snmp-mqtt
clean: ## Remove previous build
@rm -f $(PROJECT_NAME)/build
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View File

@ -1,41 +1,64 @@
# snmp-mqtt
This project is directly derived from https://github.com/dchote/snmp-mqtt
A simple go app that reads SNMP values and publishes it to the specified MQTT endpoint at the specified interval.
Please download the precompiled binary from the releases page: https://github.com/dchote/snmp-mqtt/releases
In constrast to the original version, all configuration must be provided via an environment variable containing
a JSON string.
The MQTT configuration is part of this JSON string.
The topic has been moved to the MQTT configuration and there is now only one topic for all endpoints and variables.
The published message on MQTT looks like this:
```
Usage: snmp-mqtt [options]
Options:
--endpoints_map=<endpoints_map> SNMP Endpoints Map File [default: ./endpoints.json]
--server=<server> MQTT server host/IP [default: 127.0.0.1]
--port=<port> MQTT server port [default: 1883]
--topic=<topic> MQTT topic prefix [default: snmp]
--clientid=<clientid> MQTT client identifier [default: snmp]
--interval=<interval> Poll interval (seconds) [default: 5]
-h, --help Show this screen.
-v, --version Show version.
{"device":"172.16.3.1","label":"router","variables":{"lan-in":{"label":"lan-in","variable":".1.3.6.1.2.1.31.1.1.1.6.2","value":"979673705579"},"lan-out":{"label":"lan-out","variable":".1.3.6.1.2.1.31.1.1.1.10.2","value":"1813410276168"},"wan-in":{"label":"wan-in","variable":".1.3.6.1.2.1.31.1.1.1.6.4","value":"83591215399"},"wan-out":{"label":"wan-out","variable":".1.3.6.1.2.1.31.1.1.1.10.4","value":"83741895468"}}}
```
An example endpoints.json file:
```
export SNMP_MQTT_CONF=$(cat config.json)
./smq
```
An example config.json file:
```
{
"mqtt": {
"broker": "mqtt://172.23.1.102:1883",
"tlsEnable": "false",
"topic": "snmp"
},
"interval": 10,
"snmpEndpoints": [
{
"endpoint": "172.18.0.1",
"endpoint": "172.16.3.1",
"label": "router",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.4",
"topic": "router/bytesIn"
"label": "wan-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.4",
"topic": "router/bytesOut"
"label": "wan-out",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.2",
"label": "lan-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.2",
"label": "lan-out",
"diff": "true"
}
]
}
]
]
}
```
```

View File

@ -1,65 +0,0 @@
package config
import (
"encoding/json"
"os"
"strconv"
)
// OIDTopicObject maps OIDs to MQTT topics
type OIDTopicObject struct {
OID string `json:"oid"`
Topic string `json:"topic"`
}
// SNMPEndpointObject is the SNMP Endpoint definition
type SNMPEndpointObject struct {
Endpoint string `json:"endpoint"`
Community string `json:"community"`
OIDTopics []OIDTopicObject `json:"oidTopics"`
}
// SNMPMapObject basic map of endpoints
type SNMPMapObject struct {
SNMPEndpoints []SNMPEndpointObject `json:"snmpEndpoints"`
}
var (
// SNMPMap is the loaded JSON configuration
SNMPMap *SNMPMapObject
// Server is the MQTT server address
Server string
// Port is the MQTT server listen port
Port int
// ClientID is how the name of the client
ClientID string
// Interval is the poll interval in seconds
Interval int
)
// ConnectionString returns the MQTT connection string
func ConnectionString() string {
return "tcp://" + Server + ":" + strconv.Itoa(Port)
}
// LoadMap loads the file in to the struct
func LoadMap(file string) error {
configFile, err := os.Open(file)
defer configFile.Close()
if err != nil {
return err
}
jsonParser := json.NewDecoder(configFile)
err = jsonParser.Decode(&SNMPMap)
if err != nil {
return err
}
return nil
}

44
deployment/config.json Normal file
View File

@ -0,0 +1,44 @@
{
"mqtt": {
"broker": "mqtt://emqx01-anonymous-cluster-internal.broker.svc.cluster.local:1883",
"tlsEnable": "false",
"topic": "snmp"
},
"interval": 10,
"snmpEndpoints": [
{
"endpoint": "172.16.3.1",
"label": "router",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.5",
"label": "wan-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.5",
"label": "wan-out",
"diff": "true"
}
]
},
{
"endpoint": "172.23.1.2",
"label": "switch-cluster",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.1",
"label": "uplink-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.1",
"label": "uplink-out",
"diff": "true"
}
]
}
]
}

View File

@ -0,0 +1,27 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: snmp-mqtt
namespace: homea
labels:
app: snmp-nmqtt
annotations:
secret.reloader.stakater.com/reload: snmp-mqtt-conf
spec:
replicas: 1
selector:
matchLabels:
app: snmp-nmqtt
template:
metadata:
labels:
app: snmp-nmqtt
spec:
containers:
- name: snmp-nmqtt
image: %IMAGE%
envFrom:
- configMapRef:
name: snmp-mqtt-conf

34
deployment/deploy.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
if [ "$IMAGE_TAG" == "" ]; then
echo "Make sure IMAGE_TAG is set"
exit 1
fi
IMAGE_NAME=gitea.hottis.de/wn/snmp-mqtt
NAMESPACE=homea
DEPLOYMENT_DIR=$PWD/deployment
CONFIG_FILE=config.json
pushd $DEPLOYMENT_DIR > /dev/null
kubectl create namespace $NAMESPACE \
--dry-run=client \
-o yaml | \
kubectl -f - apply
kubectl create configmap snmp-mqtt-conf \
--from-literal=SNMP_MQTT_CONF="`cat $CONFIG_FILE`" \
--dry-run=client \
-o yaml \
--save-config | \
kubectl apply -f - -n $NAMESPACE
cat $DEPLOYMENT_DIR/deploy-yml.tmpl | \
sed -e 's,%IMAGE%,'$IMAGE_NAME':'$IMAGE_TAG','g | \
kubectl apply -f - -n $NAMESPACE
popd > /dev/null

4
deployment/pushconfig.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
kubectl create configmap snmp-mqtt-conf --from-literal=SNMP_MQTT_CONF="`cat config.json`" --dry-run=client -o yaml --save-config | kubectl apply -f - -n homea

View File

@ -1,92 +0,0 @@
{
"snmpEndpoints": [
{
"endpoint": "172.18.0.1",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.4",
"topic": "network/router/interfaces/wan/bytesIn"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.4",
"topic": "network/router/interfaces/wan/bytesOut"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.2",
"topic": "network/router/interfaces/lan/bytesIn"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.2",
"topic": "network/router/interfaces/lan/bytesOut"
}
]
},
{
"endpoint": "172.18.0.50",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.12.2.3.1.1.2.1",
"topic": "power/rackPDU/outputCurrentX10"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.12.1.16.0",
"topic": "power/rackPDU/watts"
}
]
},
{
"endpoint": "172.18.0.51",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.1.3.2.1.0",
"topic": "power/rackUPS/lineVoltage"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.4.2.4.0",
"topic": "power/rackUPS/outputCurrent"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.1.0",
"topic": "power/rackUPS/batteryCapacity"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.3.0",
"topic": "power/rackUPS/batteryRuntime"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.2.0",
"topic": "power/rackUPS/batteryTemperature"
}
]
},
{
"endpoint": "172.18.0.52",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.1.3.2.1.0",
"topic": "power/printerUPS/lineVoltage"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.4.2.4.0",
"topic": "power/printerUPS/outputCurrent"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.1.0",
"topic": "power/printerUPS/batteryCapacity"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.3.0",
"topic": "power/printerUPS/batteryRuntime"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.2.0",
"topic": "power/printerUPS/batteryTemperature"
}
]
}
]
}

10
go.mod
View File

@ -1,10 +0,0 @@
module github.com/dchote/snmp-mqtt
go 1.13
require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/soniah/gosnmp v1.22.0
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
)

20
go.sum
View File

@ -1,20 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/soniah/gosnmp v1.22.0 h1:jVJi8+OGvR+JHIaZKMmnyNP0akJd2vEgNatybwhZvxg=
github.com/soniah/gosnmp v1.22.0/go.mod h1:DuEpAS0az51+DyVBQwITDsoq4++e3LTNckp2GoasF2I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -1,84 +0,0 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/dchote/snmp-mqtt/config"
"github.com/dchote/snmp-mqtt/snmp"
"github.com/docopt/docopt-go"
)
var exitChan = make(chan int)
// VERSION beause...
const VERSION = "0.0.1"
func cliArguments() {
usage := `
Usage: snmp-mqtt [options]
Options:
--endpoints_map=<endpoints_map> SNMP Endpoints Map File [default: ./endpoints.json]
--server=<server> MQTT server host/IP [default: 127.0.0.1]
--port=<port> MQTT server port [default: 1883]
--clientid=<clientid> MQTT client identifier [default: snmp]
--interval=<interval> Poll interval (seconds) [default: 5]
-h, --help Show this screen.
-v, --version Show version.
`
args, _ := docopt.ParseArgs(usage, os.Args[1:], VERSION)
mapFile, _ := args.String("--endpoints_map")
err := config.LoadMap(mapFile)
if err != nil {
log.Println(err)
log.Fatal("error opening " + mapFile)
}
config.Server, _ = args.String("--server")
config.Port, _ = args.Int("--port")
config.ClientID, _ = args.String("--clientid")
config.Interval, _ = args.Int("--interval")
log.Printf("server: %s, port: %d, client identifier: %s, poll interval: %d", config.Server, config.Port, config.ClientID, config.Interval)
}
// sigChannelListen basic handlers for inbound signals
func sigChannelListen() {
signalChan := make(chan os.Signal, 1)
code := 0
signal.Notify(signalChan, os.Interrupt)
signal.Notify(signalChan, os.Kill)
signal.Notify(signalChan, syscall.SIGTERM)
select {
case sig := <-signalChan:
log.Printf("Received signal %s. shutting down", sig)
case code = <-exitChan:
switch code {
case 0:
log.Println("Shutting down")
default:
log.Println("*Shutting down")
}
}
os.Exit(code)
}
func main() {
cliArguments()
// catch signals
go sigChannelListen()
// run sensor poll loop
snmp.Init()
os.Exit(0)
}

View File

@ -1,98 +0,0 @@
package snmp
import (
"fmt"
"log"
"strings"
"sync"
"time"
"github.com/dchote/snmp-mqtt/config"
"github.com/eclipse/paho.mqtt.golang"
"github.com/soniah/gosnmp"
)
var ()
// Init contains the generic read/publish loop
func Init() {
opts := mqtt.NewClientOptions().AddBroker(config.ConnectionString()).SetClientID(config.ClientID)
opts.SetKeepAlive(2 * time.Second)
opts.SetPingTimeout(1 * time.Second)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
var wg sync.WaitGroup
for {
wg.Add(1)
go func() {
defer wg.Done()
for _, endpoint := range config.SNMPMap.SNMPEndpoints {
log.Println("Polling endpoint " + endpoint.Endpoint)
snmp := gosnmp.GoSNMP{}
snmp.Target = endpoint.Endpoint
snmp.Port = 161
snmp.Version = gosnmp.Version2c
snmp.Community = endpoint.Community
snmp.Timeout = time.Duration(5 * time.Second)
err := snmp.Connect()
if err != nil {
log.Fatal("SNMP Connect error\n")
}
oids := []string{}
for _, oidTopic := range endpoint.OIDTopics {
oids = append(oids, oidTopic.OID)
}
result, err := snmp.Get(oids)
if err != nil {
log.Printf("error in Get: %s", err)
} else {
for _, variable := range result.Variables {
for _, oidTopic := range endpoint.OIDTopics {
if strings.Compare(oidTopic.OID, variable.Name) == 0 {
convertedValue := ""
switch variable.Type {
case gosnmp.OctetString:
convertedValue = string(variable.Value.([]byte))
default:
convertedValue = fmt.Sprintf("%d", gosnmp.ToBigInt(variable.Value))
}
log.Printf("%s = %s", oidTopic.Topic, convertedValue)
token := client.Publish(oidTopic.Topic, 0, false, convertedValue)
token.Wait()
if token.Error() != nil {
log.Fatal(token.Error())
}
}
}
}
}
snmp.Conn.Close()
}
}()
time.Sleep(time.Duration(config.Interval) * time.Second)
}
wg.Wait()
client.Disconnect(250)
time.Sleep(1 * time.Second)
}

37
src/smq/config-test.json Normal file
View File

@ -0,0 +1,37 @@
{
"mqtt": {
"broker": "mqtt://172.23.1.102:1883",
"tlsEnable": "false",
"topic": "snmp"
},
"interval": 10,
"snmpEndpoints": [
{
"endpoint": "172.16.3.1",
"label": "router",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.4",
"label": "wan-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.4",
"label": "wan-out",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.2",
"label": "lan-in",
"diff": "true"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.2",
"label": "lan-out",
"diff": "true"
}
]
}
]
}

47
src/smq/config/config.go Normal file
View File

@ -0,0 +1,47 @@
package config
import (
"encoding/json"
"os"
"log"
)
type OIDTopicObject struct {
OID string `json:"oid"`
Label string `json:"label"`
Diff string `json:"diff"`
}
// SNMPEndpointObject is the SNMP Endpoint definition
type SNMPEndpointObject struct {
Endpoint string `json:"endpoint"`
Label string `json:"label"`
Community string `json:"community"`
OIDTopics []OIDTopicObject `json:"oidTopics"`
}
type MQTTConfigObject struct {
Broker string `json:"broker"`
Username string `json:"username"`
Password string `json:"password"`
TlsEnable string `json:"tlsEnable"`
Topic string `json:"topic"`
}
type ConfigObject struct {
Mqtt MQTTConfigObject `json:"mqtt"`
Interval int `json:"interval"`
SNMPEndpoints []SNMPEndpointObject `json:"snmpEndpoints"`
}
var Config ConfigObject
func LoadConfiguration() {
cfg := os.Getenv("SNMP_MQTT_CONF")
log.Printf("cfg: %s", cfg)
err := json.Unmarshal([]byte(cfg), &Config)
if err != nil {
log.Fatalf("Unable to parse configuration: %v", err)
}
}

15
src/smq/go.mod Normal file
View File

@ -0,0 +1,15 @@
module smq
go 1.21.3
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/google/uuid v1.6.0
github.com/gosnmp/gosnmp v1.37.0
)
require (
github.com/gorilla/websocket v1.5.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.1.0 // indirect
)

20
src/smq/go.sum Normal file
View File

@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosnmp/gosnmp v1.37.0 h1:/Tf8D3b9wrnNuf/SfbvO+44mPrjVphBhRtcGg22V07Y=
github.com/gosnmp/gosnmp v1.37.0/go.mod h1:GDH9vNqpsD7f2HvZhKs5dlqSEcAS6s6Qp099oZRCR+M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

113
src/smq/mqtt/mqtt.go Normal file
View File

@ -0,0 +1,113 @@
package mqtt
import "log"
import "fmt"
import MQTT "github.com/eclipse/paho.mqtt.golang"
import "github.com/google/uuid"
import "crypto/tls"
import "smq/config"
// import "smq/counter"
type Message struct {
Topic string
Payload []byte
}
var OutputChannel chan Message = make(chan Message, 100)
var mqttClient MQTT.Client
func onConnectionLost(client MQTT.Client, error error) {
log.Printf("Connection lost, error %s", error)
}
func onReconnecting(client MQTT.Client, clientOpts *MQTT.ClientOptions) {
log.Println("Oops, connection lost, already reconnecting ...")
}
func onConnect(client MQTT.Client) {
log.Println("Connected")
}
func outputDispatcher(client MQTT.Client) {
for {
select {
case message := <- OutputChannel:
log.Printf("Message arrived in outputDispatcher, topic: %s, payload: %s\n", message.Topic, message.Payload)
if token := client.Publish(message.Topic, 0, false, message.Payload); token.Wait() && token.Error() != nil {
log.Printf("Unable to publish, error %s", token.Error())
}
log.Println("Successfully published")
}
}
}
func Publish(msg []byte) {
message := Message {
Topic: config.Config.Mqtt.Topic,
Payload: msg,
}
select {
case OutputChannel <- message:
{}
default:
log.Printf("Channel full, message %s lost")
}
}
func Start() {
broker := config.Config.Mqtt.Broker
if broker == "" {
log.Fatal("No broker given")
}
prefix := "SMQ"
uuid := uuid.New()
clientId := fmt.Sprintf("%s-%s", prefix, uuid)
opts := MQTT.NewClientOptions().
AddBroker(broker).
SetClientID(clientId).
SetConnectionLostHandler(onConnectionLost).
SetOnConnectHandler(onConnect).
SetReconnectingHandler(onReconnecting).
SetConnectRetry(true)
username := config.Config.Mqtt.Username
if username != "" {
opts.SetUsername(username)
}
password := config.Config.Mqtt.Password
if password != "" {
opts.SetPassword(password)
}
enableTls := config.Config.Mqtt.TlsEnable
if enableTls == "true" {
//log.Println("Enabling TLS connection")
tlsConfig := &tls.Config {
InsecureSkipVerify: true,
}
opts.SetTLSConfig(tlsConfig)
}
log.Println("Broker connecting")
mqttClient = MQTT.NewClient(opts)
if token := mqttClient.Connect(); token.Wait() && token.Error() != nil {
log.Fatalf("Unable to connect to broker %s, error %s", broker, token.Error())
}
//log.Printf("Successfully connected to broker %s", broker)
go outputDispatcher(mqttClient)
return
}
func Stop() {
log.Println("Disconnecting from broker")
mqttClient.Disconnect(250)
}

33
src/smq/snmp-mqtt.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"log"
"os"
"os/signal"
"smq/config"
"smq/mqtt"
"smq/snmp"
)
func main() {
log.SetPrefix("SMQ: ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("starting")
config.LoadConfiguration()
mqtt.Start()
defer mqtt.Stop()
snmp.Start()
defer snmp.Stop()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
<- c
log.Println("terminating")
}

156
src/smq/snmp/snmp.go Normal file
View File

@ -0,0 +1,156 @@
package snmp
import (
"fmt"
"log"
"time"
"encoding/json"
"strconv"
"math"
"smq/config"
"smq/mqtt"
"github.com/gosnmp/gosnmp"
)
type variable_t struct {
Label string `json:"label"`
Variable string `json:"variable"`
Value string `json:"value"`
}
type message_t struct {
Device string `json:"device"`
Label string `json:"label"`
Variables map[string]variable_t `json:"variables"`
}
type lastValue_t struct {
Timestamp time.Time
Value string
}
var lastValues map[string]lastValue_t = make(map[string]lastValue_t)
func calculateDifference(key string, newValue string) (string, error) {
currentTime := time.Now()
lvv, ok := lastValues[key]
if ok {
log.Printf("lvv for %s exists: %s", key, lvv.Value)
oldValueI, err1 := strconv.Atoi(lvv.Value)
if err1 != nil {
return "", fmt.Errorf("failed to convert lastValue: %s, %v", lvv.Value, err1)
}
newValueI, err2 := strconv.Atoi(newValue)
if err2 != nil {
return "", fmt.Errorf("failed to convert newValue: %s, %v", newValue, err2)
}
diffValueI := newValueI - oldValueI
diffTime := int(math.Round(currentTime.Sub(lvv.Timestamp).Seconds()))
diffValuePerSecond := diffValueI / diffTime
log.Printf("Diff: %d, Duration: %d, Diff per second: %d", diffValueI, diffTime, diffValuePerSecond)
lastValues[key] = lastValue_t {
Timestamp: currentTime,
Value: newValue,
}
return strconv.Itoa(diffValuePerSecond), nil
} else {
log.Printf("create lvv for %s", key)
lastValues[key] = lastValue_t {
Timestamp: currentTime,
Value: newValue,
}
return "0", nil
}
return "0", nil
}
func Start() {
for {
for _, endpoint := range config.Config.SNMPEndpoints {
log.Println("Polling endpoint " + endpoint.Endpoint)
snmp := gosnmp.GoSNMP{}
snmp.Target = endpoint.Endpoint
snmp.Port = 161
snmp.Version = gosnmp.Version2c
snmp.Community = endpoint.Community
snmp.Timeout = time.Duration(5 * time.Second)
err := snmp.Connect()
if err != nil {
log.Fatal("SNMP Connect error\n")
}
oids := []string{}
for _, oidTopic := range endpoint.OIDTopics {
oids = append(oids, oidTopic.OID)
}
result, err := snmp.Get(oids)
if err != nil {
log.Printf("error in Get: %s", err)
} else {
message := message_t {
Device: endpoint.Endpoint,
Label: endpoint.Label,
Variables: make(map[string]variable_t),
}
for _, variable := range result.Variables {
for _, oidTopic := range endpoint.OIDTopics {
if oidTopic.OID == variable.Name {
convertedValue := ""
switch variable.Type {
case gosnmp.OctetString:
convertedValue = string(variable.Value.([]byte))
default:
convertedValue = fmt.Sprintf("%d", gosnmp.ToBigInt(variable.Value))
}
if oidTopic.Diff == "true" {
log.Println("Calculate difference to last value")
key := endpoint.Endpoint + ":" + oidTopic.OID
diff, err := calculateDifference(key, convertedValue)
if err != nil {
log.Printf("Error when building difference: %v", err)
convertedValue = "-1"
} else {
convertedValue = diff
}
}
log.Printf("%s = %s", oidTopic.OID, convertedValue)
v := variable_t {
Label: oidTopic.Label,
Variable: oidTopic.OID,
Value: convertedValue,
}
message.Variables[oidTopic.Label] = v
// build topic and payload and push into the mqtt.OutputChannel
}
}
}
j, err := json.Marshal(message)
if err != nil {
log.Printf("Unable to marshal message, it is lost: %s, %v", message, err)
} else {
mqtt.Publish(j)
}
}
snmp.Conn.Close()
}
time.Sleep(time.Duration(config.Config.Interval) * time.Second)
}
}
func Stop() {
}