Browse Source

Fixes all names to be compliant with the golang standards.

pull/1/head
Christian Fritz 5 years ago
parent
commit
6713abd7d5
No known key found for this signature in database GPG Key ID: AB40486FCA9FA29C
  1. 69
      fritzbox_upnp/service.go
  2. 108
      main.go

69
fritzbox_upnp/service.go

@ -16,17 +16,17 @@ package fritzbox_upnp
// limitations under the License. // limitations under the License.
import ( import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/tls"
"encoding/xml" "encoding/xml"
"errors" "errors"
"bytes"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"crypto/tls"
"strconv" "strconv"
"strings" "strings"
"crypto/md5"
"crypto/rand"
) )
// curl http://fritz.box:49000/igddesc.xml // curl http://fritz.box:49000/igddesc.xml
@ -36,7 +36,7 @@ import (
// curl http://fritz.box:49000/igddslSCPD.xml // curl http://fritz.box:49000/igddslSCPD.xml
// curl http://fritz.box:49000/igd2ipv6fwcSCPD.xml // curl http://fritz.box:49000/igd2ipv6fwcSCPD.xml
const text_xml = `text/xml; charset="utf-8"` const textXml = `text/xml; charset="utf-8"`
var ErrInvalidSOAPResponse = errors.New("invalid SOAP response") var ErrInvalidSOAPResponse = errors.New("invalid SOAP response")
@ -99,34 +99,33 @@ type Action struct {
// An Inüut Argument to pass to an action // An Inüut Argument to pass to an action
type ActionArgument struct { type ActionArgument struct {
Name string Name string
Value interface{} Value interface{}
} }
// structs to unmarshal SOAP faults // structs to unmarshal SOAP faults
type SoapEnvelope struct { type SoapEnvelope struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Body SoapBody Body SoapBody
} }
type SoapBody struct { type SoapBody struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
Fault SoapFault Fault SoapFault
} }
type SoapFault struct { type SoapFault struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
FaultCode string `xml:"faultcode"` FaultCode string `xml:"faultcode"`
FaultString string `xml:"faultstring"` FaultString string `xml:"faultstring"`
Detail FaultDetail `xml:"detail"` Detail FaultDetail `xml:"detail"`
} }
type FaultDetail struct { type FaultDetail struct {
UpnpError UpnpError `xml:"UPnPError"` UpnpError UpnpError `xml:"UPnPError"`
} }
type UpnpError struct { type UpnpError struct {
ErrorCode int `xml:"errorCode"` ErrorCode int `xml:"errorCode"`
ErrorDescription string `xml:"errorDescription"` ErrorDescription string `xml:"errorDescription"`
} }
// Returns if the action seems to be a query for information. // Returns if the action seems to be a query for information.
// This is determined by checking if the action has no input arguments and at least one output argument. // This is determined by checking if the action has no input arguments and at least one output argument.
func (a *Action) IsGetOnly() bool { func (a *Action) IsGetOnly() bool {
@ -136,9 +135,6 @@ func (a *Action) IsGetOnly() bool {
} }
} }
return len(a.Arguments) > 0 return len(a.Arguments) > 0
return false
} }
// An Argument to an action // An Argument to an action
@ -262,7 +258,7 @@ func (d *Device) fillServices(r *Root) error {
const SoapActionXML = `<?xml version="1.0" encoding="utf-8"?>` + const SoapActionXML = `<?xml version="1.0" encoding="utf-8"?>` +
`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">` + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">` +
`<s:Body><u:%s xmlns:u=%s>%s</u:%s xmlns:u=%s></s:Body>` + `<s:Body><u:%s xmlns:u=%s>%s</u:%s xmlns:u=%s></s:Body>` +
`</s:Envelope>` `</s:Envelope>`
const SoapActionParamXML = `<%s>%s</%s>` const SoapActionParamXML = `<%s>%s</%s>`
@ -287,10 +283,10 @@ func (a *Action) createCallHttpRequest(actionArg *ActionArgument) (*http.Request
action := fmt.Sprintf("%s#%s", a.service.ServiceType, a.Name) action := fmt.Sprintf("%s#%s", a.service.ServiceType, a.Name)
req.Header.Set("Content-Type", text_xml) req.Header.Set("Content-Type", textXml)
req.Header.Set("SOAPAction", action) req.Header.Set("SOAPAction", action)
return req, nil; return req, nil
} }
// store auth header for reuse // store auth header for reuse
@ -318,7 +314,7 @@ func (a *Action) Call(actionArg *ActionArgument) (Result, error) {
wwwAuth := resp.Header.Get("WWW-Authenticate") wwwAuth := resp.Header.Get("WWW-Authenticate")
if resp.StatusCode == http.StatusUnauthorized { if resp.StatusCode == http.StatusUnauthorized {
resp.Body.Close() // close now, since we make a new request below or fail resp.Body.Close() // close now, since we make a new request below or fail
if wwwAuth != "" && a.service.Device.root.Username != "" && a.service.Device.root.Password != "" { if wwwAuth != "" && a.service.Device.root.Username != "" && a.service.Device.root.Password != "" {
// call failed, but we have a password so calculate header and try again // call failed, but we have a password so calculate header and try again
@ -363,7 +359,7 @@ func (a *Action) Call(actionArg *ActionArgument) (Result, error) {
soapFault := soapEnv.Body.Fault soapFault := soapEnv.Body.Fault
if soapFault.FaultString == "UPnPError" { if soapFault.FaultString == "UPnPError" {
upe := soapFault.Detail.UpnpError; upe := soapFault.Detail.UpnpError
errMsg = fmt.Sprintf("SAOPFault: %s %d (%s)", soapFault.FaultString, upe.ErrorCode, upe.ErrorDescription) errMsg = fmt.Sprintf("SAOPFault: %s %d (%s)", soapFault.FaultString, upe.ErrorCode, upe.ErrorDescription)
} else { } else {
@ -379,7 +375,7 @@ func (a *Action) Call(actionArg *ActionArgument) (Result, error) {
func (a *Action) getDigestAuthHeader(wwwAuth string, username string, password string) (string, error) { func (a *Action) getDigestAuthHeader(wwwAuth string, username string, password string) (string, error) {
// parse www-auth header // parse www-auth header
if ! strings.HasPrefix(wwwAuth, "Digest ") { if !strings.HasPrefix(wwwAuth, "Digest ") {
return "", errors.New(fmt.Sprintf("WWW-Authentication header is not Digest: '%s'", wwwAuth)) return "", errors.New(fmt.Sprintf("WWW-Authentication header is not Digest: '%s'", wwwAuth))
} }
@ -404,27 +400,26 @@ func (a *Action) getDigestAuthHeader(wwwAuth string, username string, password s
} }
// calc h1 and h2 // calc h1 and h2
ha1 := fmt.Sprintf("%x", md5.Sum([]byte(username + ":" + d["realm"] + ":" + password))) ha1 := fmt.Sprintf("%x", md5.Sum([]byte(username+":"+d["realm"]+":"+password)))
ha2 := fmt.Sprintf("%x", md5.Sum([]byte("POST:" + a.service.ControlUrl))) ha2 := fmt.Sprintf("%x", md5.Sum([]byte("POST:"+a.service.ControlUrl)))
cn := make([]byte, 8) cn := make([]byte, 8)
rand.Read(cn) rand.Read(cn)
cnonce := fmt.Sprintf("%x", cn) cnonce := fmt.Sprintf("%x", cn)
nCounter := 1 nCounter := 1
nc:=fmt.Sprintf("%08x", nCounter) nc := fmt.Sprintf("%08x", nCounter)
ds := strings.Join([]string{ha1, d["nonce"], nc, cnonce, d["qop"], ha2}, ":") ds := strings.Join([]string{ha1, d["nonce"], nc, cnonce, d["qop"], ha2}, ":")
response := fmt.Sprintf("%x", md5.Sum([]byte(ds))) response := fmt.Sprintf("%x", md5.Sum([]byte(ds)))
authHeader := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", cnonce=\"%s\", nc=%s, qop=%s, response=\"%s\", algorithm=%s", authHeader := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", cnonce=\"%s\", nc=%s, qop=%s, response=\"%s\", algorithm=%s",
username, d["realm"], d["nonce"], a.service.ControlUrl, cnonce, nc, d["qop"], response, d["algorithm"]) username, d["realm"], d["nonce"], a.service.ControlUrl, cnonce, nc, d["qop"], response, d["algorithm"])
return authHeader, nil return authHeader, nil
} }
func (a *Action) parseSoapResponse(r io.Reader) (Result, error) { func (a *Action) parseSoapResponse(r io.Reader) (Result, error) {
res := make(Result) res := make(Result)
dec := xml.NewDecoder(r) dec := xml.NewDecoder(r)
@ -482,7 +477,7 @@ func convertResult(val string, arg *Argument) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return uint64(res), nil return res, nil
case "i4": case "i4":
res, err := strconv.ParseInt(val, 10, 64) res, err := strconv.ParseInt(val, 10, 64)
if err != nil { if err != nil {

108
main.go

@ -39,26 +39,26 @@ import (
const serviceLoadRetryTime = 1 * time.Minute const serviceLoadRetryTime = 1 * time.Minute
var ( var (
flag_test = flag.Bool("test", false, "print all available metrics to stdout") flagTest = flag.Bool("test", false, "print all available metrics to stdout")
flag_collect = flag.Bool("collect", false, "print configured metrics to stdout and exit") flagCollect = flag.Bool("collect", false, "print configured metrics to stdout and exit")
flag_jsonout = flag.String("json-out", "", "store metrics also to JSON file when running test") flagJsonOut = flag.String("json-out", "", "store metrics also to JSON file when running test")
flag_addr = flag.String("listen-address", "127.0.0.1:9042", "The address to listen on for HTTP requests.") flagAddr = flag.String("listen-address", "127.0.0.1:9042", "The address to listen on for HTTP requests.")
flag_metrics_file = flag.String("metrics-file", "metrics.json", "The JSON file with the metric definitions.") flagMetricsFile = flag.String("metrics-file", "metrics.json", "The JSON file with the metric definitions.")
flag_gateway_url = flag.String("gateway-url", "http://fritz.box:49000", "The URL of the FRITZ!Box") flagGatewayUrl = flag.String("gateway-url", "http://fritz.box:49000", "The URL of the FRITZ!Box")
flag_gateway_username = flag.String("username", "", "The user for the FRITZ!Box UPnP service") flagGatewayUsername = flag.String("username", "", "The user for the FRITZ!Box UPnP service")
flag_gateway_password = flag.String("password", "", "The password for the FRITZ!Box UPnP service") flagGatewayPassword = flag.String("password", "", "The password for the FRITZ!Box UPnP service")
) )
var ( var (
collect_errors = prometheus.NewCounter(prometheus.CounterOpts{ collectErrors = prometheus.NewCounter(prometheus.CounterOpts{
Name: "fritzbox_exporter_collect_errors", Name: "fritzbox_exporter_collect_errors",
Help: "Number of collection errors.", Help: "Number of collection errors.",
}) })
) )
type JSON_PromDesc struct { type JsonPromDesc struct {
FqName string `json:"fqName"` FqName string `json:"fqName"`
Help string `json:"help"` Help string `json:"help"`
VarLabels []string `json:"varLabels"` VarLabels []string `json:"varLabels"`
@ -73,13 +73,13 @@ type ActionArg struct {
type Metric struct { type Metric struct {
// initialized loading JSON // initialized loading JSON
Service string `json:"service"` Service string `json:"service"`
Action string `json:"action"` Action string `json:"action"`
ActionArgument *ActionArg `json:"actionArgument"` ActionArgument *ActionArg `json:"actionArgument"`
Result string `json:"result"` Result string `json:"result"`
OkValue string `json:"okValue"` OkValue string `json:"okValue"`
PromDesc JSON_PromDesc `json:"promDesc"` PromDesc JsonPromDesc `json:"promDesc"`
PromType string `json:"promType"` PromType string `json:"promType"`
// initialized at startup // initialized at startup
Desc *prometheus.Desc Desc *prometheus.Desc
@ -152,7 +152,7 @@ func (fc *FritzboxCollector) ReportMetric(ch chan<- prometheus.Metric, m *Metric
val, ok := result[m.Result] val, ok := result[m.Result]
if !ok { if !ok {
fmt.Printf("%s.%s has no result %s", m.Service, m.Action, m.Result) fmt.Printf("%s.%s has no result %s", m.Service, m.Action, m.Result)
collect_errors.Inc() collectErrors.Inc()
return return
} }
@ -174,7 +174,7 @@ func (fc *FritzboxCollector) ReportMetric(ch chan<- prometheus.Metric, m *Metric
} }
default: default:
fmt.Println("unknown type", val) fmt.Println("unknown type", val)
collect_errors.Inc() collectErrors.Inc()
return return
} }
@ -189,7 +189,7 @@ func (fc *FritzboxCollector) ReportMetric(ch chan<- prometheus.Metric, m *Metric
lval = "" lval = ""
} }
// convert tolower to avoid problems with labels like hostname // convert to lower to avoid problems with labels like hostname
labels[i] = strings.ToLower(fmt.Sprintf("%v", lval)) labels[i] = strings.ToLower(fmt.Sprintf("%v", lval))
} }
} }
@ -201,18 +201,18 @@ func (fc *FritzboxCollector) ReportMetric(ch chan<- prometheus.Metric, m *Metric
labels...) labels...)
} }
func (fc *FritzboxCollector) GetActionResult(result_map map[string]upnp.Result, serviceType string, actionName string, actionArg *upnp.ActionArgument) (upnp.Result, error) { func (fc *FritzboxCollector) GetActionResult(resultMap map[string]upnp.Result, serviceType string, actionName string, actionArg *upnp.ActionArgument) (upnp.Result, error) {
m_key := serviceType + "|" + actionName mKey := serviceType + "|" + actionName
// for calls with argument also add arguement name and value to key // for calls with argument also add arguement name and value to key
if actionArg != nil { if actionArg != nil {
m_key += "|" + actionArg.Name + "|" + fmt.Sprintf("%v", actionArg.Value) mKey += "|" + actionArg.Name + "|" + fmt.Sprintf("%v", actionArg.Value)
} }
last_result := result_map[m_key] lastResult := resultMap[mKey]
if last_result == nil { if lastResult == nil {
service, ok := fc.Root.Services[serviceType] service, ok := fc.Root.Services[serviceType]
if !ok { if !ok {
return nil, errors.New(fmt.Sprintf("service %s not found", serviceType)) return nil, errors.New(fmt.Sprintf("service %s not found", serviceType))
@ -224,16 +224,16 @@ func (fc *FritzboxCollector) GetActionResult(result_map map[string]upnp.Result,
} }
var err error var err error
last_result, err = action.Call(actionArg) lastResult, err = action.Call(actionArg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result_map[m_key] = last_result resultMap[mKey] = lastResult
} }
return last_result, nil return lastResult, nil
} }
func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) { func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
@ -247,7 +247,7 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
} }
// create a map for caching results // create a map for caching results
var result_map = make(map[string]upnp.Result) var resultMap = make(map[string]upnp.Result)
for _, m := range metrics { for _, m := range metrics {
var actArg *upnp.ActionArgument var actArg *upnp.ActionArgument
@ -257,19 +257,19 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
value = aa.Value value = aa.Value
if aa.ProviderAction != "" { if aa.ProviderAction != "" {
provRes, err := fc.GetActionResult(result_map, m.Service, aa.ProviderAction, nil) provRes, err := fc.GetActionResult(resultMap, m.Service, aa.ProviderAction, nil)
if err != nil { if err != nil {
fmt.Printf("Error getting provider action %s result for %s.%s: %s\n", aa.ProviderAction, m.Service, m.Action, err.Error()) fmt.Printf("Error getting provider action %s result for %s.%s: %s\n", aa.ProviderAction, m.Service, m.Action, err.Error())
collect_errors.Inc() collectErrors.Inc()
continue continue
} }
var ok bool var ok bool
value, ok = provRes[aa.Value] // Value contains the result name for provider actions value, ok = provRes[aa.Value] // Value contains the result name for provider actions
if !ok { if !ok {
fmt.Printf("provider action %s for %s.%s has no result %s", m.Service, m.Action, aa.Value) fmt.Printf("provider action %s for %s.%s has no result", m.Service, m.Action, aa.Value)
collect_errors.Inc() collectErrors.Inc()
continue continue
} }
} }
@ -279,17 +279,17 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
count, err := strconv.Atoi(sval) count, err := strconv.Atoi(sval)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
collect_errors.Inc() collectErrors.Inc()
continue continue
} }
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
actArg = &upnp.ActionArgument{Name: aa.Name, Value: i} actArg = &upnp.ActionArgument{Name: aa.Name, Value: i}
result, err := fc.GetActionResult(result_map, m.Service, m.Action, actArg) result, err := fc.GetActionResult(resultMap, m.Service, m.Action, actArg)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
collect_errors.Inc() collectErrors.Inc()
continue continue
} }
@ -302,11 +302,11 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
} }
} }
result, err := fc.GetActionResult(result_map, m.Service, m.Action, actArg) result, err := fc.GetActionResult(resultMap, m.Service, m.Action, actArg)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
collect_errors.Inc() collectErrors.Inc()
continue continue
} }
@ -315,7 +315,7 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
} }
func test() { func test() {
root, err := upnp.LoadServices(*flag_gateway_url, *flag_gateway_username, *flag_gateway_password) root, err := upnp.LoadServices(*flagGatewayUrl, *flagGatewayUsername, *flagGatewayPassword)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -333,7 +333,7 @@ func test() {
s := root.Services[k] s := root.Services[k]
fmt.Printf("Service: %s (Url: %s)\n", k, s.ControlUrl) fmt.Printf("Service: %s (Url: %s)\n", k, s.ControlUrl)
actionKeys := []string{} var actionKeys []string
for l, _ := range s.Actions { for l, _ := range s.Actions {
actionKeys = append(actionKeys, l) actionKeys = append(actionKeys, l)
} }
@ -386,10 +386,10 @@ func test() {
json.WriteString("\n]") json.WriteString("\n]")
if *flag_jsonout != "" { if *flagJsonOut != "" {
err := ioutil.WriteFile(*flag_jsonout, json.Bytes(), 0644) err := ioutil.WriteFile(*flagJsonOut, json.Bytes(), 0644)
if err != nil { if err != nil {
fmt.Printf("Failed writing JSON file '%s': %s\n", *flag_jsonout, err.Error()) fmt.Printf("Failed writing JSON file '%s': %s\n", *flagJsonOut, err.Error())
} }
} }
} }
@ -410,19 +410,19 @@ func getValueType(vt string) prometheus.ValueType {
func main() { func main() {
flag.Parse() flag.Parse()
u, err := url.Parse(*flag_gateway_url) u, err := url.Parse(*flagGatewayUrl)
if err != nil { if err != nil {
fmt.Println("invalid URL:", err) fmt.Println("invalid URL:", err)
return return
} }
if *flag_test { if *flagTest {
test() test()
return return
} }
// read metrics // read metrics
jsonData, err := ioutil.ReadFile(*flag_metrics_file) jsonData, err := ioutil.ReadFile(*flagMetricsFile)
if err != nil { if err != nil {
fmt.Println("error reading metric file:", err) fmt.Println("error reading metric file:", err)
return return
@ -449,17 +449,17 @@ func main() {
} }
collector := &FritzboxCollector{ collector := &FritzboxCollector{
Url: *flag_gateway_url, Url: *flagGatewayUrl,
Gateway: u.Hostname(), Gateway: u.Hostname(),
Username: *flag_gateway_username, Username: *flagGatewayUsername,
Password: *flag_gateway_password, Password: *flagGatewayPassword,
} }
if *flag_collect { if *flagCollect {
collector.LoadServices() collector.LoadServices()
prometheus.MustRegister(collector) prometheus.MustRegister(collector)
prometheus.MustRegister(collect_errors) prometheus.MustRegister(collectErrors)
fmt.Println("collecting metrics via http") fmt.Println("collecting metrics via http")
@ -476,10 +476,10 @@ func main() {
go collector.LoadServices() go collector.LoadServices()
prometheus.MustRegister(collector) prometheus.MustRegister(collector)
prometheus.MustRegister(collect_errors) prometheus.MustRegister(collectErrors)
http.Handle("/metrics", promhttp.Handler()) http.Handle("/metrics", promhttp.Handler())
fmt.Printf("metrics available at http://%s/metrics\n", *flag_addr) fmt.Printf("metrics available at http://%s/metrics\n", *flagAddr)
log.Fatal(http.ListenAndServe(*flag_addr, nil)) log.Fatal(http.ListenAndServe(*flagAddr, nil))
} }

Loading…
Cancel
Save