blob: 09090b6e8500ae45aa46e1c6994bed58522fe253 [file] [log] [blame]
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +02001package main
2
3import (
4 "bufio"
5 "context"
6 "flag"
7 "fmt"
8 "io"
9 "net"
10 "os"
11 "strings"
12 "time"
13
14 "github.com/golang/glog"
15
16 mirko "code.hackerspace.pl/hscloud/go/mirko"
17 hpb "code.hackerspace.pl/hscloud/proto/hswaw"
18)
19
20type lease struct {
21 hardware net.HardwareAddr
22 ip net.IP
23 from time.Time
24 to time.Time
25}
26
27const timeFmt = "2006/01/02 15:04:05"
28
29func parseLeases(f io.Reader, now time.Time) ([]lease, error) {
30 scanner := bufio.NewScanner(f)
31
32 leases := make(map[string]*lease)
33
34 var curAddr *net.IP
35 var curStart *time.Time
36 var curEnd *time.Time
37 var curHW *net.HardwareAddr
38
39 for scanner.Scan() {
40 l := scanner.Text()
41 l = strings.TrimSpace(l)
42 if len(l) < 1 {
43 continue
44 }
45
46 if strings.HasPrefix(l, "#") {
47 continue
48 }
49
50 parts := strings.Fields(l)
51 if len(parts) < 1 {
52 continue
53 }
54
55 if curAddr == nil {
56 switch parts[0] {
57 case "lease":
58 if len(parts) < 3 || parts[2] != "{" {
59 glog.Warningf("invalid lease line %q", l)
60 break
61 }
62
63 ip := net.ParseIP(parts[1])
64 if ip == nil {
65 glog.Warningf("invalid lease line %q: invalid ip")
66 break
67 }
68 curAddr = &ip
69 }
70 } else {
71 parts[len(parts)-1] = strings.TrimRight(parts[len(parts)-1], ";")
72
73 switch parts[0] {
74 case "starts":
75 fallthrough
76 case "ends":
77 if len(parts) != 4 {
78 glog.Warningf("invalid time line %q", l)
79 break
80 }
81 t, err := time.Parse(timeFmt, fmt.Sprintf("%s %s", parts[2], parts[3]))
82 if err != nil {
83 glog.Warningf("invalid time line %q: %v", l, err)
84 break
85 }
86 if parts[0] == "starts" {
87 curStart = &t
88 } else {
89 curEnd = &t
90 }
91 case "hardware":
92 if len(parts) < 2 {
93 glog.Warningf("invalid hardware line %q", l)
94 break
95 }
96 if parts[1] != "ethernet" {
97 break
98 }
99 if len(parts) != 3 {
100 glog.Warningf("invalid hardware ethernet line %q", l)
101 break
102 }
103 hw, err := net.ParseMAC(parts[2])
104 if err != nil {
105 glog.Warningf("invalid hardware ethernet line %q: %v", l, err)
106 break
107 }
108 curHW = &hw
109 case "}":
110 if curStart == nil || curEnd == nil || curHW == nil {
111 glog.V(2).Info("Invalid block for %q, not enough fields", curAddr.String())
112 } else if curEnd.Before(now) {
113 // skip.
114 } else {
115 leases[curHW.String()] = &lease{
116 hardware: *curHW,
117 ip: *curAddr,
118 from: *curStart,
119 to: *curEnd,
120 }
121 }
122 curAddr = nil
123 curStart = nil
124 curEnd = nil
125 curHW = nil
126 }
127 }
128 }
129
130 ret := make([]lease, len(leases))
131 i := 0
132 for _, v := range leases {
133 ret[i] = *v
134 i += 1
135 }
136
137 return ret, nil
138}
139
140type service struct {
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200141 leaseFile string
142 leaseRefreshString string
143 leaseRefresh time.Duration
144 leaseC chan chan []lease
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200145}
146
147func (s *service) work(ctx context.Context) {
148 leases := []lease{}
149
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200150 ticker := time.NewTicker(s.leaseRefresh)
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200151 start := make(chan struct{}, 1)
152 start <- struct{}{}
153
154 work := func() {
155 glog.Infof("Parsing leases...")
156 f, err := os.Open(s.leaseFile)
157 if err != nil {
158 glog.Errorf("Could not open lease file: %v", err)
159 return
160 }
161 l, err := parseLeases(f, time.Now())
162 f.Close()
163 if err != nil {
164 glog.Errorf("Could not parse lease file: %v", err)
165 return
166 }
167 leases = l
168 glog.Infof("Got %d leases", len(leases))
169 }
170
171 glog.Infof("Worker started.")
172
173 for {
174 select {
175 case <-start:
176 work()
177 case <-ticker.C:
178 work()
179 case c := <-s.leaseC:
180 c <- leases
181 case <-ctx.Done():
182 glog.Infof("Worker quitting.")
183 close(start)
184 return
185 }
186 }
187}
188
189func (s *service) Leases(ctx context.Context, req *hpb.LeasifierLeasesRequest) (*hpb.LeasifierLeasesResponse, error) {
190 c := make(chan []lease)
191 s.leaseC <- c
192 leases := <-c
193
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200194 res := &hpb.LeasifierLeasesResponse{
195 Leases: make([]*hpb.LeasifierLease, len(leases)),
196 }
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200197
198 for i, l := range leases {
199 res.Leases[i] = &hpb.LeasifierLease{
200 PhysicalAddress: l.hardware.String(),
201 IpAddress: l.ip.String(),
202 }
203 }
204
205 return res, nil
206}
207
208func main() {
209 s := &service{
210 leaseC: make(chan chan []lease),
211 }
212
213 flag.StringVar(&s.leaseFile, "lease_file", "/var/db/dhcpd.leases", "Location of leasefile")
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200214 flag.StringVar(&s.leaseRefreshString, "lease_refresh", "1m", "How often to refresh leases")
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200215 flag.Parse()
216
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200217 d, err := time.ParseDuration(s.leaseRefreshString)
218 if err != nil {
219 glog.Exit(err)
220 }
221 s.leaseRefresh = d
222
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200223 m := mirko.New()
224 if err := m.Listen(); err != nil {
225 glog.Exitf("Could not listen: %v", err)
226 }
227
228 hpb.RegisterLeasifierServer(m.GRPC(), s)
229
Sergiusz Bazanskia9a266c2019-04-06 01:21:25 +0200230 s.setupStatusz(m)
Sergiusz Bazanski9dc4b682019-04-05 23:51:49 +0200231 go s.work(m.Context())
232
233 if err := m.Serve(); err != nil {
234 glog.Exitf("Could not run: %v", err)
235 }
236
237 glog.Info("Running.")
238 <-m.Done()
239
240}