quirk: enforce minimum lease time of 1 hour for Nintendo devices
The Nintendo Switch has been observed to hold on to IP addresses even after their expiration. My guess is that this is an oversight: likely the device enters power saving mode with a configured IP address and just sleeps through the expiration time. As the device seems to wake up once every hour, we enforce a minimum lease time of 1 hour, but only for affected devices. The rest of the network gets short lease times. https://twitter.com/zekjur/status/1263949112036282374
This commit is contained in:
parent
d81b77a876
commit
53c495091e
@ -16,9 +16,13 @@
|
||||
package dhcp4d
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@ -246,17 +250,32 @@ func (h *Handler) leaseHW(hwAddr string) (*Lease, bool) {
|
||||
return l, ok && l.HardwareAddr == hwAddr
|
||||
}
|
||||
|
||||
func (h *Handler) leasePeriodForDevice(hwAddr string) time.Duration {
|
||||
hwAddrPrefix, err := hex.DecodeString(strings.ReplaceAll(hwAddr, ":", ""))
|
||||
if err != nil {
|
||||
return h.LeasePeriod
|
||||
}
|
||||
hwAddrPrefix = hwAddrPrefix[:3]
|
||||
i := sort.Search(len(nintendoMacPrefixes), func(i int) bool {
|
||||
return bytes.Compare(nintendoMacPrefixes[i][:], hwAddrPrefix) >= 0
|
||||
})
|
||||
if i < len(nintendoMacPrefixes) && bytes.Equal(nintendoMacPrefixes[i][:], hwAddrPrefix) {
|
||||
return 1 * time.Hour
|
||||
}
|
||||
return h.LeasePeriod
|
||||
}
|
||||
|
||||
// TODO: is ServeDHCP always run from the same goroutine, or do we need locking?
|
||||
func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) dhcp4.Packet {
|
||||
reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress])
|
||||
if reqIP == nil {
|
||||
reqIP = net.IP(p.CIAddr())
|
||||
}
|
||||
hwAddr := p.CHAddr().String()
|
||||
|
||||
switch msgType {
|
||||
case dhcp4.Discover:
|
||||
free := -1
|
||||
hwAddr := p.CHAddr().String()
|
||||
|
||||
// try to offer the requested IP, if any and available
|
||||
if !reqIP.To4().Equal(net.IPv4zero) {
|
||||
@ -284,14 +303,14 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
dhcp4.Offer,
|
||||
h.serverIP,
|
||||
dhcp4.IPAdd(h.start, free),
|
||||
h.LeasePeriod,
|
||||
h.leasePeriodForDevice(hwAddr),
|
||||
h.options.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
|
||||
case dhcp4.Request:
|
||||
if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.serverIP) {
|
||||
return nil // message not for this dhcp server
|
||||
}
|
||||
leaseNum := h.canLease(reqIP, p.CHAddr().String())
|
||||
leaseNum := h.canLease(reqIP, hwAddr)
|
||||
if leaseNum == -1 {
|
||||
return dhcp4.ReplyPacket(p, dhcp4.NAK, h.serverIP, nil, 0, nil)
|
||||
}
|
||||
@ -299,8 +318,8 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
lease := &Lease{
|
||||
Num: leaseNum,
|
||||
Addr: make([]byte, 4),
|
||||
HardwareAddr: p.CHAddr().String(),
|
||||
Expiry: h.timeNow().Add(h.LeasePeriod),
|
||||
HardwareAddr: hwAddr,
|
||||
Expiry: h.timeNow().Add(h.leasePeriodForDevice(hwAddr)),
|
||||
Hostname: string(options[dhcp4.OptionHostName]),
|
||||
}
|
||||
copy(lease.Addr, reqIP.To4())
|
||||
@ -327,7 +346,12 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
h.leasesIP[leaseNum] = lease
|
||||
h.leasesHW[lease.HardwareAddr] = leaseNum
|
||||
h.callLeasesLocked(lease)
|
||||
return dhcp4.ReplyPacket(p, dhcp4.ACK, h.serverIP, reqIP, h.LeasePeriod,
|
||||
return dhcp4.ReplyPacket(
|
||||
p,
|
||||
dhcp4.ACK,
|
||||
h.serverIP,
|
||||
reqIP,
|
||||
h.leasePeriodForDevice(hwAddr),
|
||||
h.options.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
}
|
||||
return nil
|
||||
|
@ -15,6 +15,7 @@
|
||||
package dhcp4d
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
@ -455,3 +456,47 @@ func TestPersistentStorage(t *testing.T) {
|
||||
t.Errorf("DHCPOFFER for wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinimumLeaseTime(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
var addr = net.IP{192, 168, 42, 23}
|
||||
|
||||
for _, tt := range []struct {
|
||||
hwaddr net.HardwareAddr
|
||||
wantLeaseTime time.Duration
|
||||
}{
|
||||
{
|
||||
hwaddr: net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
|
||||
wantLeaseTime: 20 * time.Minute,
|
||||
},
|
||||
|
||||
{
|
||||
// Nintendo MAC address range for Nintendo Switch specific minimum
|
||||
// lease time quirk:
|
||||
hwaddr: net.HardwareAddr{0x7c, 0xbb, 0x8a, 0x11, 0x22, 0x33},
|
||||
wantLeaseTime: 1 * time.Hour,
|
||||
},
|
||||
} {
|
||||
p := discover(addr, tt.hwaddr)
|
||||
resp := handler.serveDHCP(p, dhcp4.Discover, p.ParseOptions())
|
||||
if resp == nil {
|
||||
t.Fatalf("DHCPDISCOVER(%v) = nil", tt.hwaddr)
|
||||
}
|
||||
if got, want := messageType(resp), dhcp4.Offer; got != want {
|
||||
t.Errorf("DHCPDISCOVER resulted in unexpected message type: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
opts := resp.ParseOptions()
|
||||
leaseTimeBytes, ok := opts[dhcp4.OptionIPAddressLeaseTime]
|
||||
if !ok {
|
||||
t.Fatalf("DHCPDISCOVER(%v): lease time option not set", tt.hwaddr)
|
||||
}
|
||||
leaseTimeSecs := binary.BigEndian.Uint32(leaseTimeBytes)
|
||||
want := uint32(tt.wantLeaseTime.Seconds())
|
||||
if got := leaseTimeSecs; got != want {
|
||||
t.Errorf("unexpected lease time for hwaddr %v: got %d, want %d", tt.hwaddr, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
internal/dhcp4d/quirks.go
Normal file
73
internal/dhcp4d/quirks.go
Normal file
@ -0,0 +1,73 @@
|
||||
package dhcp4d
|
||||
|
||||
// Sorted list of MAC address prefixes assigned to Nintendo.
|
||||
// From the IEEE MA-L (MAC Address Block Large, formerly known as OUI) database.
|
||||
var nintendoMacPrefixes = [...][3]byte{
|
||||
{0x0, 0x9, 0xbf},
|
||||
{0x0, 0x16, 0x56},
|
||||
{0x0, 0x17, 0xab},
|
||||
{0x0, 0x19, 0x1d},
|
||||
{0x0, 0x19, 0xfd},
|
||||
{0x0, 0x1a, 0xe9},
|
||||
{0x0, 0x1b, 0x7a},
|
||||
{0x0, 0x1b, 0xea},
|
||||
{0x0, 0x1c, 0xbe},
|
||||
{0x0, 0x1d, 0xbc},
|
||||
{0x0, 0x1e, 0x35},
|
||||
{0x0, 0x1e, 0xa9},
|
||||
{0x0, 0x1f, 0x32},
|
||||
{0x0, 0x1f, 0xc5},
|
||||
{0x0, 0x21, 0x47},
|
||||
{0x0, 0x21, 0xbd},
|
||||
{0x0, 0x22, 0x4c},
|
||||
{0x0, 0x22, 0xaa},
|
||||
{0x0, 0x22, 0xd7},
|
||||
{0x0, 0x23, 0x31},
|
||||
{0x0, 0x23, 0xcc},
|
||||
{0x0, 0x24, 0x1e},
|
||||
{0x0, 0x24, 0x44},
|
||||
{0x0, 0x24, 0xf3},
|
||||
{0x0, 0x25, 0xa0},
|
||||
{0x0, 0x26, 0x59},
|
||||
{0x0, 0x27, 0x9},
|
||||
{0x4, 0x3, 0xd6},
|
||||
{0x18, 0x2a, 0x7b},
|
||||
{0x2c, 0x10, 0xc1},
|
||||
{0x34, 0x2f, 0xbd},
|
||||
{0x34, 0xaf, 0x2c},
|
||||
{0x40, 0xd2, 0x8a},
|
||||
{0x40, 0xf4, 0x7},
|
||||
{0x48, 0xa5, 0xe7},
|
||||
{0x58, 0x2f, 0x40},
|
||||
{0x58, 0xbd, 0xa3},
|
||||
{0x5c, 0x52, 0x1e},
|
||||
{0x60, 0x6b, 0xff},
|
||||
{0x64, 0xb5, 0xc6},
|
||||
{0x70, 0x48, 0xf7},
|
||||
{0x78, 0xa2, 0xa0},
|
||||
{0x7c, 0xbb, 0x8a},
|
||||
{0x8c, 0x56, 0xc5},
|
||||
{0x8c, 0xcd, 0xe8},
|
||||
{0x94, 0x58, 0xcb},
|
||||
{0x98, 0x41, 0x5c},
|
||||
{0x98, 0xb6, 0xe9},
|
||||
{0x98, 0xe8, 0xfa},
|
||||
{0x9c, 0xe6, 0x35},
|
||||
{0xa4, 0x38, 0xcc},
|
||||
{0xa4, 0x5c, 0x27},
|
||||
{0xa4, 0xc0, 0xe1},
|
||||
{0xb8, 0x78, 0x26},
|
||||
{0xb8, 0x8a, 0xec},
|
||||
{0xb8, 0xae, 0x6e},
|
||||
{0xcc, 0x9e, 0x0},
|
||||
{0xcc, 0xfb, 0x65},
|
||||
{0xd4, 0xf0, 0x57},
|
||||
{0xd8, 0x6b, 0xf7},
|
||||
{0xdc, 0x68, 0xeb},
|
||||
{0xe0, 0xc, 0x7f},
|
||||
{0xe0, 0xe7, 0x51},
|
||||
{0xe0, 0xf6, 0xb5},
|
||||
{0xe8, 0x4e, 0xce},
|
||||
{0xe8, 0xda, 0x20},
|
||||
{0xec, 0xc4, 0xd},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user