blob: 9b2cf5ac7c887ad5638635a314c52da7fe76cd5e [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 {
141 leaseFile string
142 leaseC chan chan []lease
143}
144
145func (s *service) work(ctx context.Context) {
146 leases := []lease{}
147
148 ticker := time.NewTicker(30 * time.Second)
149 start := make(chan struct{}, 1)
150 start <- struct{}{}
151
152 work := func() {
153 glog.Infof("Parsing leases...")
154 f, err := os.Open(s.leaseFile)
155 if err != nil {
156 glog.Errorf("Could not open lease file: %v", err)
157 return
158 }
159 l, err := parseLeases(f, time.Now())
160 f.Close()
161 if err != nil {
162 glog.Errorf("Could not parse lease file: %v", err)
163 return
164 }
165 leases = l
166 glog.Infof("Got %d leases", len(leases))
167 }
168
169 glog.Infof("Worker started.")
170
171 for {
172 select {
173 case <-start:
174 work()
175 case <-ticker.C:
176 work()
177 case c := <-s.leaseC:
178 c <- leases
179 case <-ctx.Done():
180 glog.Infof("Worker quitting.")
181 close(start)
182 return
183 }
184 }
185}
186
187func (s *service) Leases(ctx context.Context, req *hpb.LeasifierLeasesRequest) (*hpb.LeasifierLeasesResponse, error) {
188 c := make(chan []lease)
189 s.leaseC <- c
190 leases := <-c
191
192 res := &hpb.LeasifierLeasesResponse{}
193
194 for i, l := range leases {
195 res.Leases[i] = &hpb.LeasifierLease{
196 PhysicalAddress: l.hardware.String(),
197 IpAddress: l.ip.String(),
198 }
199 }
200
201 return res, nil
202}
203
204func main() {
205 s := &service{
206 leaseC: make(chan chan []lease),
207 }
208
209 flag.StringVar(&s.leaseFile, "lease_file", "/var/db/dhcpd.leases", "Location of leasefile")
210 flag.Parse()
211
212 m := mirko.New()
213 if err := m.Listen(); err != nil {
214 glog.Exitf("Could not listen: %v", err)
215 }
216
217 hpb.RegisterLeasifierServer(m.GRPC(), s)
218
219 go s.work(m.Context())
220
221 if err := m.Serve(); err != nil {
222 glog.Exitf("Could not run: %v", err)
223 }
224
225 glog.Info("Running.")
226 <-m.Done()
227
228}