AMT in status page

This commit is contained in:
Josh Deprez 2024-04-26 11:51:27 +10:00
parent 15621ec7e0
commit 96ab3f9905
No known key found for this signature in database

View file

@ -39,6 +39,28 @@ const (
aarpRequestTimeout = 10 * time.Second aarpRequestTimeout = 10 * time.Second
) )
const aarpStatusTemplate = `
Status: {{.Status}}<br/>
<table>
<thead><tr>
<th>DDP addr</th>
<th>Ethernet addr</th>
<th>Last updated</th>
<th>Being resolved?</th>
</tr></thead>
<tbody>
{{range $key, $entry := .AMT}}
<tr>
<td>{{$key.Network}}.{{$key.Node}}</td>
<td>{{$entry.HWAddr}}</td>
<td>{{$entry.LastUpdated}}</td>
<td>{{if $entry.Resolving}}Resolving...{{else}}Resolved{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
`
// AARPMachine maintains both an Address Mapping Table and handles AARP packets // AARPMachine maintains both an Address Mapping Table and handles AARP packets
// (sending and receiving requests, responses, and probes). This process assumes // (sending and receiving requests, responses, and probes). This process assumes
// a particular network range rather than using the startup range, since this // a particular network range rather than using the startup range, since this
@ -53,6 +75,7 @@ type AARPMachine struct {
// probes, so this mutex is not used to enforce a single writer, only // probes, so this mutex is not used to enforce a single writer, only
// consistent reads // consistent reads
mu sync.RWMutex mu sync.RWMutex
statusMsg string
myAddr aarp.AddrPair myAddr aarp.AddrPair
probes int probes int
assigned bool assigned bool
@ -85,15 +108,26 @@ func (a *AARPMachine) Assigned() <-chan struct{} {
return a.assignedCh return a.assignedCh
} }
func (a *AARPMachine) status(ctx context.Context) (any, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return struct {
Status string
AMT map[ddp.Addr]AMTEntry
}{
Status: a.statusMsg,
AMT: a.addressMappingTable.Dump(),
}, nil
}
// Run executes the machine. // Run executes the machine.
func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Packet) error { func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Packet) error {
ctx, setStatus, done := status.AddSimpleItem(ctx, "AARP") ctx, done := status.AddItem(ctx, "AARP", aarpStatusTemplate, a.status)
defer done() defer done()
setStatus("Initialising")
// Initialise our DDP address with a preferred address (first network.1) // Initialise our DDP address with a preferred address (first network.1)
a.mu.Lock() a.mu.Lock()
a.statusMsg = "Initialising"
a.probes = 0 a.probes = 0
a.myAddr.Proto = ddp.Addr{ a.myAddr.Proto = ddp.Addr{
Network: ddp.Network(a.cfg.EtherTalk.NetStart), Network: ddp.Network(a.cfg.EtherTalk.NetStart),
@ -112,16 +146,16 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
case <-ticker.C: case <-ticker.C:
if a.probes >= 10 { if a.probes >= 10 {
a.mu.Lock() a.mu.Lock()
a.statusMsg = fmt.Sprintf("Assigned address %d.%d", a.myAddr.Proto.Network, a.myAddr.Proto.Node)
a.assigned = true a.assigned = true
a.mu.Unlock() a.mu.Unlock()
close(a.assignedCh) close(a.assignedCh)
ticker.Stop() ticker.Stop()
setStatus(fmt.Sprintf("Assigned address %d.%d", a.myAddr.Proto.Network, a.myAddr.Proto.Node))
continue continue
} }
a.mu.Lock() a.mu.Lock()
a.statusMsg = fmt.Sprintf("Probed %d times", a.probes)
a.probes++ a.probes++
a.mu.Unlock() a.mu.Unlock()
@ -129,8 +163,6 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
log.Printf("Couldn't broadcast a Probe: %v", err) log.Printf("Couldn't broadcast a Probe: %v", err)
} }
setStatus(fmt.Sprintf("Probed %d times", a.probes))
case ethFrame, ok := <-incomingCh: case ethFrame, ok := <-incomingCh:
if !ok { if !ok {
incomingCh = nil incomingCh = nil
@ -310,18 +342,38 @@ func (a *AARPMachine) request(ddpAddr ddp.Addr) error {
return a.pcapHandle.WritePacketData(reqFrameRaw) return a.pcapHandle.WritePacketData(reqFrameRaw)
} }
type amtEntry struct { // AMTEntry is an entry in an address mapping table.
hwAddr ethernet.Addr type AMTEntry struct {
last time.Time // The hardware address that the entry maps to.
HWAddr ethernet.Addr
// The last time this entry was updated.
LastUpdated time.Time
// Whether the address is being resolved.
Resolving bool
// Closed when this entry is updated.
updated chan struct{} updated chan struct{}
requesting bool
} }
// addressMappingTable implements a concurrent-safe Address Mapping Table for // addressMappingTable implements a concurrent-safe Address Mapping Table for
// AppleTalk (DDP) addresses to Ethernet hardware addresses. // AppleTalk (DDP) addresses to Ethernet hardware addresses.
type addressMappingTable struct { type addressMappingTable struct {
mu sync.Mutex mu sync.Mutex
table map[ddp.Addr]*amtEntry table map[ddp.Addr]*AMTEntry
}
// Dump returns a copy of the table at a point in time.
func (t *addressMappingTable) Dump() map[ddp.Addr]AMTEntry {
t.mu.Lock()
defer t.mu.Unlock()
table := make(map[ddp.Addr]AMTEntry, len(t.table))
for k, v := range t.table {
table[k] = *v
}
return table
} }
// Learn adds or updates an AMT entry. // Learn adds or updates an AMT entry.
@ -329,22 +381,22 @@ func (t *addressMappingTable) Learn(ddpAddr ddp.Addr, hwAddr ethernet.Addr) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
if t.table == nil { if t.table == nil {
t.table = make(map[ddp.Addr]*amtEntry) t.table = make(map[ddp.Addr]*AMTEntry)
} }
oldEnt := t.table[ddpAddr] oldEnt := t.table[ddpAddr]
if oldEnt == nil { if oldEnt == nil {
t.table[ddpAddr] = &amtEntry{ t.table[ddpAddr] = &AMTEntry{
hwAddr: hwAddr, HWAddr: hwAddr,
last: time.Now(), LastUpdated: time.Now(),
updated: make(chan struct{}), updated: make(chan struct{}),
requesting: false, Resolving: false,
} }
return return
} }
oldEnt.hwAddr = hwAddr oldEnt.HWAddr = hwAddr
oldEnt.last = time.Now() oldEnt.LastUpdated = time.Now()
oldEnt.requesting = false oldEnt.Resolving = false
close(oldEnt.updated) close(oldEnt.updated)
oldEnt.updated = make(chan struct{}) oldEnt.updated = make(chan struct{})
} }
@ -356,25 +408,25 @@ func (t *addressMappingTable) lookupOrWait(ddpAddr ddp.Addr) (ethernet.Addr, <-c
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
if t.table == nil { if t.table == nil {
t.table = make(map[ddp.Addr]*amtEntry) t.table = make(map[ddp.Addr]*AMTEntry)
} }
ent := t.table[ddpAddr] ent := t.table[ddpAddr]
if ent == nil { if ent == nil {
ch := make(chan struct{}) ch := make(chan struct{})
t.table[ddpAddr] = &amtEntry{ t.table[ddpAddr] = &AMTEntry{
updated: ch, updated: ch,
requesting: true, Resolving: true,
} }
return ethernet.Addr{}, ch, true return ethernet.Addr{}, ch, true
} }
if time.Since(ent.last) >= maxAMTEntryAge { if time.Since(ent.LastUpdated) >= maxAMTEntryAge {
if ent.requesting { if ent.Resolving {
return ent.hwAddr, ent.updated, false return ent.HWAddr, ent.updated, false
} }
ent.requesting = true ent.Resolving = true
return ent.hwAddr, ent.updated, true return ent.HWAddr, ent.updated, true
} }
return ent.hwAddr, nil, false return ent.HWAddr, nil, false
} }
func (t *addressMappingTable) requestingStopped(ddpAddr ddp.Addr) { func (t *addressMappingTable) requestingStopped(ddpAddr ddp.Addr) {
@ -387,5 +439,5 @@ func (t *addressMappingTable) requestingStopped(ddpAddr ddp.Addr) {
if ent == nil { if ent == nil {
return return
} }
ent.requesting = false ent.Resolving = false
} }