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/aep"
|
||||
"gitea.drjosh.dev/josh/jrouter/atalk/nbp"
|
||||
"gitea.drjosh.dev/josh/jrouter/atalk/zip"
|
||||
"gitea.drjosh.dev/josh/jrouter/aurp"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"github.com/sfiera/multitalk/pkg/ddp"
|
||||
|
@ -267,9 +268,10 @@ func main() {
|
|||
case 1: // The RTMP socket
|
||||
rtmpCh <- ddpkt
|
||||
|
||||
case 2: // The NIS (NBP socket)
|
||||
case 2: // The NIS (name information socket / NBP socket)
|
||||
if ddpkt.Proto != ddp.ProtoNBP {
|
||||
log.Printf("NBP: invalid DDP type %d on socket 2", ddpkt.Proto)
|
||||
continue
|
||||
}
|
||||
|
||||
nbpkt, err := nbp.Unmarshal(ddpkt.Data)
|
||||
|
@ -292,6 +294,72 @@ func main() {
|
|||
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:
|
||||
log.Printf("DDP: No handler for socket %d", ddpkt.DstSocket)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue