Called by a network stack implementer to push application-layer
        data "up" from the stack.

        Arguments are protocol number, local_addr (a 2-tuple of IP address
        and port), remote_addr (a 2-tuple of IP address and port), and the
        message.  

        Returns True if a socket was found to which to deliver the message,
        and False otherwise.  When False is returned, a log warning is also
        emitted.

def send_to_app(proto, local_addr, remote_addr, data):
        '''
        Called by a network stack implementer to push application-layer
        data "up" from the stack.

        Arguments are protocol number, local_addr (a 2-tuple of IP address
        and port), remote_addr (a 2-tuple of IP address and port), and the
        message.  

        Returns True if a socket was found to which to deliver the message,
        and False otherwise.  When False is returned, a log warning is also
        emitted.
        '''
        proto = IPProtocol(proto)
        local_addr = _normalize_addrs(local_addr)
        remote_addr = _normalize_addrs(remote_addr)
        xtup = (proto, local_addr[0], local_addr[1])
        with _lock:
            sockqueue = ApplicationLayer._to_app.get(xtup, None)

        if sockqueue is not None:
            sockqueue.put((local_addr,remote_addr,data))
            return True
        
        # no dice, try local IP addr of 0.0.0.0
        local2 = _normalize_addrs(("0.0.0.0", local_addr[1]))
        xtup = (proto, local2[0], local2[1])
        with _lock:
            sockqueue = ApplicationLayer._to_app.get(xtup, None)
        if sockqueue is not None:    
            sockqueue.put((local_addr,remote_addr,data))
            return True

        log_warn("No socket queue found for local proto/address: {}".format(xtup))
        return False