Start implementing ZIP
This commit is contained in:
parent
e20eb9be2d
commit
40867c23ea
4 changed files with 278 additions and 1 deletions
107
atalk/zip/getnetinfo.go
Normal file
107
atalk/zip/getnetinfo.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 Josh Deprez
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
"github.com/sfiera/multitalk/pkg/ethernet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetNetInfoPacket struct {
|
||||||
|
// Destination socket = 6
|
||||||
|
// DDP type = 6
|
||||||
|
// ---
|
||||||
|
// ZIP command = 5
|
||||||
|
// Flags = 0 (reserved)
|
||||||
|
// Four more bytes of 0 (reserved)
|
||||||
|
// Zone name length (1 byte)
|
||||||
|
ZoneName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalGetNetInfoPacket(data []byte) (*GetNetInfoPacket, error) {
|
||||||
|
if len(data) < 7 {
|
||||||
|
return nil, fmt.Errorf("insufficient input length %d for GetNetInfo packet", len(data))
|
||||||
|
}
|
||||||
|
if data[0] != FunctionGetNetInfo {
|
||||||
|
return nil, fmt.Errorf("not a GetNetInfo packet (ZIP command %d != %d)", data[0], FunctionGetNetInfo)
|
||||||
|
}
|
||||||
|
slen := data[6]
|
||||||
|
data = data[7:]
|
||||||
|
if len(data) != int(slen) {
|
||||||
|
return nil, fmt.Errorf("wrong remaining input length %d for length=%d-prefixed string", len(data), slen)
|
||||||
|
}
|
||||||
|
return &GetNetInfoPacket{
|
||||||
|
ZoneName: string(data),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNetInfoReplyPacket struct {
|
||||||
|
// Source socket = 6
|
||||||
|
// DDP type = 6
|
||||||
|
// ---
|
||||||
|
// ZIP command = 6
|
||||||
|
ZoneInvalid bool // 0x80
|
||||||
|
UseBroadcast bool // 0x40
|
||||||
|
OnlyOneZone bool // 0x20
|
||||||
|
// Remainder of flags reserved
|
||||||
|
NetStart ddp.Network
|
||||||
|
NetEnd ddp.Network
|
||||||
|
// Zone name length (1 byte)
|
||||||
|
ZoneName string
|
||||||
|
// Multicast address length (1 byte)
|
||||||
|
MulticastAddr ethernet.Addr
|
||||||
|
// Only if ZoneInvalid flag is set:
|
||||||
|
// Default zone length (1 byte)
|
||||||
|
DefaultZoneName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GetNetInfoReplyPacket) Marshal() ([]byte, error) {
|
||||||
|
if len(p.ZoneName) > 32 {
|
||||||
|
return nil, fmt.Errorf("zone name too long [%d > 32]", len(p.ZoneName))
|
||||||
|
}
|
||||||
|
if len(p.DefaultZoneName) > 32 {
|
||||||
|
return nil, fmt.Errorf("default zone name too long [%d > 32]", len(p.DefaultZoneName))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
b.WriteByte(FunctionGetNetInfoReply)
|
||||||
|
var flags byte
|
||||||
|
if p.ZoneInvalid {
|
||||||
|
flags |= 0x80
|
||||||
|
}
|
||||||
|
if p.UseBroadcast {
|
||||||
|
flags |= 0x40
|
||||||
|
}
|
||||||
|
if p.OnlyOneZone {
|
||||||
|
flags |= 0x20
|
||||||
|
}
|
||||||
|
b.WriteByte(flags)
|
||||||
|
write16(b, p.NetStart)
|
||||||
|
write16(b, p.NetEnd)
|
||||||
|
b.WriteByte(byte(len(p.ZoneName)))
|
||||||
|
b.WriteString(p.ZoneName)
|
||||||
|
b.WriteByte(6)
|
||||||
|
b.Write(p.MulticastAddr[:])
|
||||||
|
if p.ZoneInvalid {
|
||||||
|
b.WriteByte(byte(len(p.DefaultZoneName)))
|
||||||
|
b.WriteString(p.DefaultZoneName)
|
||||||
|
}
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
36
atalk/zip/query_reply.go
Normal file
36
atalk/zip/query_reply.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 Josh Deprez
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zip
|
||||||
|
|
||||||
|
import "github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
|
||||||
|
type QueryPacket struct {
|
||||||
|
Function Function // 1
|
||||||
|
// NetworkCount uint8
|
||||||
|
Networks []ddp.Network
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyPacket struct {
|
||||||
|
Function Function // 2 or 8
|
||||||
|
// NetworkCount uint8
|
||||||
|
Tuples []ZoneTuple
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZoneTuple struct {
|
||||||
|
Network ddp.Network
|
||||||
|
ZoneName string
|
||||||
|
}
|
66
atalk/zip/zip.go
Normal file
66
atalk/zip/zip.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 Josh Deprez
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Function uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
FunctionQuery = 1
|
||||||
|
FunctionReply = 2
|
||||||
|
FunctionGetNetInfo = 5
|
||||||
|
FunctionGetNetInfoReply = 6
|
||||||
|
FunctionNotify = 7
|
||||||
|
FunctionExtendedReply = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Non-ATP packets only
|
||||||
|
func UnmarshalPacket(data []byte) (any, error) {
|
||||||
|
if len(data) < 1 {
|
||||||
|
return nil, fmt.Errorf("insufficient input length %d for any ZIP packet", len(data))
|
||||||
|
}
|
||||||
|
switch data[0] {
|
||||||
|
case FunctionQuery:
|
||||||
|
return nil, fmt.Errorf("ZIP Query unmarshaling unimplemented")
|
||||||
|
|
||||||
|
case FunctionReply:
|
||||||
|
return nil, fmt.Errorf("ZIP Reply unmarshaling unimplemented")
|
||||||
|
|
||||||
|
case FunctionExtendedReply:
|
||||||
|
return nil, fmt.Errorf("ZIP Extended Reply unmarshaling unimplemented")
|
||||||
|
|
||||||
|
case FunctionGetNetInfo:
|
||||||
|
return UnmarshalGetNetInfoPacket(data)
|
||||||
|
|
||||||
|
case FunctionGetNetInfoReply:
|
||||||
|
return nil, fmt.Errorf("ZIP GetNetInfo Reply unmarshaling unimplemented")
|
||||||
|
|
||||||
|
case FunctionNotify:
|
||||||
|
return nil, fmt.Errorf("ZIP Notify unmarshaling unimplemented")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown ZIP function %d", data[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write16[I ~uint16](b *bytes.Buffer, n I) {
|
||||||
|
b.Write([]byte{byte(n >> 8), byte(n & 0xff)})
|
||||||
|
}
|
70
main.go
70
main.go
|
@ -34,6 +34,7 @@ import (
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk"
|
"gitea.drjosh.dev/josh/jrouter/atalk"
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk/aep"
|
"gitea.drjosh.dev/josh/jrouter/atalk/aep"
|
||||||
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
||||||
|
"gitea.drjosh.dev/josh/jrouter/atalk/zip"
|
||||||
"gitea.drjosh.dev/josh/jrouter/aurp"
|
"gitea.drjosh.dev/josh/jrouter/aurp"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
"github.com/sfiera/multitalk/pkg/ddp"
|
"github.com/sfiera/multitalk/pkg/ddp"
|
||||||
|
@ -267,9 +268,10 @@ func main() {
|
||||||
case 1: // The RTMP socket
|
case 1: // The RTMP socket
|
||||||
rtmpCh <- ddpkt
|
rtmpCh <- ddpkt
|
||||||
|
|
||||||
case 2: // The NIS (NBP socket)
|
case 2: // The NIS (name information socket / NBP socket)
|
||||||
if ddpkt.Proto != ddp.ProtoNBP {
|
if ddpkt.Proto != ddp.ProtoNBP {
|
||||||
log.Printf("NBP: invalid DDP type %d on socket 2", ddpkt.Proto)
|
log.Printf("NBP: invalid DDP type %d on socket 2", ddpkt.Proto)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
||||||
|
@ -292,6 +294,72 @@ func main() {
|
||||||
log.Printf("AEP: Couldn't handle: %v", err)
|
log.Printf("AEP: Couldn't handle: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 6: // The ZIS (zone information socket / ZIP socket)
|
||||||
|
switch ddpkt.Proto {
|
||||||
|
case 3: // ATP
|
||||||
|
log.Print("ZIP: TODO implement ATP-based ZIP requests")
|
||||||
|
continue
|
||||||
|
|
||||||
|
case 6: // ZIP
|
||||||
|
zipkt, err := zip.UnmarshalPacket(ddpkt.Data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ZIP: invalid packet: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch zipkt := zipkt.(type) {
|
||||||
|
case *zip.GetNetInfoPacket:
|
||||||
|
// Only running a network with one zone for now.
|
||||||
|
resp := &zip.GetNetInfoReplyPacket{
|
||||||
|
ZoneInvalid: zipkt.ZoneName != cfg.EtherTalk.ZoneName,
|
||||||
|
UseBroadcast: true, // TODO: add multicast addr computation
|
||||||
|
OnlyOneZone: true,
|
||||||
|
NetStart: cfg.EtherTalk.NetStart,
|
||||||
|
NetEnd: cfg.EtherTalk.NetEnd,
|
||||||
|
ZoneName: zipkt.ZoneName, // has to match request
|
||||||
|
MulticastAddr: ethertalk.AppleTalkBroadcast,
|
||||||
|
DefaultZoneName: cfg.EtherTalk.ZoneName,
|
||||||
|
}
|
||||||
|
respRaw, err := resp.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ZIP: couldn't marshal GetNetInfoReplyPacket: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fix
|
||||||
|
// "In cases where a node's provisional address is
|
||||||
|
// invalid, routers will not be able to respond to
|
||||||
|
// the node in a directed manner. An address is
|
||||||
|
// invalid if the network number is neither in the
|
||||||
|
// startup range nor in the network number range
|
||||||
|
// assigned to the node's network. In these cases,
|
||||||
|
// if the request was sent via a broadcast, the
|
||||||
|
// routers should respond with a broadcast."
|
||||||
|
ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket = 0x0000, 0xFF, ddpkt.SrcSocket
|
||||||
|
ddpkt.SrcNet = myAddr.Proto.Network
|
||||||
|
ddpkt.SrcNode = myAddr.Proto.Node
|
||||||
|
ddpkt.SrcSocket = 6
|
||||||
|
ddpkt.Data = respRaw
|
||||||
|
outFrame, err := ethertalk.AppleTalk(myHWAddr, *ddpkt)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ZIP: couldn't create EtherTalk frame: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
outFrame.Dst = ethFrame.Src
|
||||||
|
outFrameRaw, err := ethertalk.Marshal(*outFrame)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ZIP: couldn't marshal EtherTalk frame: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := pcapHandle.WritePacketData(outFrameRaw); err != nil {
|
||||||
|
log.Printf("ZIP: couldn't write packet data: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("ZIP: invalid DDP type %d on socket 6", ddpkt.Proto)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Printf("DDP: No handler for socket %d", ddpkt.DstSocket)
|
log.Printf("DDP: No handler for socket %d", ddpkt.DstSocket)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue