robbiet480 / go.nut Goto Github PK
View Code? Open in Web Editor NEWA Golang library for interacting with NUT (Network UPS Tools)
Home Page: https://godoc.org/github.com/robbiet480/go.nut
License: MIT License
A Golang library for interacting with NUT (Network UPS Tools)
Home Page: https://godoc.org/github.com/robbiet480/go.nut
License: MIT License
Hi everyone, I'm raising this issue on behalf of @Malinskiy Anton Malinskiy
He has created a patch which adds socket timeouts and is being depended on for a Telegraf plugin that implements monitoring of UPS via nut.go
here influxdata/telegraf#9890 and the Telegraf maintainers would prefer the plugin depend on the original repo rather than a fork that creates another point of failure.
timeout.txt <- (txt file as Github won't let you upload patch
files ๐ค )
which originates from Malinskiy@bb4669c
so some changes will probably needed to repoint the code back at this module, rather than his fork.
I'm a beginner at Go, I could give it a try, but I think I'd rather let someone who is more adept than myself do it for fear of introducing a breaking change.
From bb4669c66e1ed80138a6326b3f5185f063f07d46 Mon Sep 17 00:00:00 2001
From: Anton Malinskiy <[email protected]>
Date: Sun, 3 Oct 2021 19:56:06 +1100
Subject: [PATCH] feat(nut): implement socket timeouts
---
example_test.go | 7 +++----
go.mod | 3 +++
nut.go | 47 +++++++++++++++++++++++++++--------------------
3 files changed, 33 insertions(+), 24 deletions(-)
create mode 100644 go.mod
diff --git a/example_test.go b/example_test.go
index 6a23689..31936a1 100644
--- a/example_test.go
+++ b/example_test.go
@@ -2,17 +2,16 @@ package nut
import (
"fmt"
-
- nut "github.com/robbiet480/go.nut"
+ "time"
)
// This example connects to NUT, authenticates and returns the first UPS listed.
func ExampleGetUPSList() {
- client, connectErr := nut.Connect("127.0.0.1")
+ client, connectErr := Connect("127.0.0.1", 10*time.Second, 30*time.Second)
if connectErr != nil {
fmt.Print(connectErr)
}
- _, authenticationError = client.Authenticate("username", "password")
+ _, authenticationError := client.Authenticate("username", "password")
if authenticationError != nil {
fmt.Print(authenticationError)
}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..cba4e3e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/Malinskiy/go.nut
+
+go 1.17
diff --git a/nut.go b/nut.go
index 34fd406..9705867 100644
--- a/nut.go
+++ b/nut.go
@@ -8,32 +8,33 @@ import (
"fmt"
"net"
"strings"
+ "time"
)
// Client contains information about the NUT server as well as the connection.
type Client struct {
- Version string
- ProtocolVersion string
- Hostname net.Addr
- conn *net.TCPConn
+ opTimeout time.Duration
+ conn net.Conn
}
// Connect accepts a hostname/IP string and creates a connection to NUT, returning a Client.
-func Connect(hostname string) (Client, error) {
- tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:3493", hostname))
+func Connect(hostname string, connectTimeout time.Duration, opTimeout time.Duration) (*Client, error) {
+ _, _, err := net.SplitHostPort(hostname)
if err != nil {
- return Client{}, err
+ hostname = net.JoinHostPort(hostname, "3493")
}
- conn, err := net.DialTCP("tcp", nil, tcpAddr)
+ d := net.Dialer{
+ Timeout: connectTimeout,
+ }
+ conn, err := d.Dial("tcp", hostname)
if err != nil {
- return Client{}, err
+ return nil, err
}
- client := Client{
- Hostname: conn.RemoteAddr(),
- conn: conn,
+
+ client := &Client{
+ opTimeout: opTimeout,
+ conn: conn,
}
- client.GetVersion()
- client.GetNetworkProtocolVersion()
return client, nil
}
@@ -55,6 +56,10 @@ func (c *Client) ReadResponse(endLine string, multiLineResponse bool) (resp []st
response := []string{}
for {
+ err = c.conn.SetReadDeadline(time.Now().Add(c.opTimeout))
+ if err != nil {
+ return nil, err
+ }
line, err := connbuff.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("error reading response: %v", err)
@@ -79,18 +84,22 @@ func (c *Client) SendCommand(cmd string) (resp []string, err error) {
if strings.HasPrefix(cmd, "USERNAME ") || strings.HasPrefix(cmd, "PASSWORD ") || strings.HasPrefix(cmd, "SET ") || strings.HasPrefix(cmd, "HELP ") || strings.HasPrefix(cmd, "VER ") || strings.HasPrefix(cmd, "NETVER ") {
endLine = "OK\n"
}
- _, err = fmt.Fprint(c.conn, cmd)
+ err = c.conn.SetWriteDeadline(time.Now().Add(c.opTimeout))
+ if err != nil {
+ return nil, err
+ }
+ _, err = c.conn.Write([]byte(cmd))
if err != nil {
- return []string{}, err
+ return nil, err
}
resp, err = c.ReadResponse(endLine, strings.HasPrefix(cmd, "LIST "))
if err != nil {
- return []string{}, err
+ return nil, err
}
if strings.HasPrefix(resp[0], "ERR ") {
- return []string{}, errorForMessage(strings.Split(resp[0], " ")[1])
+ return nil, errorForMessage(strings.Split(resp[0], " ")[1])
}
return resp, nil
@@ -141,13 +150,11 @@ func (c *Client) Help() (string, error) {
// GetVersion returns the the version of the server currently in use.
func (c *Client) GetVersion() (string, error) {
versionResponse, err := c.SendCommand("VER")
- c.Version = versionResponse[0]
return versionResponse[0], err
}
// GetNetworkProtocolVersion returns the version of the network protocol currently in use.
func (c *Client) GetNetworkProtocolVersion() (string, error) {
versionResponse, err := c.SendCommand("NETVER")
- c.ProtocolVersion = versionResponse[0]
return versionResponse[0], err
}
Hey there! An issue was reported in nut_exporter a few days back that traces back to an index out of bounds in the go.nut client. I have asked for additional information to see how to defend against the issue, but a quick look at the source code suggests we could simply just check for length greater than 0 and/or err != nil before attempting to reference the response value in index 0.
I'll open a PR to do this checking, but may follow up with another once I know some more about the actual response that came back.
Using this library via nut_exporter, I was getting errors with my UPSs:
ts=2024-03-28T15:25:22.903Z caller=nut_collector.go:131 level=error msg="Failure getting the list of UPS devices" err="error reading response: EOF"
I tracked it down to getting the list of UPSs. When I have spaces in the description, it seems to be failing. When I changed the spaces to underscores, it worked.
This fails with EOF:
Trying ::1...
Connected to localhost.
Escape character is '^]'.
LIST UPS
BEGIN LIST UPS
UPS hotwater "Hotwater UPS"
UPS pi "Raspberry Pi UPSPlus"
END LIST UPS
Connection closed by foreign host.
This works:
Trying ::1...
Connected to localhost.
Escape character is '^]'.
LIST UPS
BEGIN LIST UPS
UPS hotwater "Hotwater_UPS"
UPS pi "RaspberryPi_UPSPlus"
END LIST UPS
Connection closed by foreign host.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.