| 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() |
| |
| } |