Allow and deny lists per subnet; every option of a DHCP message in words
- A subnet is a deny-list subnet (the default: everyone not denied) or
an allow-list subnet (only clients allowed or with a reservation);
the deny list counts in both. Entries are MAC addresses, prefixes
(aa:bb:cc:*) or client IDs. A refused client gets no answer (DENIED
in the activity log, with an Allow button) or a NAK on renewal.
steward access, the subnet's Access tab. Schema migration 7.
- The activity log keeps each message's header fields and options,
decoded into words (dhcp4.Describe): the parameter request list by
name, client IDs, lease times. ▸ on an Activity row, steward
activity show N / --details, GET /events/{id}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b3c8329ae8
commit
3ad2f981d4
30 files changed
+1092
-37
No files matched your search
@@ -612,3 +612,51 @@ func TestReservationLeaseTimeAndClientID(t *testing.T) {
|
||||
t.Errorf("stranger: %s %d", ack.YIAddr, u32(ack))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessLists(t *testing.T) {
|
||||
// A deny-list subnet: everyone but the denied.
|
||||
h := newHarness(t, func(st *model.State) {
|
||||
st.Subnets[0].Deny = []model.ClientMatch{{Match: "aa:00:00:00:00:02"}, {Match: "aa:00:00:00:00:9*"}}
|
||||
})
|
||||
if h.discover("eth0", mac(1)) == nil {
|
||||
t.Error("an unlisted client was refused")
|
||||
}
|
||||
for _, m := range []byte{2, 0x95} {
|
||||
if out := h.discover("eth0", mac(m)); out != nil {
|
||||
t.Errorf("denied %s got %s", mac(m), out.Packet.YIAddr)
|
||||
}
|
||||
}
|
||||
if evs := h.s.Events(1, nil); evs[0].Type != "DENIED" || !strings.Contains(evs[0].Detail, "deny list of office") {
|
||||
t.Errorf("event %+v", evs[0])
|
||||
}
|
||||
// The deny list wins over a reservation (the printer, aa:...:98).
|
||||
if out := h.discover("eth0", mac(0x98)); out != nil {
|
||||
t.Errorf("a denied reserved client got %s", out.Packet.YIAddr)
|
||||
}
|
||||
|
||||
// An allow-list subnet: the allowed, by MAC, prefix or client ID, and
|
||||
// clients with a reservation.
|
||||
h = newHarness(t, func(st *model.State) {
|
||||
st.Subnets[0].Access = model.AccessAllowList
|
||||
st.Subnets[0].Allow = []model.ClientMatch{{Match: "aa:00:00:00:00:01"}, {Match: "id ff07"}}
|
||||
})
|
||||
ip := h.lease("eth0", mac(1)).YIAddr
|
||||
if h.discover("eth0", mac(3)) != nil {
|
||||
t.Error("an unlisted client got an address")
|
||||
}
|
||||
if h.discover("eth0", mac(7), dhcp4.Option{Code: dhcp4.OptClientID, Data: []byte{0xff, 7}}) == nil {
|
||||
t.Error("a client allowed by client ID was refused")
|
||||
}
|
||||
if h.discover("eth0", mac(0x99)) == nil {
|
||||
t.Error("a reserved client was refused")
|
||||
}
|
||||
// Taken off the list: its renewal gets a NAK.
|
||||
st := h.s.cfg.Load().state
|
||||
st.Subnets[0].Allow = st.Subnets[0].Allow[1:]
|
||||
h.s.SetConfig(st)
|
||||
ren := dhcp4.NewRequest(dhcp4.Request, mac(1), 9)
|
||||
ren.CIAddr = ip
|
||||
if out := h.send("eth0", ren); out == nil || out.Packet.Type() != dhcp4.Nak || !strings.Contains(string(out.Packet.Get(dhcp4.OptMessage)), "allow list") {
|
||||
t.Errorf("renewal of a refused client: %+v", out)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user