pihole-exporter/internal/pihole/client.go

206 lines
6.1 KiB
Go
Raw Normal View History

2019-05-08 23:45:04 +02:00
package pihole
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
2019-05-08 23:45:04 +02:00
"strconv"
"strings"
"time"
"github.com/eko/pihole-exporter/config"
2019-05-08 23:45:04 +02:00
"github.com/eko/pihole-exporter/internal/metrics"
"github.com/prometheus/client_golang/prometheus/promhttp"
2019-05-08 23:45:04 +02:00
)
2019-05-09 21:11:31 +02:00
// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
2019-05-08 23:45:04 +02:00
type Client struct {
httpClient http.Client
interval time.Duration
config *config.Config
2019-05-08 23:45:04 +02:00
}
2019-05-09 21:11:31 +02:00
// NewClient method initializes a new PI-Hole client.
func NewClient(config *config.Config) *Client {
err := config.Validate()
if err != nil {
log.Print(err)
os.Exit(1)
}
fmt.Printf("Creating client with config %s\n", config)
client := &Client{
config: config,
2019-05-08 23:45:04 +02:00
httpClient: http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
}
fmt.Printf("Client created with config %s\n", client)
return client
}
func (c *Client) String() string {
return c.config.PIHoleHostname
2019-05-08 23:45:04 +02:00
}
// Metrics scrapes pihole and sets them
func (c *Client) Metrics() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
stats, err := c.getStatistics()
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte(err.Error()))
return
}
2019-05-09 21:11:31 +02:00
c.setMetrics(stats)
2019-05-08 23:45:04 +02:00
2019-05-09 21:11:31 +02:00
log.Printf("New tick of statistics: %s", stats.ToString())
promhttp.Handler().ServeHTTP(writer, request)
2019-05-09 21:11:31 +02:00
}
}
func (c *Client) CollectMetrics(writer http.ResponseWriter, request *http.Request) {
stats, err := c.getStatistics()
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte(err.Error()))
return
}
c.setMetrics(stats)
log.Printf("New tick of statistics: %s", stats.ToString())
promhttp.Handler().ServeHTTP(writer, request)
}
func (c *Client) GetHostname() string {
return c.config.PIHoleHostname
}
2019-05-09 21:11:31 +02:00
func (c *Client) setMetrics(stats *Stats) {
metrics.DomainsBlocked.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DomainsBeingBlocked))
metrics.DNSQueriesToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesToday))
metrics.AdsBlockedToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.AdsBlockedToday))
metrics.AdsPercentageToday.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.AdsPercentageToday))
metrics.UniqueDomains.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.UniqueDomains))
metrics.QueriesForwarded.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.QueriesForwarded))
metrics.QueriesCached.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.QueriesCached))
metrics.ClientsEverSeen.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.ClientsEverSeen))
metrics.UniqueClients.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.UniqueClients))
metrics.DNSQueriesAllTypes.WithLabelValues(c.config.PIHoleHostname).Set(float64(stats.DNSQueriesAllTypes))
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "no_data").Set(float64(stats.ReplyNoData))
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "nx_domain").Set(float64(stats.ReplyNxDomain))
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "cname").Set(float64(stats.ReplyCname))
metrics.Reply.WithLabelValues(c.config.PIHoleHostname, "ip").Set(float64(stats.ReplyIP))
2019-05-09 21:11:31 +02:00
var isEnabled int = 0
if stats.Status == enabledStatus {
isEnabled = 1
}
metrics.Status.WithLabelValues(c.config.PIHoleHostname).Set(float64(isEnabled))
2019-05-09 21:11:31 +02:00
for domain, value := range stats.TopQueries {
metrics.TopQueries.WithLabelValues(c.config.PIHoleHostname, domain).Set(float64(value))
2019-05-09 21:11:31 +02:00
}
for domain, value := range stats.TopAds {
metrics.TopAds.WithLabelValues(c.config.PIHoleHostname, domain).Set(float64(value))
2019-05-09 21:11:31 +02:00
}
for source, value := range stats.TopSources {
metrics.TopSources.WithLabelValues(c.config.PIHoleHostname, source).Set(float64(value))
2019-05-09 21:11:31 +02:00
}
for destination, value := range stats.ForwardDestinations {
metrics.ForwardDestinations.WithLabelValues(c.config.PIHoleHostname, destination).Set(value)
2019-05-09 21:11:31 +02:00
}
for queryType, value := range stats.QueryTypes {
metrics.QueryTypes.WithLabelValues(c.config.PIHoleHostname, queryType).Set(value)
2019-05-08 23:45:04 +02:00
}
}
func (c *Client) getPHPSessionID() (sessionID string) {
values := url.Values{"pw": []string{c.config.PIHolePassword}}
2019-05-08 23:45:04 +02:00
req, err := http.NewRequest("POST", c.config.PIHoleLoginURL(), strings.NewReader(values.Encode()))
2019-05-08 23:45:04 +02:00
if err != nil {
log.Fatal("An error has occured when creating HTTP statistics request", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(values.Encode())))
resp, err := c.httpClient.Do(req)
if err != nil {
log.Printf("An error has occured during login to PI-Hole: %v", err)
}
for _, cookie := range resp.Cookies() {
if cookie.Name == "PHPSESSID" {
sessionID = cookie.Value
break
}
}
return
2019-05-08 23:45:04 +02:00
}
func (c *Client) getStatistics() (*Stats, error) {
stats := new(Stats)
2019-05-08 23:45:04 +02:00
statsURL := c.config.PIHoleStatsURL()
2019-05-08 23:45:04 +02:00
if c.isUsingApiToken() {
statsURL = fmt.Sprintf("%s&auth=%s", statsURL, c.config.PIHoleApiToken)
}
2019-05-08 23:45:04 +02:00
req, err := http.NewRequest("GET", statsURL, nil)
if err != nil {
return nil, fmt.Errorf("an error has occured when creating HTTP statistics request: %w", err)
2019-05-08 23:45:04 +02:00
}
if c.isUsingPassword() {
c.authenticateRequest(req)
}
2019-05-08 23:45:04 +02:00
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("an error has occured during retrieving PI-Hole statistics: %w", err)
2019-05-08 23:45:04 +02:00
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read PI-Hole statistics HTTP response: %w", err)
2019-05-08 23:45:04 +02:00
}
err = json.Unmarshal(body, stats)
2019-05-08 23:45:04 +02:00
if err != nil {
return nil, fmt.Errorf("unable to unmarshal PI-Hole statistics to statistics struct model: %w", err)
2019-05-08 23:45:04 +02:00
}
return stats, nil
2019-05-08 23:45:04 +02:00
}
func (c *Client) isUsingPassword() bool {
return len(c.config.PIHolePassword) > 0
}
func (c *Client) isUsingApiToken() bool {
return len(c.config.PIHoleApiToken) > 0
}
func (c *Client) authenticateRequest(req *http.Request) {
cookie := http.Cookie{Name: "PHPSESSID", Value: c.getPHPSessionID()}
req.AddCookie(&cookie)
}