Finish implementing AARP
This commit is contained in:
parent
dd68ef97b6
commit
d990977165
3 changed files with 119 additions and 28 deletions
134
aarp.go
134
aarp.go
|
@ -14,8 +14,12 @@ import (
|
||||||
"github.com/sfiera/multitalk/pkg/ethertalk"
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: verify this parameter
|
const (
|
||||||
const maxAMTEntryAge = 30 * time.Second
|
// TODO: verify parameters
|
||||||
|
maxAMTEntryAge = 30 * time.Second
|
||||||
|
aarpRequestRetransmit = 1 * time.Second
|
||||||
|
aarpRequestTimeout = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -30,8 +34,7 @@ type AARPMachine struct {
|
||||||
state aarpState
|
state aarpState
|
||||||
probes int
|
probes int
|
||||||
|
|
||||||
myHWAddr ethernet.Addr
|
myAddr aarp.AddrPair
|
||||||
myDDPAddr ddp.Addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type aarpState int
|
type aarpState int
|
||||||
|
@ -49,7 +52,7 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
|
||||||
a.probes = 0
|
a.probes = 0
|
||||||
|
|
||||||
// Initialise our DDP address with a preferred address (first network.1)
|
// Initialise our DDP address with a preferred address (first network.1)
|
||||||
a.myDDPAddr = ddp.Addr{
|
a.myAddr.Proto = ddp.Addr{
|
||||||
Network: ddp.Network(a.cfg.EtherTalk.NetStart),
|
Network: ddp.Network(a.cfg.EtherTalk.NetStart),
|
||||||
Node: 1,
|
Node: 1,
|
||||||
}
|
}
|
||||||
|
@ -95,7 +98,7 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
|
||||||
a.AMT.Learn(aapkt.Src.Proto, aapkt.Src.Hardware)
|
a.AMT.Learn(aapkt.Src.Proto, aapkt.Src.Hardware)
|
||||||
log.Printf("AARP: Gleaned that %v -> %v", aapkt.Src.Proto, aapkt.Src.Hardware)
|
log.Printf("AARP: Gleaned that %v -> %v", aapkt.Src.Proto, aapkt.Src.Hardware)
|
||||||
|
|
||||||
if aapkt.Dst.Proto != a.myDDPAddr {
|
if aapkt.Dst.Proto != a.myAddr.Proto {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if a.state != aarpStateAssigned {
|
if a.state != aarpStateAssigned {
|
||||||
|
@ -111,7 +114,7 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
|
||||||
log.Printf("AARP: %v is at %v", aapkt.Dst.Proto, aapkt.Dst.Hardware)
|
log.Printf("AARP: %v is at %v", aapkt.Dst.Proto, aapkt.Dst.Hardware)
|
||||||
a.AMT.Learn(aapkt.Dst.Proto, aapkt.Dst.Hardware)
|
a.AMT.Learn(aapkt.Dst.Proto, aapkt.Dst.Hardware)
|
||||||
|
|
||||||
if aapkt.Dst.Proto != a.myDDPAddr {
|
if aapkt.Dst.Proto != a.myAddr.Proto {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if a.state == aarpStateProbing {
|
if a.state == aarpStateProbing {
|
||||||
|
@ -122,7 +125,7 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
|
||||||
log.Printf("AARP: %v probing to see if %v is available", aapkt.Src.Hardware, aapkt.Src.Proto)
|
log.Printf("AARP: %v probing to see if %v is available", aapkt.Src.Hardware, aapkt.Src.Proto)
|
||||||
// AMT should not be updated, because the address is tentative
|
// AMT should not be updated, because the address is tentative
|
||||||
|
|
||||||
if aapkt.Dst.Proto != a.myDDPAddr {
|
if aapkt.Dst.Proto != a.myAddr.Proto {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch a.state {
|
switch a.state {
|
||||||
|
@ -142,30 +145,69 @@ func (a *AARPMachine) Run(ctx context.Context, incomingCh <-chan *ethertalk.Pack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve resolves an AppleTalk node address to an Ethernet address.
|
||||||
|
// If the address is in the cache (AMT) and is still valid, that is used.
|
||||||
|
// Otherwise, the address is resolved using AARP.
|
||||||
|
func (a *AARPMachine) Resolve(ctx context.Context, ddpAddr ddp.Addr) (ethernet.Addr, error) {
|
||||||
|
// try the cache first
|
||||||
|
result, ok := a.AMT.Lookup(ddpAddr)
|
||||||
|
if ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.request(ddpAddr); err != nil {
|
||||||
|
return ethernet.Addr{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(aarpRequestRetransmit)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, aarpRequestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// We might have a result already
|
||||||
|
result, ok := a.AMT.Lookup(ddpAddr)
|
||||||
|
if ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ethernet.Addr{}, ctx.Err()
|
||||||
|
|
||||||
|
case <-a.AMT.Wait(ddpAddr):
|
||||||
|
// Should have a result now.
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := a.request(ddpAddr); err != nil {
|
||||||
|
return ethernet.Addr{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Re-roll a local address
|
// Re-roll a local address
|
||||||
func (a *AARPMachine) reroll() {
|
func (a *AARPMachine) reroll() {
|
||||||
if a.cfg.EtherTalk.NetStart != a.cfg.EtherTalk.NetEnd {
|
if a.cfg.EtherTalk.NetStart != a.cfg.EtherTalk.NetEnd {
|
||||||
// Pick a new network number at random
|
// Pick a new network number at random
|
||||||
a.myDDPAddr.Network = rand.N[ddp.Network](
|
a.myAddr.Proto.Network = rand.N[ddp.Network](
|
||||||
a.cfg.EtherTalk.NetEnd-a.cfg.EtherTalk.NetStart+1,
|
a.cfg.EtherTalk.NetEnd-a.cfg.EtherTalk.NetStart+1,
|
||||||
) + a.cfg.EtherTalk.NetStart
|
) + a.cfg.EtherTalk.NetStart
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't use: 0x00, 0xff, 0xfe, or the existing node number
|
// Can't use: 0x00, 0xff, 0xfe, or the existing node number
|
||||||
newNode := rand.N[ddp.Node](0xfd) + 1
|
newNode := rand.N[ddp.Node](0xfd) + 1
|
||||||
for newNode != a.myDDPAddr.Node {
|
for newNode != a.myAddr.Proto.Node {
|
||||||
newNode = rand.N[ddp.Node](0xfd) + 1
|
newNode = rand.N[ddp.Node](0xfd) + 1
|
||||||
}
|
}
|
||||||
a.myDDPAddr.Node = newNode
|
a.myAddr.Proto.Node = newNode
|
||||||
a.probes = 0
|
a.probes = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an AARP response
|
// Send an AARP response
|
||||||
func (a *AARPMachine) heyThatsMe(targ aarp.AddrPair) error {
|
func (a *AARPMachine) heyThatsMe(targ aarp.AddrPair) error {
|
||||||
respFrame, err := ethertalk.AARP(a.myHWAddr, aarp.Response(targ, aarp.AddrPair{
|
respFrame, err := ethertalk.AARP(a.myAddr.Hardware, aarp.Response(targ, a.myAddr))
|
||||||
Proto: a.myDDPAddr,
|
|
||||||
Hardware: a.myHWAddr,
|
|
||||||
}))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -180,7 +222,7 @@ func (a *AARPMachine) heyThatsMe(targ aarp.AddrPair) error {
|
||||||
|
|
||||||
// Broadcast an AARP Probe
|
// Broadcast an AARP Probe
|
||||||
func (a *AARPMachine) probe() error {
|
func (a *AARPMachine) probe() error {
|
||||||
probeFrame, err := ethertalk.AARP(a.myHWAddr, aarp.Probe(a.myHWAddr, a.myDDPAddr))
|
probeFrame, err := ethertalk.AARP(a.myAddr.Hardware, aarp.Probe(a.myAddr.Hardware, a.myAddr.Proto))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -191,16 +233,30 @@ func (a *AARPMachine) probe() error {
|
||||||
return a.pcapHandle.WritePacketData(probeFrameRaw)
|
return a.pcapHandle.WritePacketData(probeFrameRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcast an AARP Request
|
||||||
|
func (a AARPMachine) request(ddpAddr ddp.Addr) error {
|
||||||
|
reqFrame, err := ethertalk.AARP(a.myAddr.Hardware, aarp.Request(a.myAddr, ddpAddr))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqFrameRaw, err := ethertalk.Marshal(*reqFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.pcapHandle.WritePacketData(reqFrameRaw)
|
||||||
|
}
|
||||||
|
|
||||||
type amtEntry struct {
|
type amtEntry struct {
|
||||||
hwAddr ethernet.Addr
|
hwAddr ethernet.Addr
|
||||||
last time.Time
|
last time.Time
|
||||||
|
updated chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AMT implements a concurrent-safe Address Mapping Table for AppleTalk (DDP)
|
// AMT implements a concurrent-safe Address Mapping Table for AppleTalk (DDP)
|
||||||
// addresses to Ethernet hardware addresses.
|
// addresses to Ethernet hardware addresses.
|
||||||
type AMT struct {
|
type AMT struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
table map[ddp.Addr]amtEntry
|
table map[ddp.Addr]*amtEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Learn adds or updates an AMT entry.
|
// Learn adds or updates an AMT entry.
|
||||||
|
@ -208,12 +264,44 @@ func (t *AMT) 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)
|
||||||
}
|
}
|
||||||
t.table[ddpAddr] = amtEntry{
|
oldEnt := t.table[ddpAddr]
|
||||||
hwAddr: hwAddr,
|
if oldEnt == nil {
|
||||||
last: time.Now(),
|
t.table[ddpAddr] = &amtEntry{
|
||||||
|
hwAddr: hwAddr,
|
||||||
|
last: time.Now(),
|
||||||
|
updated: make(chan struct{}),
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if oldEnt.hwAddr == hwAddr && time.Since(oldEnt.last) < maxAMTEntryAge {
|
||||||
|
oldEnt.last = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldEnt.hwAddr = hwAddr
|
||||||
|
oldEnt.last = time.Now()
|
||||||
|
close(oldEnt.updated)
|
||||||
|
oldEnt.updated = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait returns a channel that is closed when the entry for ddpAddr is updated.
|
||||||
|
func (t *AMT) Wait(ddpAddr ddp.Addr) <-chan struct{} {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
if t.table == nil {
|
||||||
|
t.table = make(map[ddp.Addr]*amtEntry)
|
||||||
|
}
|
||||||
|
oldEnt := t.table[ddpAddr]
|
||||||
|
if oldEnt != nil {
|
||||||
|
return oldEnt.updated
|
||||||
|
}
|
||||||
|
ch := make(chan struct{})
|
||||||
|
t.table[ddpAddr] = &amtEntry{
|
||||||
|
updated: ch,
|
||||||
|
}
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup searches for a non-expired entry in the table only. It does not send
|
// Lookup searches for a non-expired entry in the table only. It does not send
|
||||||
|
|
5
main.go
5
main.go
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk"
|
"gitea.drjosh.dev/josh/jrouter/atalk"
|
||||||
"gitea.drjosh.dev/josh/jrouter/aurp"
|
"gitea.drjosh.dev/josh/jrouter/aurp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/aarp"
|
||||||
"github.com/sfiera/multitalk/pkg/ddp"
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
"github.com/sfiera/multitalk/pkg/ethernet"
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
"github.com/sfiera/multitalk/pkg/ethertalk"
|
"github.com/sfiera/multitalk/pkg/ethertalk"
|
||||||
|
@ -154,7 +155,9 @@ func main() {
|
||||||
AMT: new(AMT),
|
AMT: new(AMT),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
pcapHandle: pcapHandle,
|
pcapHandle: pcapHandle,
|
||||||
myHWAddr: myHWAddr,
|
myAddr: aarp.AddrPair{
|
||||||
|
Hardware: myHWAddr,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
aarpCh := make(chan *ethertalk.Packet, 1024)
|
aarpCh := make(chan *ethertalk.Packet, 1024)
|
||||||
go aarpMachine.Run(ctx, aarpCh)
|
go aarpMachine.Run(ctx, aarpCh)
|
||||||
|
|
8
peer.go
8
peer.go
|
@ -256,8 +256,8 @@ func (p *peer) handle(ctx context.Context) error {
|
||||||
nets := aurp.NetworkTuples{
|
nets := aurp.NetworkTuples{
|
||||||
{
|
{
|
||||||
Extended: true,
|
Extended: true,
|
||||||
RangeStart: p.cfg.EtherTalk.NetStart,
|
RangeStart: uint16(p.cfg.EtherTalk.NetStart),
|
||||||
RangeEnd: p.cfg.EtherTalk.NetEnd,
|
RangeEnd: uint16(p.cfg.EtherTalk.NetEnd),
|
||||||
Distance: 0,
|
Distance: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,7 @@ func (p *peer) handle(ctx context.Context) error {
|
||||||
if pkt.Flags&aurp.RoutingFlagSendZoneInfo != 0 {
|
if pkt.Flags&aurp.RoutingFlagSendZoneInfo != 0 {
|
||||||
zones := aurp.ZoneTuples{
|
zones := aurp.ZoneTuples{
|
||||||
{
|
{
|
||||||
Network: p.cfg.EtherTalk.NetStart,
|
Network: uint16(p.cfg.EtherTalk.NetStart),
|
||||||
Name: p.cfg.EtherTalk.ZoneName,
|
Name: p.cfg.EtherTalk.ZoneName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ func (p *peer) handle(ctx context.Context) error {
|
||||||
// ZI-Req
|
// ZI-Req
|
||||||
zones := aurp.ZoneTuples{
|
zones := aurp.ZoneTuples{
|
||||||
{
|
{
|
||||||
Network: p.cfg.EtherTalk.NetStart,
|
Network: uint16(p.cfg.EtherTalk.NetStart),
|
||||||
Name: p.cfg.EtherTalk.ZoneName,
|
Name: p.cfg.EtherTalk.ZoneName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue