Giter Club home page Giter Club logo

go.nut's Issues

Addition of Socket Timeout Support (incl. Patch)

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
 }

Index out of bounds error when rate limited by Synology NUT server

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.

Errors with spaces in UPS desc

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.