blob: 74ce88df073cce4b7e286d4b72cb0a0272bb66b8 [file] [log] [blame]
package main
import (
"bufio"
"context"
"flag"
"fmt"
"io"
"net"
"os"
"sort"
"strings"
"time"
"github.com/golang/glog"
mirko "code.hackerspace.pl/hscloud/go/mirko"
hpb "code.hackerspace.pl/hscloud/hswaw/proto"
)
type lease struct {
hardware net.HardwareAddr
ip net.IP
from time.Time
to time.Time
}
const timeFmt = "2006/01/02 15:04:05"
func parseLeases(f io.Reader, now time.Time) ([]lease, error) {
scanner := bufio.NewScanner(f)
leases := make(map[string]*lease)
var curAddr *net.IP
var curStart *time.Time
var curEnd *time.Time
var curHW *net.HardwareAddr
for scanner.Scan() {
l := scanner.Text()
l = strings.TrimSpace(l)
if len(l) < 1 {
continue
}
if strings.HasPrefix(l, "#") {
continue
}
parts := strings.Fields(l)
if len(parts) < 1 {
continue
}
if curAddr == nil {
switch parts[0] {
case "lease":
if len(parts) < 3 || parts[2] != "{" {
glog.Warningf("invalid lease line %q", l)
break
}
ip := net.ParseIP(parts[1])
if ip == nil {
glog.Warningf("invalid lease line %q: invalid ip")
break
}
curAddr = &ip
}
} else {
parts[len(parts)-1] = strings.TrimRight(parts[len(parts)-1], ";")
switch parts[0] {
case "starts":
fallthrough
case "ends":
if len(parts) != 4 {
glog.Warningf("invalid time line %q", l)
break
}
t, err := time.Parse(timeFmt, fmt.Sprintf("%s %s", parts[2], parts[3]))
if err != nil {
glog.Warningf("invalid time line %q: %v", l, err)
break
}
if parts[0] == "starts" {
curStart = &t
} else {
curEnd = &t
}
case "hardware":
if len(parts) < 2 {
glog.Warningf("invalid hardware line %q", l)
break
}
if parts[1] != "ethernet" {
break
}
if len(parts) != 3 {
glog.Warningf("invalid hardware ethernet line %q", l)
break
}
hw, err := net.ParseMAC(parts[2])
if err != nil {
glog.Warningf("invalid hardware ethernet line %q: %v", l, err)
break
}
curHW = &hw
case "}":
if curStart == nil || curEnd == nil || curHW == nil {
glog.V(2).Info("Invalid block for %q, not enough fields", curAddr.String())
} else if curEnd.Before(now) {
// skip.
} else {
leases[curHW.String()] = &lease{
hardware: *curHW,
ip: *curAddr,
from: *curStart,
to: *curEnd,
}
}
curAddr = nil
curStart = nil
curEnd = nil
curHW = nil
}
}
}
ret := make([]lease, len(leases))
i := 0
for _, v := range leases {
ret[i] = *v
i += 1
}
return ret, nil
}
type service struct {
leaseFile string
leaseRefreshString string
leaseRefresh time.Duration
leaseC chan chan []lease
}
func (s *service) work(ctx context.Context) {
leases := []lease{}
ticker := time.NewTicker(s.leaseRefresh)
start := make(chan struct{}, 1)
start <- struct{}{}
work := func() {
glog.Infof("Parsing leases...")
f, err := os.Open(s.leaseFile)
if err != nil {
glog.Errorf("Could not open lease file: %v", err)
return
}
l, err := parseLeases(f, time.Now())
f.Close()
if err != nil {
glog.Errorf("Could not parse lease file: %v", err)
return
}
sort.Slice(l, func(i, j int) bool { return string([]byte(l[i].ip)) < string([]byte(l[j].ip)) })
leases = l
glog.Infof("Got %d leases", len(leases))
}
glog.Infof("Worker started.")
for {
select {
case <-start:
work()
case <-ticker.C:
work()
case c := <-s.leaseC:
c <- leases
case <-ctx.Done():
glog.Infof("Worker quitting.")
close(start)
return
}
}
}
func (s *service) Leases(ctx context.Context, req *hpb.LeasifierLeasesRequest) (*hpb.LeasifierLeasesResponse, error) {
c := make(chan []lease)
s.leaseC <- c
leases := <-c
res := &hpb.LeasifierLeasesResponse{
Leases: make([]*hpb.LeasifierLease, len(leases)),
}
for i, l := range leases {
res.Leases[i] = &hpb.LeasifierLease{
PhysicalAddress: l.hardware.String(),
IpAddress: l.ip.String(),
}
}
return res, nil
}
func main() {
s := &service{
leaseC: make(chan chan []lease),
}
flag.StringVar(&s.leaseFile, "lease_file", "/var/db/dhcpd.leases", "Location of leasefile")
flag.StringVar(&s.leaseRefreshString, "lease_refresh", "1m", "How often to refresh leases")
flag.Parse()
d, err := time.ParseDuration(s.leaseRefreshString)
if err != nil {
glog.Exit(err)
}
s.leaseRefresh = d
m := mirko.New()
if err := m.Listen(); err != nil {
glog.Exitf("Could not listen: %v", err)
}
hpb.RegisterLeasifierServer(m.GRPC(), s)
s.setupStatusz(m)
go s.work(m.Context())
if err := m.Serve(); err != nil {
glog.Exitf("Could not run: %v", err)
}
glog.Info("Running.")
<-m.Done()
}