You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
18 KiB
Go

// Package homebrew implements the Home Brew DMR IPSC protocol
package homebrew
import (
"bytes"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/op/go-logging"
"github.com/pd0mz/go-dmr"
)
var log = logging.MustGetLogger("dmr/homebrew")
type AuthStatus uint8
func (a *AuthStatus) String() string {
switch *a {
case AuthNone:
return "none"
case AuthBegin:
return "begin"
case AuthDone:
return "none"
case AuthFailed:
return "failed"
default:
return "invalid"
}
}
const (
AuthNone AuthStatus = iota
AuthBegin
AuthDone
AuthFailed
)
// Messages as documented by DL5DI, G4KLX and DG1HT, see "DMRplus IPSC Protocol for HB repeater (20150726).pdf".
var (
DMRData = []byte("DMRD")
MasterNAK = []byte("MSTNAK")
MasterACK = []byte("MSTACK")
RepeaterLogin = []byte("RPTL")
RepeaterKey = []byte("RPTK")
MasterPing = []byte("MSTPING")
RepeaterPong = []byte("RPTPONG")
MasterClosing = []byte("MSTCL")
RepeaterClosing = []byte("RPTCL")
)
// We ping the peers every minute
var (
AuthTimeout = time.Second * 5
PingInterval = time.Second * 5
PingTimeout = time.Second * 15
SendInterval = time.Millisecond * 30
)
// Homebrew is implements the Homebrew IPSC DMR Air Interface protocol
type Homebrew struct {
Config *RepeaterConfiguration
Peer map[string]*Peer
PeerID map[uint32]*Peer
pf dmr.PacketFunc
conn *net.UDPConn
closed bool
id []byte
last time.Time // Record last received frame time
mutex *sync.Mutex // Mutex for manipulating peer list or send queue
rxtx *sync.Mutex // Mutex for when receiving data or sending data
stop chan bool
queue []*dmr.Packet
}
// New creates a new Homebrew repeater
func New(config *RepeaterConfiguration, addr *net.UDPAddr) (*Homebrew, error) {
var err error
if config == nil {
return nil, errors.New("homebrew: RepeaterConfiguration can't be nil")
}
if addr == nil {
return nil, errors.New("homebrew: addr can't be nil")
}
h := &Homebrew{
Config: config,
Peer: make(map[string]*Peer),
PeerID: make(map[uint32]*Peer),
id: packRepeaterID(config.ID),
mutex: &sync.Mutex{},
rxtx: &sync.Mutex{},
queue: make([]*dmr.Packet, 0),
}
if h.conn, err = net.ListenUDP("udp", addr); err != nil {
return nil, errors.New("homebrew: " + err.Error())
}
return h, nil
}
func (h *Homebrew) Active() bool {
return !h.closed && h.conn != nil
}
// Close stops the active listeners
func (h *Homebrew) Close() error {
h.mutex.Lock()
defer h.mutex.Unlock()
if !h.Active() {
return nil
}
log.Info("closing")
// Tell peers we're closing
closing:
for _, peer := range h.Peer {
if peer.Status == AuthDone {
if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil {
break closing
}
}
}
// Kill keepalive goroutine
if h.stop != nil {
close(h.stop)
h.stop = nil
}
// Kill listening socket
h.closed = true
return h.conn.Close()
}
// Link establishes a new link with a peer
func (h *Homebrew) Link(peer *Peer) error {
if peer == nil {
return errors.New("homebrew: peer can't be nil")
}
if peer.Addr == nil {
return errors.New("homebrew: peer Addr can't be nil")
}
if peer.AuthKey == nil || len(peer.AuthKey) == 0 {
return errors.New("homebrew: peer AuthKey can't be nil")
}
h.mutex.Lock()
defer h.mutex.Unlock()
// Reset state
peer.Last.PacketSent = time.Time{}
peer.Last.PacketReceived = time.Time{}
peer.Last.PingSent = time.Time{}
peer.Last.PongReceived = time.Time{}
// Register our peer
peer.id = packRepeaterID(peer.ID)
h.Peer[peer.Addr.String()] = peer
h.PeerID[peer.ID] = peer
return h.handleAuth(peer)
}
func (h *Homebrew) Unlink(id uint32) error {
h.mutex.Lock()
defer h.mutex.Unlock()
peer, ok := h.PeerID[id]
if !ok {
return fmt.Errorf("homebrew: peer %d not linked", id)
}
delete(h.Peer, peer.Addr.String())
delete(h.PeerID, id)
return nil
}
func (h *Homebrew) ListenAndServe() error {
var data = make([]byte, 53)
h.stop = make(chan bool)
go h.keepalive(h.stop)
h.closed = false
for !h.closed {
n, peer, err := h.conn.ReadFromUDP(data)
if err != nil {
return err
}
if err := h.handle(peer, data[:n]); err != nil {
if h.closed && strings.HasSuffix(err.Error(), "use of closed network connection") {
break
}
return err
}
}
log.Info("listener closed")
return nil
}
// Send a packet to the peers. Will block until the packet is sent.
func (h *Homebrew) Send(p *dmr.Packet) error {
h.rxtx.Lock()
defer h.rxtx.Unlock()
data := BuildData(p, h.Config.ID)
for _, peer := range h.getPeers() {
if err := h.WriteToPeer(data, peer); err != nil {
return err
}
}
return nil
}
func (h *Homebrew) GetPacketFunc() dmr.PacketFunc {
return h.pf
}
func (h *Homebrew) SetPacketFunc(f dmr.PacketFunc) {
h.pf = f
}
func (h *Homebrew) WritePacketToPeer(p *dmr.Packet, peer *Peer) error {
return h.WriteToPeer(h.parsePacket(p), peer)
}
func (h *Homebrew) WriteToPeer(b []byte, peer *Peer) error {
if peer == nil {
return errors.New("homebrew: can't write to nil peer")
}
peer.Last.PacketSent = time.Now()
_, err := h.conn.WriteTo(b, peer.Addr)
return err
}
func (h *Homebrew) WriteToPeerWithID(b []byte, id uint32) error {
return h.WriteToPeer(b, h.getPeer(id))
}
func (h *Homebrew) checkRepeaterID(id []byte) bool {
// BrandMeister release 20190421-185653 switched from upper case to lower case hex digits
return id != nil && bytes.EqualFold(id, h.id)
}
func (h *Homebrew) getPeer(id uint32) *Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
if peer, ok := h.PeerID[id]; ok {
return peer
}
return nil
}
func (h *Homebrew) getPeerByAddr(addr *net.UDPAddr) *Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
if peer, ok := h.Peer[addr.String()]; ok {
return peer
}
return nil
}
func (h *Homebrew) getPeers() []*Peer {
h.mutex.Lock()
defer h.mutex.Unlock()
var peers = make([]*Peer, 0)
for _, peer := range h.Peer {
peers = append(peers, peer)
}
return peers
}
func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
peer := h.getPeerByAddr(remote)
if peer == nil {
log.Debugf("ignored packet from unknown peer %s\n", remote)
return nil
}
// Ignore packet that are clearly invalid, this is the minimum packet length for any Homebrew protocol frame
if len(data) < 14 {
return nil
}
if peer.Status != AuthDone {
// Ignore DMR data at this stage
if bytes.Equal(data[:4], DMRData) {
return nil
}
if peer.Incoming {
switch peer.Status {
case AuthNone:
switch {
case bytes.Equal(data[:4], RepeaterLogin):
if !peer.CheckRepeaterID(data[4:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:]))
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
// Peer is verified, generate a nonce
nonce := make([]byte, 4)
if _, err := rand.Read(nonce); err != nil {
log.Errorf("peer %d@%s nonce generation failed: %v\n", peer.ID, remote, err)
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
peer.UpdateToken(nonce)
peer.Status = AuthBegin
return h.WriteToPeer(append(append(MasterACK, h.id...), nonce...), peer)
default:
// Ignore unauthenticated repeater, we're not going to reply unless it's
// an actual login request; if it was indeed a valid repeater and we missed
// anything, we rely on the remote end to retry to reconnect if it doesn't
// get an answer in a timely manner.
break
}
break
case AuthBegin:
switch {
case bytes.Equal(data[:4], RepeaterKey):
if !peer.CheckRepeaterID(data[4:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:]))
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
if len(data) != 76 {
peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
if !bytes.Equal(data[12:], peer.Token) {
log.Errorf("peer %d@%s sent invalid key challenge token\n", peer.ID, remote)
peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
}
peer.Last.PingSent = time.Now()
peer.Last.PongReceived = time.Now()
peer.Status = AuthDone
return h.WriteToPeer(append(MasterACK, h.id...), peer)
}
}
} else {
// Verify we have a matching peer ID
if !h.checkRepeaterID(data[6:14]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
return nil
}
switch peer.Status {
case AuthNone:
switch {
case bytes.Equal(data[:6], MasterACK):
log.Debugf("peer %d@%s sent nonce\n", peer.ID, remote)
peer.Status = AuthBegin
peer.UpdateToken(data[14:])
return h.handleAuth(peer)
case bytes.Equal(data[:6], MasterNAK):
log.Errorf("peer %d@%s refused login\n", peer.ID, remote)
peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID)
}
break
default:
log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
break
}
case AuthBegin:
switch {
case bytes.Equal(data[:6], MasterACK):
log.Infof("peer %d@%s accepted login\n", peer.ID, remote)
peer.Status = AuthDone
peer.Last.PingSent = time.Now()
peer.Last.PongReceived = time.Now()
return h.WriteToPeer(h.Config.Bytes(), peer)
case bytes.Equal(data[:6], MasterNAK):
log.Errorf("peer %d@%s refused login\n", peer.ID, remote)
peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID)
}
break
default:
log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
break
}
}
}
} else {
// Authentication is done
if peer.Incoming {
switch {
case bytes.Equal(data[:4], DMRData):
p, err := h.parseData(data)
if err != nil {
return err
}
return h.handlePacket(p, peer)
case bytes.Equal(data[:6], MasterACK):
break
case len(data) == 15 && bytes.Equal(data[:7], MasterPing):
return h.WriteToPeer(append(RepeaterPong, data[7:]...), peer)
default:
log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String())
log.Debug(hex.Dump(data))
break
}
} else {
switch {
case bytes.Equal(data[:4], DMRData):
p, err := h.parseData(data)
if err != nil {
return err
}
return h.handlePacket(p, peer)
case bytes.Equal(data[:6], MasterACK):
if !h.checkRepeaterID(data[6:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
return nil
}
peer.Last.PingSent = time.Now()
return h.WriteToPeer(append(MasterPing, h.id...), peer)
case bytes.Equal(data[:6], MasterNAK):
if !h.checkRepeaterID(data[6:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
return nil
}
log.Errorf("peer %d@%s deauthenticated us; re-authenticating\n", peer.ID, remote)
peer.Status = AuthNone
return h.handleAuth(peer)
case len(data) == 15 && bytes.Equal(data[:7], RepeaterPong):
if !h.checkRepeaterID(data[7:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
return nil
}
peer.Last.PongReceived = time.Now()
break
case len(data) == 10 && bytes.Equal(data[:6], MasterNAK):
if !h.checkRepeaterID(data[6:]) {
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
return nil
}
log.Errorf("peer %d@%s sent NAK; re-establishing link\n", peer.ID, remote)
peer.Status = AuthNone
return h.handleAuth(peer)
default:
log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String())
log.Debug(hex.Dump(data))
break
}
}
}
return nil
}
func (h *Homebrew) handleAuth(peer *Peer) error {
if !peer.Incoming {
switch peer.Status {
case AuthNone:
// Send login packet
return h.WriteToPeer(append(RepeaterLogin, h.id...), peer)
case AuthBegin:
// Send repeater key exchange packet
return h.WriteToPeer(append(append(RepeaterKey, h.id...), peer.Token...), peer)
}
}
return nil
}
func (h *Homebrew) handlePacket(p *dmr.Packet, peer *Peer) error {
h.rxtx.Lock()
defer h.rxtx.Unlock()
// Record last received time
h.last = time.Now()
// Offload packet to handle callback
if peer.PacketReceived != nil {
return peer.PacketReceived(h, p)
}
if h.pf == nil {
return errors.New("homebrew: no PacketReceived func defined to handle DMR packet")
}
return h.pf(h, p)
}
func (h *Homebrew) keepalive(stop <-chan bool) {
for {
select {
case <-time.After(time.Second):
now := time.Now()
for _, peer := range h.getPeers() {
// Ping protocol only applies to outgoing links, and also the auth retries
// are entirely up to the peer.
if peer.Incoming {
switch peer.Status {
case AuthDone:
switch {
case now.Sub(peer.Last.PingReceived) > PingTimeout:
peer.Status = AuthNone
log.Errorf("peer %d@%s not requesting to ping; dropping connection", peer.ID, peer.Addr)
if err := h.WriteToPeer(append(MasterClosing, h.id...), peer); err != nil {
log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err)
}
break
}
break
}
} else {
switch peer.Status {
case AuthNone, AuthBegin:
switch {
case now.Sub(peer.Last.PacketReceived) > AuthTimeout:
peer.Status = AuthNone
log.Errorf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr)
if err := h.handleAuth(peer); err != nil {
log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
}
break
}
case AuthDone:
switch {
case now.Sub(peer.Last.PongReceived) > PingTimeout:
peer.Status = AuthNone
log.Errorf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr)
if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil {
log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err)
}
if err := h.handleAuth(peer); err != nil {
log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
}
break
case now.Sub(peer.Last.PingSent) > PingInterval:
peer.Last.PingSent = now
if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil {
log.Errorf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err)
}
break
}
}
}
}
case <-stop:
return
}
}
}
// parseData converts Homebrew packet format to DMR packet format
func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) {
p, err := ParseData(data)
if err == nil {
p.RepeaterID = h.Config.ID
}
return p, err
}
// parsePacket converts DMR packet format to Homebrew packet format suitable for sending on the wire
func (h *Homebrew) parsePacket(p *dmr.Packet) []byte {
var d = make([]byte, 53)
// Signature, 4 bytes, "DMRD"
copy(d[0:], DMRData)
// Seq No, 1 byte
d[4] = p.Sequence
// Src ID, 3 bytes
d[5] = uint8((p.SrcID >> 16) & 0xff)
d[6] = uint8((p.SrcID >> 8) & 0xff)
d[7] = uint8((p.SrcID & 0xff))
// Dst ID, 3 bytes
d[8] = uint8((p.DstID >> 16) & 0xff)
d[9] = uint8((p.DstID >> 8) & 0xff)
d[10] = uint8((p.DstID & 0xff))
// RptrID, 4 bytes
binary.LittleEndian.PutUint32(d[11:], p.RepeaterID)
var s byte
s |= (p.Timeslot & 0x01) // Slot no, 1 bit
s |= (p.CallType & 0x01) << 1 // Call Type, 1 bit
switch p.DataType { // Frame Type, 2 bits and Data Type or Voice Seq, 4 bits
case dmr.VoiceBurstA:
s |= 0x01 << 2 // Voice sync
s |= (p.DataType - dmr.VoiceBurstA) << 4
case dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF:
s |= 0x00 << 2 // Voice (no-op)
s |= (p.DataType - dmr.VoiceBurstA) << 4
default:
s |= 0x02 << 2 // Data
s |= p.DataType << 4
}
// StreamID, 4 bytes
binary.BigEndian.PutUint32(d[16:], p.StreamID)
// DMR Data, 33 bytes
copy(d[20:], p.Data)
return d
}
func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) {
id, err := strconv.ParseUint(string(data), 16, 32)
if err != nil {
return 0, err
}
return uint32(id), nil
}
// Interface compliance check
var _ dmr.Repeater = (*Homebrew)(nil)
func packRepeaterID(id uint32) []byte {
return []byte(fmt.Sprintf("%08X", id))
}
// BuildData converts DMR packet format to Homebrew packet format.
func BuildData(p *dmr.Packet, repeaterID uint32) []byte {
var data = make([]byte, 53)
copy(data[:4], DMRData)
data[4] = p.Sequence
data[5] = uint8(p.SrcID >> 16)
data[6] = uint8(p.SrcID >> 8)
data[7] = uint8(p.SrcID)
data[8] = uint8(p.DstID >> 16)
data[9] = uint8(p.DstID >> 8)
data[10] = uint8(p.DstID)
data[11] = uint8(repeaterID >> 24)
data[12] = uint8(repeaterID >> 16)
data[13] = uint8(repeaterID >> 8)
data[14] = uint8(repeaterID)
data[15] = p.Timeslot | (p.CallType << 1)
data[16] = uint8(p.StreamID >> 24)
data[17] = uint8(p.StreamID >> 16)
data[18] = uint8(p.StreamID >> 8)
data[19] = uint8(p.StreamID)
copy(data[20:], p.Data)
switch p.DataType {
case dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF:
data[15] |= (0x00 << 2)
data[15] |= (p.DataType - dmr.VoiceBurstA) << 4
break
case dmr.VoiceBurstA:
data[15] |= (0x01 << 2)
break
default:
data[15] |= (0x02 << 2)
data[15] |= (p.DataType) << 4
}
return data
}
// ParseData converts Homebrew packet format to DMR packet format.
func ParseData(data []byte) (*dmr.Packet, error) {
if len(data) != 53 {
return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data))
}
var p = &dmr.Packet{
Sequence: data[4],
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
RepeaterID: uint32(data[11])<<24 | uint32(data[12])<<16 | uint32(data[13])<<8 | uint32(data[14]),
Timeslot: (data[15] >> 0) & 0x01,
CallType: (data[15] >> 1) & 0x01,
StreamID: uint32(data[16])<<24 | uint32(data[17])<<16 | uint32(data[18])<<8 | uint32(data[19]),
}
p.SetData(data[20:])
switch (data[15] >> 2) & 0x03 {
case 0x00, 0x01: // voice (B-F), voice sync (A)
p.DataType = dmr.VoiceBurstA + (data[15] >> 4)
break
case 0x02: // data sync
p.DataType = (data[15] >> 4)
break
default: // unknown/unused
return nil, errors.New("homebrew: unexpected frame type 0b11")
}
return p, nil
}