jrouter/rtmp.go

228 lines
6.2 KiB
Go
Raw Normal View History

2024-04-08 09:21:35 +10:00
/*
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.
*/
2024-04-07 15:14:54 +10:00
package main
import (
"context"
2024-04-07 17:01:26 +10:00
"fmt"
2024-04-07 15:14:54 +10:00
"log"
"time"
2024-04-15 10:16:57 +10:00
"gitea.drjosh.dev/josh/jrouter/atalk"
2024-04-07 15:14:54 +10:00
"gitea.drjosh.dev/josh/jrouter/atalk/rtmp"
"github.com/google/gopacket/pcap"
2024-04-07 17:04:30 +10:00
"github.com/sfiera/multitalk/pkg/aarp"
2024-04-07 15:14:54 +10:00
"github.com/sfiera/multitalk/pkg/ddp"
2024-04-07 16:31:19 +10:00
"github.com/sfiera/multitalk/pkg/ethernet"
2024-04-07 15:14:54 +10:00
"github.com/sfiera/multitalk/pkg/ethertalk"
)
// RTMPMachine implements RTMP on an AppleTalk network attached to the router.
type RTMPMachine struct {
2024-04-14 13:44:35 +10:00
aarp *AARPMachine
cfg *config
pcapHandle *pcap.Handle
2024-04-14 17:13:12 +10:00
routingTable *RoutingTable
2024-04-07 15:14:54 +10:00
}
2024-04-07 17:01:26 +10:00
// Run executes the machine.
2024-04-07 15:41:27 +10:00
func (m *RTMPMachine) Run(ctx context.Context, incomingCh <-chan *ddp.ExtPacket) error {
2024-04-07 17:01:26 +10:00
// Await local address assignment before doing anything
<-m.aarp.Assigned()
myAddr, ok := m.aarp.Address()
if !ok {
return fmt.Errorf("AARP machine closed Assigned channel but Address is not valid")
}
2024-04-07 17:04:30 +10:00
// Initial broadcast
if err := m.broadcastData(myAddr); err != nil {
log.Printf("RTMP: Couldn't broadcast Data: %v", err)
}
2024-04-07 15:14:54 +10:00
bcastTicker := time.NewTicker(10 * time.Second)
defer bcastTicker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-bcastTicker.C:
2024-04-07 17:04:30 +10:00
if err := m.broadcastData(myAddr); err != nil {
log.Printf("RTMP: Couldn't broadcast Data: %v", err)
2024-04-07 15:14:54 +10:00
}
2024-04-07 15:41:27 +10:00
case pkt := <-incomingCh:
switch pkt.Proto {
case ddp.ProtoRTMPReq:
// I can answer RTMP requests!
req, err := rtmp.UnmarshalRequestPacket(pkt.Data)
if err != nil {
log.Printf("RTMP: Couldn't unmarshal Request packet: %v", err)
}
2024-04-07 16:31:19 +10:00
// should be in the cache...
theirHWAddr, err := m.aarp.Resolve(ctx, ddp.Addr{Network: pkt.SrcNet, Node: pkt.SrcNode})
if err != nil {
log.Printf("RTMP: Couldn't resolve %d.%d to a hardware address: %v", pkt.SrcNet, pkt.SrcNode, err)
continue
}
2024-04-07 15:41:27 +10:00
2024-04-07 16:31:19 +10:00
switch req.Function {
case 1: // RTMP Request
// Respond with RTMP Response
respPkt := &rtmp.ResponsePacket{
SenderAddr: myAddr.Proto,
Extended: true,
RangeStart: m.cfg.EtherTalk.NetStart,
RangeEnd: m.cfg.EtherTalk.NetEnd,
}
respPktRaw, err := respPkt.Marshal()
if err != nil {
log.Printf("RTMP: Couldn't marshal RTMP Response packet: %v", err)
continue
}
ddpPkt := &ddp.ExtPacket{
ExtHeader: ddp.ExtHeader{
2024-04-15 10:16:57 +10:00
Size: uint16(len(respPktRaw)) + atalk.DDPExtHeaderSize,
2024-04-07 16:31:19 +10:00
Cksum: 0,
DstNet: pkt.SrcNet,
DstNode: pkt.SrcNode,
DstSocket: 1, // the RTMP socket
SrcNet: myAddr.Proto.Network,
SrcNode: myAddr.Proto.Node,
SrcSocket: 1, // the RTMP socket
Proto: ddp.ProtoRTMPResp,
},
Data: respPktRaw,
}
if err := m.send(myAddr.Hardware, theirHWAddr, ddpPkt); err != nil {
log.Printf("RTMP: Couldn't send Data broadcast: %v", err)
}
case 2, 3:
// Like the Data broadcast, but solicited by a request (RDR).
// TODO: handle split-horizon processing
dataPkt := m.dataPacket(myAddr.Proto)
dataPktRaw, err := dataPkt.Marshal()
if err != nil {
log.Printf("RTMP: Couldn't marshal Data packet: %v", err)
continue
}
ddpPkt := &ddp.ExtPacket{
ExtHeader: ddp.ExtHeader{
2024-04-15 10:16:57 +10:00
Size: uint16(len(dataPktRaw)) + atalk.DDPExtHeaderSize,
2024-04-07 16:31:19 +10:00
Cksum: 0,
DstNet: pkt.SrcNet,
DstNode: pkt.SrcNode,
DstSocket: 1, // the RTMP socket
SrcNet: myAddr.Proto.Network,
SrcNode: myAddr.Proto.Node,
SrcSocket: 1, // the RTMP socket
Proto: ddp.ProtoRTMPResp,
},
Data: dataPktRaw,
}
if err := m.send(myAddr.Hardware, theirHWAddr, ddpPkt); err != nil {
log.Printf("RTMP: Couldn't send Data response: %v", err)
}
2024-04-07 15:41:27 +10:00
}
case ddp.ProtoRTMPResp:
// It's a peer router on the AppleTalk network!
2024-04-07 16:31:19 +10:00
// TODO: integrate this information with the routing table
log.Print("RTMP: Got Response or Data")
2024-04-07 15:41:27 +10:00
2024-04-07 17:01:26 +10:00
default:
log.Printf("RTMP: invalid DDP type %d on socket 1", pkt.Proto)
2024-04-07 15:41:27 +10:00
}
2024-04-07 15:14:54 +10:00
}
}
}
2024-04-07 16:31:19 +10:00
func (m *RTMPMachine) send(src, dst ethernet.Addr, ddpPkt *ddp.ExtPacket) error {
ethFrame, err := ethertalk.AppleTalk(src, *ddpPkt)
if err != nil {
return err
}
ethFrame.Dst = dst
ethFrameRaw, err := ethertalk.Marshal(*ethFrame)
if err != nil {
return err
}
return m.pcapHandle.WritePacketData(ethFrameRaw)
}
2024-04-07 17:04:30 +10:00
func (m *RTMPMachine) broadcastData(myAddr aarp.AddrPair) error {
dataPkt := m.dataPacket(myAddr.Proto)
dataPktRaw, err := dataPkt.Marshal()
if err != nil {
return fmt.Errorf("marshal Data packet: %v", err)
}
ddpPkt := &ddp.ExtPacket{
ExtHeader: ddp.ExtHeader{
2024-04-15 10:16:57 +10:00
Size: uint16(len(dataPktRaw)) + atalk.DDPExtHeaderSize,
2024-04-07 17:04:30 +10:00
Cksum: 0,
DstNet: 0, // this network
DstNode: 0xff, // broadcast packet
DstSocket: 1, // the RTMP socket
SrcNet: myAddr.Proto.Network,
SrcNode: myAddr.Proto.Node,
SrcSocket: 1, // the RTMP socket
Proto: ddp.ProtoRTMPResp,
},
Data: dataPktRaw,
}
return m.send(myAddr.Hardware, ethertalk.AppleTalkBroadcast, ddpPkt)
}
2024-04-07 16:31:19 +10:00
func (m *RTMPMachine) dataPacket(myAddr ddp.Addr) *rtmp.DataPacket {
2024-04-13 15:47:58 +10:00
p := &rtmp.DataPacket{
2024-04-07 16:31:19 +10:00
RouterAddr: myAddr,
Extended: true,
NetworkTuples: []rtmp.NetworkTuple{
// "The first tuple in RTMP Data packets sent on extended
// networks ... indicates the network number range assigned
// to that network."
{
Extended: true,
RangeStart: m.cfg.EtherTalk.NetStart,
RangeEnd: m.cfg.EtherTalk.NetEnd,
Distance: 0,
},
},
}
2024-04-14 17:13:12 +10:00
for _, rt := range m.routingTable.ValidRoutes() {
2024-04-13 15:47:58 +10:00
p.NetworkTuples = append(p.NetworkTuples, rtmp.NetworkTuple{
2024-04-14 17:13:12 +10:00
Extended: rt.Extended,
RangeStart: rt.NetStart,
RangeEnd: rt.NetEnd,
Distance: rt.Distance + 1,
2024-04-13 15:47:58 +10:00
})
}
return p
2024-04-07 16:31:19 +10:00
}