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.

Constants

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.

Attributes

listener R

The socket catching multicast UDP packets.

timer R

Concurrent::TimerTask that periodically checks for an interface to bind to.

Public Class Methods

new( * )

Create a new (unstarted) TAK gateway.

# File lib/ravn/net/gateway/tak.rb, line 100
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.

# File lib/ravn/net/gateway/tak.rb, line 82
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

Public Instance Methods

handler_dispatch()

Registered handlers for a given CoT type.

# File lib/ravn/net/gateway/tak.rb, line 67
singleton_attr_reader :handler_dispatch
relay_poi_event( message )
# File lib/ravn/net/gateway/tak.rb, line 140
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
start()

Setup and begin listening for TAK network data.

# File lib/ravn/net/gateway/tak.rb, line 122
def start
        self.timer.execute
end
start_gateway()

Setup the multicast listener if unset. There must be a device capable of broadcast at BDE startup for INADDR_ANY to bind.

# File lib/ravn/net/gateway/tak.rb, line 157
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
stop()

Shutdown the multicast listener.

# File lib/ravn/net/gateway/tak.rb, line 128
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.

# File lib/ravn/net/gateway/tak.rb, line 228
def lookup_cot_handler( message )
        type    = message.dig( :cotEvent, :type ) or return nil
        handler = self.class.handler_dispatch.longest_prefix_value( type )

        # Check for an affiliation wildcard handler for Atom TAK types.
        #
        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.

# File lib/ravn/net/gateway/tak.rb, line 244
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
setup_listener()

Create a new socket capable of receiving TAK multicast broadcasts.

# File lib/ravn/net/gateway/tak.rb, line 205
def setup_listener
        @listener = UDPSocket.new

        igmp_membership = IPAddr.new( TAK_IGMP_ADDRESS ).hton +
                IPAddr.new( LOCAL_ADDRESS ).hton

        # Register IGMP membership to the multicast group.
        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_listening()

Start the loop of pulling packets off the wire, parsing them, and sending them to any matching dispatch handlers.

# File lib/ravn/net/gateway/tak.rb, line 174
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