forked from hswaw/hscloud
243 lines
4.7 KiB
Go
243 lines
4.7 KiB
Go
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()
|
|
|
|
}
|