Ravn::Net::Gateway::
Tak
class
The Helios TAK gateway.
This gateway joins the TAK multicast IGMP group, and silently picks up and parses the traffic for relaying TAK and CoT events -into- Helios.
Current behavior is to dispatch based on the CoT event type, similarly to how Ravn::Actor messages are routed – a type is hierarchical, and a single handler can manage an entire CoT classification. CoT “atom” types are optionally wildcarded, so determining the ‘affiliation’ can be easily left to the handler. If a specific handler exists, it is preferred, falling through to a possible wildcard handler. All other types (bits, tasking, etc) are otherwise unmolested.
Example:
For a type of “a-f-A-W-M-S-A-m” (a friendly Air/Weapon/Missile/SAM/Mobile):
“register_cot_handler ‘a-.-A-W’” would match any air weapon with any affiliation (hostile, unknown, friendly, etc), while “register_cot_handler ‘a-h-A-W-M’” would only match hostile missiles.
It is meant to be run only by the TAK operator, however there may be future needs that call for bidirectional TAK communications, or even acting as a full TAK client for each member. As of now, other TAK clients are totally unaware of our presence, we’re just a passive network sniffer.
- LOCAL_ADDRESS
The multicast binding address. Currently always INADDR_ANY, since that is what works.
- MAX_PACKET_SIZE
How much to read off the socket.
- TAK_IGMP_ADDRESS
The TAK multicast IP for UDP broadcast.
- TAK_IGMP_PORT
The TAK broadcast port.
- listener R
The socket catching multicast UDP packets.
- timer R
Concurrent::TimerTask that periodically checks for an interface to bind to.
Create a new (unstarted) TAK gateway.
def initialize( * )
@listener = nil
@timer = self.make_check_interface_timer
super
end
register_cot_handler( *args )
Register a handler method for one or more CoT prefixes.
def self::register_cot_handler( *args )
handler, *prefixes = args.reverse
raise ArgumentError, "no handler specified" unless handler && handler.respond_to?( :to_proc )
raise ArgumentError, "no CoT types specified" if prefixes.empty?
prefixes.each do |prefix|
Loggability[ Ravn ].debug "Adding %p handler for %s CoT events: %p" % [
self,
prefix,
handler
]
self.handler_dispatch.push_or_update( prefix, handler )
end
end
Registered handlers for a given CoT type.
singleton_attr_reader :handler_dispatch
relay_poi_event( message )
def relay_poi_event( message )
label = message.dig( :cotEvent, :detail, :contact, :callsign )
pos = [
message.dig( :cotEvent, :lat ),
message.dig( :cotEvent, :lon )
]
return if pos.empty? || ! label
msg = Ravn::Net::Message.new( 'net.pois', data: { label => pos } )
self.log.debug "Relaying %p" % [ msg ]
self.filter_up( msg )
end
Setup and begin listening for TAK network data.
def start
self.timer.execute
end
Setup the multicast listener if unset. There must be a device capable of broadcast at BDE startup for INADDR_ANY to bind.
def start_gateway
return if self.listener
self.setup_listener
self.start_listening
self.timer.shutdown
rescue Errno::ENODEV => err
self.log.debug "Unable to start TAK gateway (will retry): %p" % [ err ]
self.stop
end
Shutdown the multicast listener.
def stop
self.listener&.close
@listener = nil
@thread&.join( 10 )
@thread&.kill
@thread = nil
end
Protected Instance Methods
lookup_cot_handler( message )
Look up the name of the most-specific handler for messages of the given message
CoT type and return it as a call-able.
def lookup_cot_handler( message )
type = message.dig( :cotEvent, :type ) or return nil
handler = self.class.handler_dispatch.longest_prefix_value( type )
if type.start_with?( 'a-' ) && ! handler
wildtype = type.sub( /^a\-\w\-/, 'a-.-' )
handler = self.class.handler_dispatch.longest_prefix_value( wildtype )
end
return handler ? self.public_method( handler ) : nil
end
make_check_interface_timer()
Build a Concurrent::TimerTask that attempts to bind to multicast.
def make_check_interface_timer
timer = Concurrent::TimerTask.new { self.start_gateway }
timer.execution_interval = self.class.status_frequency
timer.add_observer( Ravn::LoggingTaskObserver.new(:tak_interface_check_timer) )
return timer
end
Create a new socket capable of receiving TAK multicast broadcasts.
def setup_listener
@listener = UDPSocket.new
igmp_membership = IPAddr.new( TAK_IGMP_ADDRESS ).hton +
IPAddr.new( LOCAL_ADDRESS ).hton
self.listener.setsockopt(
Socket::IPPROTO_IP,
Socket::IP_ADD_MEMBERSHIP,
igmp_membership
)
self.listener.setsockopt( :SOL_SOCKET, :SO_REUSEPORT, 1 )
self.listener.setsockopt( :IPPROTO_IP, :IP_MULTICAST_TTL, 1 )
self.listener.bind( LOCAL_ADDRESS, TAK_IGMP_PORT )
self.log.info "Listening for TAK messages at %s" % [ TAK_IGMP_ADDRESS ]
end
Start the loop of pulling packets off the wire, parsing them, and sending them to any matching dispatch handlers.
def start_listening
@thread = Thread.new do
Thread.current.name = "TAK Gateway I/O"
while self.listener && ! self.listener.closed?
packet, addr = listener.recvfrom( 1450 )
self.log.debug "Parsing a TAK message from %s." % [ addr.last ]
begin
message = Ravn::Tak.parse( packet )
if ( handler = self.lookup_cot_handler( message ) )
self.log.debug "Handling TAK message (%s) from %s." % [
message.dig( :cotEvent, :type ),
addr.last
]
handler.call( message )
else
self.log.debug "No handler for TAK message (%s) from %s." % [
message.dig( :cotEvent, :type ),
addr.last
]
end
rescue Ravn::Tak::ParseError => err
self.log.warn( err.message )
end
end
end
end