2013-11-21 02:12:59 +00:00
|
|
|
/*
|
|
|
|
Serval DNA MDP overlay network link tracking
|
2013-12-04 06:26:55 +00:00
|
|
|
Copyright (C) 2012-2013 Serval Project Inc.
|
2013-11-21 02:12:59 +00:00
|
|
|
Copyright (C) 2010-2012 Paul Gardner-Stephen
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2012-12-12 03:10:00 +00:00
|
|
|
#include "serval.h"
|
|
|
|
#include "conf.h"
|
|
|
|
#include "str.h"
|
|
|
|
#include "overlay_address.h"
|
|
|
|
#include "overlay_buffer.h"
|
2013-12-09 07:15:47 +00:00
|
|
|
#include "overlay_interface.h"
|
2012-12-12 03:10:00 +00:00
|
|
|
#include "overlay_packet.h"
|
2013-10-16 03:00:00 +00:00
|
|
|
#include "keyring.h"
|
2013-12-09 07:15:47 +00:00
|
|
|
#include "strbuf_helpers.h"
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2013-08-08 05:50:31 +00:00
|
|
|
int set_reachable(struct subscriber *subscriber,
|
|
|
|
struct network_destination *destination, struct subscriber *next_hop){
|
|
|
|
|
|
|
|
int reachable = REACHABLE_NONE;
|
|
|
|
if (destination)
|
|
|
|
reachable = destination->unicast?REACHABLE_UNICAST:REACHABLE_BROADCAST;
|
|
|
|
else if(next_hop)
|
|
|
|
reachable = REACHABLE_INDIRECT;
|
|
|
|
|
|
|
|
if (subscriber->reachable==reachable
|
|
|
|
&& subscriber->next_hop==next_hop
|
|
|
|
&& subscriber->destination == destination)
|
2012-12-12 03:10:00 +00:00
|
|
|
return 0;
|
2013-08-08 05:50:31 +00:00
|
|
|
|
2012-12-12 03:10:00 +00:00
|
|
|
int old_value = subscriber->reachable;
|
2013-08-08 05:50:31 +00:00
|
|
|
subscriber->reachable = reachable;
|
|
|
|
set_destination_ref(&subscriber->destination, destination);
|
|
|
|
subscriber->next_hop = next_hop;
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
// These log messages are for use in tests. Changing them may break test scripts.
|
2013-08-08 05:50:31 +00:00
|
|
|
if (config.debug.overlayrouting || config.debug.linkstate) {
|
2012-12-12 03:10:00 +00:00
|
|
|
switch (reachable) {
|
|
|
|
case REACHABLE_NONE:
|
2013-10-09 08:24:21 +00:00
|
|
|
DEBUGF("NOT REACHABLE sid=%s", alloca_tohex_sid_t(subscriber->sid));
|
2012-12-12 03:10:00 +00:00
|
|
|
break;
|
|
|
|
case REACHABLE_INDIRECT:
|
2013-08-08 05:50:31 +00:00
|
|
|
DEBUGF("REACHABLE INDIRECTLY sid=%s, via %s",
|
2013-10-09 08:24:21 +00:00
|
|
|
alloca_tohex_sid_t(subscriber->sid), alloca_tohex_sid_t(next_hop->sid));
|
2012-12-12 03:10:00 +00:00
|
|
|
break;
|
|
|
|
case REACHABLE_UNICAST:
|
2013-10-09 08:24:21 +00:00
|
|
|
DEBUGF("REACHABLE VIA UNICAST sid=%s, on %s ", alloca_tohex_sid_t(subscriber->sid), destination->interface->name);
|
2012-12-12 03:10:00 +00:00
|
|
|
break;
|
|
|
|
case REACHABLE_BROADCAST:
|
2013-10-09 08:24:21 +00:00
|
|
|
DEBUGF("REACHABLE VIA BROADCAST sid=%s, on %s ", alloca_tohex_sid_t(subscriber->sid), destination->interface->name);
|
2013-05-20 03:53:35 +00:00
|
|
|
break;
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Pre-emptively send a sas request */
|
|
|
|
if (!subscriber->sas_valid && reachable&REACHABLE)
|
|
|
|
keyring_send_sas_request(subscriber);
|
|
|
|
|
|
|
|
// Hacky layering violation... send our identity to a directory service
|
|
|
|
if (subscriber==directory_service)
|
|
|
|
directory_registration();
|
|
|
|
|
|
|
|
if ((old_value & REACHABLE) && (!(reachable & REACHABLE)))
|
2013-10-09 08:24:21 +00:00
|
|
|
monitor_announce_unreachable_peer(&subscriber->sid);
|
2012-12-12 03:10:00 +00:00
|
|
|
if ((!(old_value & REACHABLE)) && (reachable & REACHABLE))
|
2013-10-09 08:24:21 +00:00
|
|
|
monitor_announce_peer(&subscriber->sid);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2013-08-08 05:50:31 +00:00
|
|
|
return 1;
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int resolve_name(const char *name, struct in_addr *addr){
|
|
|
|
// TODO this can block, move to worker thread.
|
|
|
|
IN();
|
|
|
|
int ret=0;
|
|
|
|
struct addrinfo hint={
|
|
|
|
.ai_family=AF_INET,
|
|
|
|
};
|
|
|
|
struct addrinfo *addresses=NULL;
|
|
|
|
if (getaddrinfo(name, NULL, &hint, &addresses))
|
|
|
|
RETURN(WHYF("Failed to resolve %s",name));
|
|
|
|
|
|
|
|
if (addresses->ai_addr->sa_family==AF_INET){
|
|
|
|
*addr = ((struct sockaddr_in *)addresses->ai_addr)->sin_addr;
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
|
|
|
DEBUGF("Resolved %s into %s", name, inet_ntoa(*addr));
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
}else
|
2013-02-14 03:48:56 +00:00
|
|
|
ret=WHY("Ignoring non IPv4 address");
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
freeaddrinfo(addresses);
|
|
|
|
RETURN(ret);
|
2013-02-16 17:47:24 +00:00
|
|
|
OUT();
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// load a unicast address from configuration
|
|
|
|
int load_subscriber_address(struct subscriber *subscriber)
|
|
|
|
{
|
2013-08-08 05:50:31 +00:00
|
|
|
if (!subscriber || subscriber->reachable&REACHABLE)
|
2012-12-12 03:10:00 +00:00
|
|
|
return 0;
|
2013-10-09 08:24:21 +00:00
|
|
|
int i = config_host_list__get(&config.hosts, &subscriber->sid);
|
2012-12-12 03:10:00 +00:00
|
|
|
// No unicast configuration? just return.
|
|
|
|
if (i == -1)
|
|
|
|
return 1;
|
|
|
|
const struct config_host *hostc = &config.hosts.av[i].value;
|
|
|
|
overlay_interface *interface = NULL;
|
|
|
|
if (*hostc->interface){
|
|
|
|
interface = overlay_interface_find_name(hostc->interface);
|
|
|
|
if (!interface)
|
2013-02-14 03:48:56 +00:00
|
|
|
return WHY("Can't fund configured interface");
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
2013-12-09 07:15:47 +00:00
|
|
|
struct socket_address addr;
|
2012-12-12 03:10:00 +00:00
|
|
|
bzero(&addr, sizeof(addr));
|
2013-12-09 07:15:47 +00:00
|
|
|
addr.addrlen = sizeof(addr.inet);
|
|
|
|
addr.inet.sin_family = AF_INET;
|
|
|
|
addr.inet.sin_addr = hostc->address;
|
|
|
|
addr.inet.sin_port = htons(hostc->port);
|
|
|
|
if (addr.inet.sin_addr.s_addr==INADDR_NONE){
|
2012-12-12 03:10:00 +00:00
|
|
|
if (interface || overlay_interface_get_default()){
|
2013-12-09 07:15:47 +00:00
|
|
|
if (resolve_name(hostc->host, &addr.inet.sin_addr))
|
2012-12-12 03:10:00 +00:00
|
|
|
return -1;
|
|
|
|
}else{
|
|
|
|
// interface isnt up yet
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2013-12-09 07:15:47 +00:00
|
|
|
DEBUGF("Loaded address %s for %s", alloca_socket_address(&addr), alloca_tohex_sid_t(subscriber->sid));
|
|
|
|
struct network_destination *destination = create_unicast_destination(&addr, interface);
|
2013-08-08 05:50:31 +00:00
|
|
|
if (!destination)
|
|
|
|
return -1;
|
|
|
|
int ret=overlay_send_probe(subscriber, destination, OQ_MESH_MANAGEMENT);
|
|
|
|
release_destination_ref(destination);
|
|
|
|
return ret;
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Collection of unicast echo responses to detect working links */
|
|
|
|
int
|
2014-01-29 06:23:19 +00:00
|
|
|
overlay_mdp_service_probe(struct internal_mdp_header *header, struct overlay_buffer *payload)
|
2012-12-12 03:10:00 +00:00
|
|
|
{
|
|
|
|
IN();
|
2014-01-29 06:23:19 +00:00
|
|
|
if (header->source_port!=MDP_PORT_ECHO){
|
2012-12-12 03:10:00 +00:00
|
|
|
WARN("Probe packets should be returned from remote echo port");
|
|
|
|
RETURN(-1);
|
|
|
|
}
|
|
|
|
|
2014-01-23 06:01:56 +00:00
|
|
|
if (header->source->reachable == REACHABLE_SELF)
|
2013-01-29 00:57:13 +00:00
|
|
|
RETURN(0);
|
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
uint8_t interface = ob_get(payload);
|
2013-12-09 07:15:47 +00:00
|
|
|
struct socket_address addr;
|
2014-01-29 06:23:19 +00:00
|
|
|
addr.addrlen = ob_remaining(payload);
|
2013-12-09 07:15:47 +00:00
|
|
|
|
|
|
|
if (addr.addrlen > sizeof(addr.store))
|
|
|
|
RETURN(-1);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
ob_get_bytes(payload, (unsigned char*)&addr.addr, addr.addrlen);
|
2013-12-09 07:15:47 +00:00
|
|
|
|
2014-01-23 06:01:56 +00:00
|
|
|
RETURN(link_unicast_ack(header->source, &overlay_interfaces[interface], &addr));
|
2013-02-16 17:47:24 +00:00
|
|
|
OUT();
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
|
2013-08-08 05:50:31 +00:00
|
|
|
int overlay_send_probe(struct subscriber *peer, struct network_destination *destination, int queue){
|
2013-02-14 03:48:56 +00:00
|
|
|
// never send unicast probes over a stream interface
|
2013-08-08 05:50:31 +00:00
|
|
|
if (destination->interface->socket_type==SOCK_STREAM)
|
2013-02-06 06:56:59 +00:00
|
|
|
return 0;
|
|
|
|
|
2013-08-13 05:46:17 +00:00
|
|
|
time_ms_t now = gettime_ms();
|
|
|
|
// though unicast probes don't typically use the same network destination,
|
|
|
|
// we should still try to throttle when we can
|
|
|
|
if (destination->last_tx + destination->tick_ms > now)
|
|
|
|
return -1;
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
// TODO enhance overlay_send_frame to support pre-supplied network destinations
|
|
|
|
|
2012-12-12 03:10:00 +00:00
|
|
|
struct overlay_frame *frame=malloc(sizeof(struct overlay_frame));
|
|
|
|
bzero(frame,sizeof(struct overlay_frame));
|
|
|
|
frame->type=OF_TYPE_DATA;
|
|
|
|
frame->source = my_subscriber;
|
|
|
|
frame->next_hop = frame->destination = peer;
|
|
|
|
frame->ttl=1;
|
2012-12-15 23:35:32 +00:00
|
|
|
frame->queue=queue;
|
2013-08-13 05:46:17 +00:00
|
|
|
frame->destinations[frame->destination_count++].destination=add_destination_ref(destination);
|
2013-11-25 06:13:32 +00:00
|
|
|
if ((frame->payload = ob_new()) == NULL) {
|
2012-12-12 03:10:00 +00:00
|
|
|
op_free(frame);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-11-25 06:13:32 +00:00
|
|
|
frame->source_full = 1;
|
|
|
|
|
|
|
|
overlay_mdp_encode_ports(frame->payload, MDP_PORT_ECHO, MDP_PORT_PROBE);
|
2013-12-09 07:15:47 +00:00
|
|
|
|
|
|
|
ob_append_byte(frame->payload, destination->interface - overlay_interfaces);
|
|
|
|
ob_append_bytes(frame->payload, (uint8_t*)&destination->address.addr, destination->address.addrlen);
|
|
|
|
|
2012-12-12 03:10:00 +00:00
|
|
|
if (overlay_payload_enqueue(frame)){
|
|
|
|
op_free(frame);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2013-12-09 07:15:47 +00:00
|
|
|
DEBUGF("Queued probe packet on interface %s to %s for %s",
|
2013-08-08 05:50:31 +00:00
|
|
|
destination->interface->name,
|
2013-12-09 07:15:47 +00:00
|
|
|
alloca_socket_address(&destination->address),
|
2013-10-09 08:24:21 +00:00
|
|
|
peer?alloca_tohex_sid_t(peer->sid):"ANY");
|
2012-12-12 03:10:00 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// append the address of a unicast link into a packet buffer
|
2013-11-25 06:13:32 +00:00
|
|
|
static void overlay_append_unicast_address(struct subscriber *subscriber, struct overlay_buffer *buff)
|
2012-12-12 03:10:00 +00:00
|
|
|
{
|
2013-11-25 06:13:32 +00:00
|
|
|
if ( subscriber->destination
|
|
|
|
&& subscriber->destination->unicast
|
2013-12-09 07:15:47 +00:00
|
|
|
&& subscriber->destination->address.addr.sa_family==AF_INET
|
2013-11-25 06:13:32 +00:00
|
|
|
) {
|
|
|
|
overlay_address_append(NULL, buff, subscriber);
|
2013-12-09 07:15:47 +00:00
|
|
|
ob_append_ui32(buff, subscriber->destination->address.inet.sin_addr.s_addr);
|
|
|
|
ob_append_ui16(buff, subscriber->destination->address.inet.sin_port);
|
2013-08-13 05:46:17 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2013-10-09 08:24:21 +00:00
|
|
|
DEBUGF("Added STUN info for %s", alloca_tohex_sid_t(subscriber->sid));
|
2013-08-13 05:46:17 +00:00
|
|
|
}else{
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2013-10-09 08:24:21 +00:00
|
|
|
DEBUGF("Unable to give address of %s, %d", alloca_tohex_sid_t(subscriber->sid),subscriber->reachable);
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
int overlay_mdp_service_stun_req(struct internal_mdp_header *header, struct overlay_buffer *payload)
|
2012-12-12 03:10:00 +00:00
|
|
|
{
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2014-01-29 06:23:19 +00:00
|
|
|
DEBUGF("Processing STUN request from %s", alloca_tohex_sid_t(header->source->sid));
|
2013-02-15 00:00:23 +00:00
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
struct internal_mdp_header reply;
|
|
|
|
bzero(&reply, sizeof reply);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
mdp_init_response(header, &reply);
|
|
|
|
reply.qos = OQ_MESH_MANAGEMENT;
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
struct overlay_buffer *replypayload = ob_new();
|
|
|
|
ob_limitsize(replypayload, MDP_MTU);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
ob_checkpoint(replypayload);
|
2013-11-25 06:13:32 +00:00
|
|
|
while (ob_remaining(payload) > 0) {
|
2012-12-12 03:10:00 +00:00
|
|
|
struct subscriber *subscriber=NULL;
|
|
|
|
if (overlay_address_parse(NULL, payload, &subscriber))
|
|
|
|
break;
|
|
|
|
if (!subscriber){
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
|
|
|
DEBUGF("Unknown subscriber");
|
2012-12-12 03:10:00 +00:00
|
|
|
continue;
|
|
|
|
}
|
2013-11-25 06:13:32 +00:00
|
|
|
overlay_append_unicast_address(subscriber, replypayload);
|
|
|
|
if (ob_overrun(payload))
|
2012-12-12 03:10:00 +00:00
|
|
|
break;
|
2013-11-25 06:13:32 +00:00
|
|
|
ob_checkpoint(replypayload);
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
ob_rewind(replypayload);
|
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
if (ob_position(replypayload)){
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
|
|
|
DEBUGF("Sending reply");
|
2014-02-05 04:56:56 +00:00
|
|
|
ob_flip(replypayload);
|
|
|
|
overlay_send_frame(&reply, replypayload);
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
ob_free(replypayload);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
int overlay_mdp_service_stun(struct internal_mdp_header *header, struct overlay_buffer *payload)
|
2012-12-12 03:10:00 +00:00
|
|
|
{
|
2013-02-15 00:00:23 +00:00
|
|
|
if (config.debug.overlayrouting)
|
2014-01-29 06:23:19 +00:00
|
|
|
DEBUGF("Processing STUN info from %s", alloca_tohex_sid_t(header->source->sid));
|
2013-02-15 00:00:23 +00:00
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
while(ob_remaining(payload)>0){
|
2012-12-12 03:10:00 +00:00
|
|
|
struct subscriber *subscriber=NULL;
|
|
|
|
|
|
|
|
// TODO explain addresses, link expiry time, resolve differences between addresses...
|
|
|
|
|
2014-01-29 06:23:19 +00:00
|
|
|
if (overlay_address_parse(NULL, payload, &subscriber)){
|
2012-12-12 03:10:00 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-12-09 07:15:47 +00:00
|
|
|
struct socket_address addr;
|
|
|
|
addr.addrlen = sizeof(addr.inet);
|
|
|
|
addr.inet.sin_family = AF_INET;
|
2014-01-29 06:23:19 +00:00
|
|
|
addr.inet.sin_addr.s_addr = ob_get_ui32(payload);
|
|
|
|
addr.inet.sin_port = ob_get_ui16(payload);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
if (!subscriber || (subscriber->reachable!=REACHABLE_NONE))
|
|
|
|
continue;
|
|
|
|
|
2013-12-09 07:15:47 +00:00
|
|
|
struct network_destination *destination = create_unicast_destination(&addr, NULL);
|
2013-08-08 05:50:31 +00:00
|
|
|
if (destination){
|
|
|
|
overlay_send_probe(subscriber, destination, OQ_MESH_MANAGEMENT);
|
|
|
|
release_destination_ref(destination);
|
|
|
|
}
|
2012-12-12 03:10:00 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int overlay_send_stun_request(struct subscriber *server, struct subscriber *request){
|
|
|
|
if ((!server) || (!request))
|
|
|
|
return -1;
|
2013-08-08 05:50:31 +00:00
|
|
|
if (!(server->reachable&REACHABLE))
|
2012-12-12 03:10:00 +00:00
|
|
|
return -1;
|
|
|
|
// don't bother with a stun request if the peer is already reachable directly
|
2013-08-08 05:50:31 +00:00
|
|
|
if (request->reachable&REACHABLE_DIRECT)
|
2012-12-12 03:10:00 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
time_ms_t now = gettime_ms();
|
|
|
|
if (request->last_stun_request +1000 > now)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
request->last_stun_request=now;
|
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
struct internal_mdp_header header;
|
|
|
|
bzero(&header, sizeof header);
|
|
|
|
header.source = my_subscriber;
|
|
|
|
header.destination = server;
|
|
|
|
|
|
|
|
header.source_port = MDP_PORT_STUN;
|
|
|
|
header.destination_port = MDP_PORT_STUNREQ;
|
|
|
|
header.qos = OQ_MESH_MANAGEMENT;
|
2012-12-12 03:10:00 +00:00
|
|
|
|
2014-02-05 04:56:56 +00:00
|
|
|
struct overlay_buffer *payload = ob_new();
|
|
|
|
ob_limitsize(payload, MDP_MTU);
|
2012-12-12 03:10:00 +00:00
|
|
|
|
|
|
|
overlay_address_append(NULL, payload, request);
|
2013-11-25 06:13:32 +00:00
|
|
|
if (!ob_overrun(payload)) {
|
|
|
|
if (config.debug.overlayrouting)
|
|
|
|
DEBUGF("Sending STUN request to %s", alloca_tohex_sid_t(server->sid));
|
2014-02-05 04:56:56 +00:00
|
|
|
|
|
|
|
ob_flip(payload);
|
|
|
|
overlay_send_frame(&header, payload);
|
2013-11-25 06:13:32 +00:00
|
|
|
}
|
2012-12-12 03:10:00 +00:00
|
|
|
ob_free(payload);
|
|
|
|
return 0;
|
|
|
|
}
|