From cb4aa46aa7718bb78b889af5c91b59a210920891 Mon Sep 17 00:00:00 2001 From: Nils Decker Date: Mon, 29 Feb 2016 17:10:34 +0100 Subject: [PATCH] initial working version --- main.go | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ upnp.go | 98 ++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 main.go create mode 100644 upnp.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..e266238 --- /dev/null +++ b/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "flag" + "net/http" + "strconv" + + "github.com/prometheus/client_golang/prometheus" +) + +var ( + flag_addr = flag.String("listen-address", ":9111", "The address to listen on for HTTP requests.") + flag_dev_address = flag.String("device-address", "fritz.box", "The URL of the upnp service") +) + +var ( + WAN_IP = UpnpValue{ + path: "/igdupnp/control/WANIPConn1", + service: "WANIPConnection:1", + method: "GetExternalIPAddress", + ret_tag: "NewExternalIPAddress", + } + + WAN_Packets_Received = UpnpValue{ + path: "/igdupnp/control/WANCommonIFC1", + service: "WANCommonInterfaceConfig:1", + method: "GetTotalPacketsReceived", + ret_tag: "NewTotalPacketsReceived", + } + + WAN_Packets_Sent = UpnpValue{ + path: "/igdupnp/control/WANCommonIFC1", + service: "WANCommonInterfaceConfig:1", + method: "GetTotalPacketsSent", + ret_tag: "NewTotalPacketsSent", + } + + WAN_Bytes_Received = UpnpValue{ + path: "/igdupnp/control/WANCommonIFC1", + service: "WANCommonInterfaceConfig:1", + method: "GetAddonInfos", + ret_tag: "NewTotalBytesReceived", + } + + WAN_Bytes_Sent = UpnpValue{ + path: "/igdupnp/control/WANCommonIFC1", + service: "WANCommonInterfaceConfig:1", + method: "GetAddonInfos", + ret_tag: "NewTotalBytesSent", + } +) + +type Metric struct { + UpnpValue + *prometheus.Desc +} + +func (m Metric) Value() (uint64, error) { + strval, err := m.Query(*flag_dev_address) + if err != nil { + return 0, err + } + + return strconv.ParseUint(strval, 10, 64) +} + +func (m Metric) Describe(ch chan<- *prometheus.Desc) { + ch <- m.Desc +} + +func (m Metric) Collect(ch chan<- prometheus.Metric) error { + val, err := m.Value() + if err != nil { + return err + } + + ch <- prometheus.MustNewConstMetric( + m.Desc, + prometheus.CounterValue, + float64(val), + ) + return nil +} + +var ( + packets_sent = Metric{ + WAN_Packets_Sent, + prometheus.NewDesc( + prometheus.BuildFQName("gateway", "wan", "packets_sent"), + "packets sent on gateway wan interface", + nil, + prometheus.Labels{"gateway": *flag_dev_address}, + ), + } + packets_received = Metric{ + WAN_Packets_Received, + prometheus.NewDesc( + prometheus.BuildFQName("gateway", "wan", "packets_received"), + "packets received on gateway wan interface", + nil, + prometheus.Labels{"gateway": *flag_dev_address}, + ), + } + bytes_sent = Metric{ + WAN_Bytes_Sent, + prometheus.NewDesc( + prometheus.BuildFQName("gateway", "wan", "bytes_sent"), + "bytes sent on gateway wan interface", + nil, + prometheus.Labels{"gateway": *flag_dev_address}, + ), + } + bytes_received = Metric{ + WAN_Bytes_Received, + prometheus.NewDesc( + prometheus.BuildFQName("gateway", "wan", "bytes_received"), + "bytes received on gateway wan interface", + nil, + prometheus.Labels{"gateway": *flag_dev_address}, + ), + } +) + + +type FritzboxCollector struct { +} + +func (fc *FritzboxCollector) Describe(ch chan<- *prometheus.Desc) { + packets_sent.Describe(ch) + packets_received.Describe(ch) + bytes_sent.Describe(ch) + bytes_received.Describe(ch) +} + +func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) { + packets_sent.Collect(ch) + packets_received.Collect(ch) + bytes_sent.Collect(ch) + bytes_received.Collect(ch) +} + + +func main() { + flag.Parse() + + prometheus.MustRegister(&FritzboxCollector{}) + // Since we are dealing with custom Collector implementations, it might + // be a good idea to enable the collect checks in the registry. + prometheus.EnableCollectChecks(true) + + http.Handle("/metrics", prometheus.Handler()) + http.ListenAndServe(*flag_addr, nil) +} diff --git a/upnp.go b/upnp.go new file mode 100644 index 0000000..1ceb2a8 --- /dev/null +++ b/upnp.go @@ -0,0 +1,98 @@ +package main + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "net/http" + "strings" +) + +const TEXT_XML = `text/xml; charset="utf-8"` + +var ( + ErrResultNotFound = errors.New("result not found") + ErrResultWithoutChardata = errors.New("result without chardata") +) + +// curl "http://fritz.box:49000/igdupnp/control/WANIPConn1" +// -H "Content-Type: text/xml; charset="utf-8"" +// -H "SoapAction:urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress" +// -d " +// +// +// +// " + +type UpnpValue struct { + path string + service string + method string + ret_tag string +} + +func (v *UpnpValue) Query(device string) (string, error) { + url := fmt.Sprintf("http://%s:49000%s", device, v.path) + + bodystr := fmt.Sprintf(` + + + + + + + `, v.method, v.service) + + body := strings.NewReader(bodystr) + + req, err := http.NewRequest("POST", url, body) + if err != nil { + return "", err + } + + action := fmt.Sprintf("urn:schemas-upnp-org:service:%s#%s", v.service, v.method) + + req.Header["Content-Type"] = []string{TEXT_XML} + req.Header["SoapAction"] = []string{action} + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + + data := new(bytes.Buffer) + data.ReadFrom(resp.Body) + + // fmt.Printf(data.String()) + + dec := xml.NewDecoder(data) + + for { + t, err := dec.Token() + if err == io.EOF { + return "", ErrResultNotFound + } + + if err != nil { + return "", err + } + + if se, ok := t.(xml.StartElement); ok { + if se.Name.Local == v.ret_tag { + t2, err := dec.Token() + if err != nil { + return "", err + } + + data, ok := t2.(xml.CharData) + if !ok { + return "", ErrResultWithoutChardata + } + return string(data), nil + } + } + + } +}