Fix ZIP ATP replies

This commit is contained in:
Josh Deprez 2024-04-19 12:21:33 +10:00
parent e95df3db20
commit c1efd9f91b
No known key found for this signature in database
5 changed files with 56 additions and 7 deletions

View file

@ -22,8 +22,9 @@ Things I plan to fix Real Soon Now:
interface. I haven't tested it with netatalk or tashrouter on the same interface. I haven't tested it with netatalk or tashrouter on the same
host, but I think using a distinct Ethernet address would help them coexist. host, but I think using a distinct Ethernet address would help them coexist.
* It doesn't do any of the required packet splitting to keep packets under the * It doesn't do any of the required packet splitting to keep packets under the
AppleTalk size limits. In particular ZIP GetZoneList Replies are incorrect AppleTalk size limits. ~~In particular ZIP GetZoneList Replies are incorrect
when the zone list would exceed the limit. when the zone list would exceed the limit.~~ GetZoneList is now fixed, but
various others need fixing.
* It logs a lot and has no other monitoring or observability capability. I plan * It logs a lot and has no other monitoring or observability capability. I plan
to add a Prometheus metrics endpoint and at least add log levels / verbosity to add a Prometheus metrics endpoint and at least add log levels / verbosity
flag. flag.

View file

@ -20,6 +20,13 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"gitea.drjosh.dev/josh/jrouter/atalk"
)
const (
HeaderSize = 8
MaxDataSize = atalk.DDPMaxDataSize - HeaderSize // 578
) )
const ( const (
@ -50,6 +57,9 @@ func (p *TReq) Marshal() ([]byte, error) {
if p.TRelTimeoutIndicator > 4 { if p.TRelTimeoutIndicator > 4 {
return nil, fmt.Errorf("invalid TRel timeout indicator [%d > 4]", p.TRelTimeoutIndicator) return nil, fmt.Errorf("invalid TRel timeout indicator [%d > 4]", p.TRelTimeoutIndicator)
} }
if len(p.Data) > MaxDataSize {
return nil, fmt.Errorf("%w [%d > %d]", atalk.ErrDataTooBig, len(p.Data), MaxDataSize)
}
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
ci := byte(ciFuncTReq) ci := byte(ciFuncTReq)

View file

@ -16,4 +16,11 @@
package atalk package atalk
const DDPExtHeaderSize = 13 import "errors"
const (
DDPExtHeaderSize = 13
DDPMaxDataSize = 586
)
var ErrDataTooBig = errors.New("data too big")

View file

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"gitea.drjosh.dev/josh/jrouter/atalk"
"gitea.drjosh.dev/josh/jrouter/atalk/atp" "gitea.drjosh.dev/josh/jrouter/atalk/atp"
) )
@ -78,6 +79,9 @@ func (p *GetZonesReplyPacket) MarshalTResp() (*atp.TResp, error) {
b.WriteString(z) b.WriteString(z)
} }
r.Data = b.Bytes() r.Data = b.Bytes()
if len(r.Data) > atp.MaxDataSize {
return nil, fmt.Errorf("%w [%d > %d]", atalk.ErrDataTooBig, len(r.Data), atp.MaxDataSize)
}
return r, nil return r, nil
} }

35
zip.go
View file

@ -32,7 +32,7 @@ import (
func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, zones *ZoneTable, ddpkt *ddp.ExtPacket) error { func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAddr aarp.AddrPair, cfg *config, zones *ZoneTable, ddpkt *ddp.ExtPacket) error {
switch ddpkt.Proto { switch ddpkt.Proto {
case 3: // ATP case ddp.ProtoATP:
atpkt, err := atp.UnmarshalPacket(ddpkt.Data) atpkt, err := atp.UnmarshalPacket(ddpkt.Data)
if err != nil { if err != nil {
return err return err
@ -43,11 +43,15 @@ func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAdd
if err != nil { if err != nil {
return err return err
} }
// TODO: handle this in a more transactiony way if gzl.StartIndex == 0 {
return fmt.Errorf("ZIP ATP: received request with StartIndex = 0 (invalid)")
}
resp := &zip.GetZonesReplyPacket{ resp := &zip.GetZonesReplyPacket{
TID: gzl.TID, TID: gzl.TID,
LastFlag: true, // TODO: support multiple response packets LastFlag: true,
} }
switch gzl.Function { switch gzl.Function {
case zip.FunctionGetZoneList: case zip.FunctionGetZoneList:
resp.Zones = zones.AllNames() resp.Zones = zones.AllNames()
@ -59,6 +63,29 @@ func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAdd
resp.Zones = []string{cfg.EtherTalk.ZoneName} resp.Zones = []string{cfg.EtherTalk.ZoneName}
} }
// Inside AppleTalk SE, pp 8-8
if int(gzl.StartIndex) > len(resp.Zones) {
// "Note: A 0-byte response will be returned by a router if the
// index specified in the request is greater than the index of
// the last zone in the list (and the user bytes field will
// indicate no more zones)."
resp.Zones = nil
} else {
// Trim the zones list
// "zone names in the router are assumed to be numbered starting
// with 1"
resp.Zones = resp.Zones[gzl.StartIndex-1:]
size := 0
for i, z := range resp.Zones {
size += 1 + len(z) // length prefix plus string
if size > atp.MaxDataSize {
resp.LastFlag = false
resp.Zones = resp.Zones[:i]
break
}
}
}
respATP, err := resp.MarshalTResp() respATP, err := resp.MarshalTResp()
if err != nil { if err != nil {
return err return err
@ -99,7 +126,7 @@ func handleZIP(pcapHandle *pcap.Handle, srcHWAddr, myHWAddr ethernet.Addr, myAdd
return fmt.Errorf("unsupported ATP packet type %T for ZIP", atpkt) return fmt.Errorf("unsupported ATP packet type %T for ZIP", atpkt)
} }
case 6: // ZIP case ddp.ProtoZIP:
zipkt, err := zip.UnmarshalPacket(ddpkt.Data) zipkt, err := zip.UnmarshalPacket(ddpkt.Data)
if err != nil { if err != nil {
return err return err