/* This file is part of GNUnet. (C) 2010 Christian Grothoff GNUnet 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 3, or (at your option) any later version. GNUnet 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 GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file vpn/gnunet-daemon-vpn.c * @brief * @author Philipp Toelke */ #include "platform.h" #include "gnunet_getopt_lib.h" #include "gnunet_program_lib.h" #include "gnunet-vpn-packet.h" #include "gnunet_common.h" #include "gnunet_protocols.h" #include #include "gnunet_client_lib.h" #include "gnunet_container_lib.h" #include "gnunet_constants.h" #include #include "gnunet-daemon-vpn-helper.h" #include "gnunet-daemon-vpn-dns.h" #include "gnunet-daemon-vpn.h" #include "gnunet-vpn-checksum.h" const struct GNUNET_CONFIGURATION_Handle *cfg; struct GNUNET_MESH_Handle *mesh_handle; struct GNUNET_CONTAINER_MultiHashMap* hashmap; static struct GNUNET_CONTAINER_Heap *heap; /** * If there are at least this many address-mappings, old ones will be removed */ static long long unsigned int max_mappings = 200; /** * Final status code. */ static int ret; /** * This hashmap contains the mapping from peer, service-descriptor, * source-port and destination-port to a socket */ static struct GNUNET_CONTAINER_MultiHashMap *udp_connections; /** * Function scheduled as very last function, cleans up after us *{{{ */ static void cleanup(void* cls, const struct GNUNET_SCHEDULER_TaskContext* tskctx) { GNUNET_assert (0 != (tskctx->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)); /* stop the helper */ cleanup_helper(helper_handle); /* close the connection to the service-dns */ if (dns_connection != NULL) { GNUNET_CLIENT_disconnect (dns_connection, GNUNET_NO); dns_connection = NULL; } if (mesh_handle != NULL) { GNUNET_MESH_disconnect(mesh_handle); mesh_handle = NULL; } } /*}}}*/ /** * @return the hash of the IP-Address if a mapping exists, NULL otherwise */ GNUNET_HashCode* address_mapping_exists(unsigned char addr[]) { GNUNET_HashCode* key = GNUNET_malloc(sizeof(GNUNET_HashCode)); unsigned char* k = (unsigned char*)key; memset(key, 0, sizeof(GNUNET_HashCode)); unsigned int i; for (i = 0; i < 16; i++) k[15-i] = addr[i]; if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(hashmap, key)) return key; else { GNUNET_free(key); return NULL; } } static void collect_mappings(void* cls, const struct GNUNET_SCHEDULER_TaskContext* tc) { if ( (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) return; struct map_entry* me = GNUNET_CONTAINER_heap_remove_root(heap); /* This is free()ed memory! */ me->heap_node = NULL; /* FIXME! GNUNET_MESH_close_tunnel(me->tunnel); */ GNUNET_CONTAINER_multihashmap_remove(hashmap, &me->hash, me); GNUNET_free(me); } void send_icmp_response(void* cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct ip6_icmp* request = cls; struct ip6_icmp* response = alloca(ntohs(request->shdr.size)); GNUNET_assert(response != NULL); memset(response, 0, ntohs(request->shdr.size)); response->shdr.size = request->shdr.size; response->shdr.type = htons(GNUNET_MESSAGE_TYPE_VPN_HELPER); response->tun.flags = 0; response->tun.type = htons(0x86dd); response->ip6_hdr.hoplmt = 255; response->ip6_hdr.paylgth = request->ip6_hdr.paylgth; response->ip6_hdr.nxthdr = 0x3a; response->ip6_hdr.version = 6; memcpy(&response->ip6_hdr.sadr, &request->ip6_hdr.dadr, 16); memcpy(&response->ip6_hdr.dadr, &request->ip6_hdr.sadr, 16); response->icmp_hdr.code = 0; response->icmp_hdr.type = 0x81; /* Magic, more Magic! */ response->icmp_hdr.chks = request->icmp_hdr.chks - 0x1; /* Copy the rest of the packet */ memcpy(response+1, request+1, ntohs(request->shdr.size) - sizeof(struct ip6_icmp)); write_to_helper(response, ntohs(response->shdr.size)); GNUNET_free(request); } /** * cls is the pointer to a GNUNET_MessageHeader that is * followed by the service-descriptor and the packet that should be sent; */ static size_t send_pkt_to_peer_notify_callback (void *cls, size_t size, void *buf) { struct GNUNET_MESH_Tunnel **tunnel = cls; struct GNUNET_MessageHeader *hdr = (struct GNUNET_MessageHeader *) (tunnel + 1); GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "send_pkt_to_peer_notify_callback: buf = %x; size = %u;\n", buf, size); GNUNET_assert (size >= ntohs (hdr->size)); memcpy (buf, hdr, ntohs (hdr->size)); size = ntohs(hdr->size); GNUNET_free (cls); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sent!\n"); return size; } unsigned int port_in_ports (uint64_t ports, uint16_t port) { uint16_t *ps = (uint16_t *) & ports; return ps[0] == port || ps[1] == port || ps[2] == port || ps[3] == port; } void send_pkt_to_peer (void *cls, const struct GNUNET_PeerIdentity *peer, const struct GNUNET_TRANSPORT_ATS_Information *atsi) { if (peer == NULL) return; struct GNUNET_MESH_Tunnel **tunnel = cls; struct GNUNET_MessageHeader *hdr = (struct GNUNET_MessageHeader *) (tunnel + 1); GNUNET_assert(NULL != tunnel); GNUNET_assert(NULL != *tunnel); GNUNET_MESH_notify_transmit_ready (*tunnel, GNUNET_NO, 42, GNUNET_TIME_relative_divide(GNUNET_CONSTANTS_MAX_CORK_DELAY, 2), (const struct GNUNET_PeerIdentity *)NULL, ntohs(hdr->size), send_pkt_to_peer_notify_callback, cls); } /** * Create a new Address from an answer-packet */ void new_ip6addr(unsigned char* buf, const GNUNET_HashCode *peer, const GNUNET_HashCode *service_desc) { /* {{{ */ char* ipv6addr; unsigned long long ipv6prefix; GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, "vpn", "IPV6ADDR", &ipv6addr)); GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_get_value_number(cfg, "vpn", "IPV6PREFIX", &ipv6prefix)); GNUNET_assert(ipv6prefix < 127); ipv6prefix = (ipv6prefix + 7)/8; inet_pton (AF_INET6, ipv6addr, buf); GNUNET_free(ipv6addr); int peer_length = 16 - ipv6prefix - 6; if (peer_length <= 0) peer_length = 0; int service_length = 16 - ipv6prefix - peer_length; if (service_length <= 0) service_length = 0; memcpy(buf+ipv6prefix, service_desc, service_length); memcpy(buf+ipv6prefix+service_length, peer, peer_length); } /*}}}*/ char max (char a, char b) { return a > b ? a : b; } /** * Create a new Address from an answer-packet */ void new_ip6addr_remote (unsigned char *buf, unsigned char *addr, char addrlen) { /* {{{ */ char *ipv6addr; unsigned long long ipv6prefix; GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, "vpn", "IPV6ADDR", &ipv6addr)); GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (cfg, "vpn", "IPV6PREFIX", &ipv6prefix)); GNUNET_assert (ipv6prefix < 127); ipv6prefix = (ipv6prefix + 7) / 8; inet_pton (AF_INET6, ipv6addr, buf); GNUNET_free (ipv6addr); int local_length = 16 - ipv6prefix; memcpy (buf + ipv6prefix, addr, max (addrlen, local_length)); } /*}}}*/ /** * This gets scheduled with cls pointing to an answer_packet and does everything * needed in order to send it to the helper. * * At the moment this means "inventing" and IPv6-Address for .gnunet-services and * doing nothing for "real" services. */ void process_answer(void* cls, const struct GNUNET_SCHEDULER_TaskContext* tc) { struct answer_packet* pkt = cls; struct answer_packet_list* list; /* This answer is about a .gnunet-service * * It contains an almost complete DNS-Response, we have to fill in the ip * at the offset pkt->addroffset */ if (pkt->subtype == GNUNET_DNS_ANSWER_TYPE_SERVICE) { pkt->subtype = GNUNET_DNS_ANSWER_TYPE_IP; GNUNET_HashCode key; memset(&key, 0, sizeof(GNUNET_HashCode)); unsigned char* c = ((unsigned char*)pkt)+ntohs(pkt->addroffset); unsigned char* k = (unsigned char*)&key; new_ip6addr(c, &pkt->service_descr.peer, &pkt->service_descr.service_descriptor); /* * Copy the newly generated ip-address to the key backwarts (as only the first part is hashed) */ unsigned int i; for (i = 0; i < 16; i++) k[15-i] = c[i]; uint16_t namelen = strlen((char*)pkt->data+12)+1; struct map_entry* value = GNUNET_malloc(sizeof(struct map_entry) + namelen); char* name = (char*)(value +1); value->namelen = namelen; memcpy(name, pkt->data+12, namelen); memcpy(&value->desc, &pkt->service_descr, sizeof(struct GNUNET_vpn_service_descriptor)); memset(value->additional_ports, 0, 8192); memcpy(&value->hash, &key, sizeof(GNUNET_HashCode)); if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (hashmap, &key)) { GNUNET_CONTAINER_multihashmap_put (hashmap, &key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); value->heap_node = GNUNET_CONTAINER_heap_insert (heap, value, GNUNET_TIME_absolute_get ().abs_value); if (GNUNET_CONTAINER_heap_get_size(heap) > max_mappings) GNUNET_SCHEDULER_add_now(collect_mappings, NULL); } else GNUNET_free(value); list = GNUNET_malloc(htons(pkt->hdr.size) + 2*sizeof(struct answer_packet_list*)); memcpy(&list->pkt, pkt, htons(pkt->hdr.size)); } else if (pkt->subtype == GNUNET_DNS_ANSWER_TYPE_REV) { GNUNET_HashCode key; memset(&key, 0, sizeof key); unsigned char* k = (unsigned char*)&key; unsigned char* s = pkt->data+12; int i = 0; /* Whoever designed the reverse IPv6-lookup is batshit insane */ for (i = 0; i < 16; i++) { unsigned char c1 = s[(4*i)+1]; unsigned char c2 = s[(4*i)+3]; if (c1 <= '9') k[i] = c1 - '0'; else k[i] = c1 - 87; /* 87 is the difference between 'a' and 10 */ if (c2 <= '9') k[i] += 16*(c2 - '0'); else k[i] += 16*(c2 - 87); } struct map_entry* map_entry = GNUNET_CONTAINER_multihashmap_get(hashmap, &key); GNUNET_CONTAINER_heap_update_cost (heap, map_entry->heap_node, GNUNET_TIME_absolute_get ().abs_value); uint16_t offset = ntohs(pkt->addroffset); if (map_entry == NULL) { GNUNET_free(pkt); return; } unsigned short namelen = htons(map_entry->namelen); char* name = (char*)(map_entry + 1); list = GNUNET_malloc(2*sizeof(struct answer_packet_list*) + offset + 2 + ntohs(namelen)); struct answer_packet* rpkt = &list->pkt; /* The offset points to the first byte belonging to the address */ memcpy(rpkt, pkt, offset - 1); rpkt->subtype = GNUNET_DNS_ANSWER_TYPE_IP; rpkt->hdr.size = ntohs(offset + 2 + ntohs(namelen)); memcpy(((char*)rpkt)+offset, &namelen, 2); memcpy(((char*)rpkt)+offset+2, name, ntohs(namelen)); } else if (pkt->subtype == GNUNET_DNS_ANSWER_TYPE_IP) { list = GNUNET_malloc(htons(pkt->hdr.size) + 2*sizeof(struct answer_packet_list*)); memcpy(&list->pkt, pkt, htons(pkt->hdr.size)); } else if (pkt->subtype == GNUNET_DNS_ANSWER_TYPE_REMOTE) { pkt->subtype = GNUNET_DNS_ANSWER_TYPE_IP; GNUNET_HashCode key; memset(&key, 0, sizeof(GNUNET_HashCode)); unsigned char* c = ((unsigned char*)pkt)+ntohs(pkt->addroffset); new_ip6addr_remote(c, pkt->addr, pkt->addrsize); unsigned char* k = (unsigned char*)&key; /* * Copy the newly generated ip-address to the key backwards (as only the first part is used in the hash-table) */ unsigned int i; for (i = 0; i < 16; i++) k[15-i] = c[i]; uint16_t namelen = strlen((char*)pkt->data+12)+1; struct map_entry* value = GNUNET_malloc(sizeof(struct map_entry) + namelen); char* name = (char*)(value +1); value->namelen = namelen; memcpy(name, pkt->data+12, namelen); value->addrlen = pkt->addrsize; memcpy(&value->addr, &pkt->addr, pkt->addrsize); memset(value->additional_ports, 0, 8192); memcpy(&value->hash, &key, sizeof(GNUNET_HashCode)); if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (hashmap, &key)) { GNUNET_CONTAINER_multihashmap_put (hashmap, &key, value, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); value->heap_node = GNUNET_CONTAINER_heap_insert (heap, value, GNUNET_TIME_absolute_get ().abs_value); if (GNUNET_CONTAINER_heap_get_size(heap) > max_mappings) GNUNET_SCHEDULER_add_now(collect_mappings, NULL); } else GNUNET_free(value); list = GNUNET_malloc(htons(pkt->hdr.size) + 2*sizeof(struct answer_packet_list*)); memcpy(&list->pkt, pkt, htons(pkt->hdr.size)); } else { GNUNET_break(0); GNUNET_free(pkt); return; } GNUNET_free(pkt); GNUNET_CONTAINER_DLL_insert_after(answer_proc_head, answer_proc_tail, answer_proc_tail, list); schedule_helper_write(GNUNET_TIME_UNIT_FOREVER_REL, NULL); return; } /** * Sets a bit active in a bitArray. * * @param bitArray memory area to set the bit in * @param bitIdx which bit to set */ void setBit (char *bitArray, unsigned int bitIdx) { size_t arraySlot; unsigned int targetBit; arraySlot = bitIdx / 8; targetBit = (1L << (bitIdx % 8)); bitArray[arraySlot] |= targetBit; } /** * Clears a bit from bitArray. * * @param bitArray memory area to set the bit in * @param bitIdx which bit to unset */ void clearBit (char *bitArray, unsigned int bitIdx) { size_t slot; unsigned int targetBit; slot = bitIdx / 8; targetBit = (1L << (bitIdx % 8)); bitArray[slot] = bitArray[slot] & (~targetBit); } /** * Checks if a bit is active in the bitArray * * @param bitArray memory area to set the bit in * @param bitIdx which bit to test * @return GNUNET_YES if the bit is set, GNUNET_NO if not. */ int testBit (char *bitArray, unsigned int bitIdx) { size_t slot; unsigned int targetBit; slot = bitIdx / 8; targetBit = (1L << (bitIdx % 8)); if (bitArray[slot] & targetBit) return GNUNET_YES; else return GNUNET_NO; } /** * @brief Add the port to the list of additional ports in the map_entry * * @param me the map_entry * @param port the port in host-byte-order */ static void add_additional_port (struct map_entry *me, uint16_t port) { setBit(me->additional_ports, port); } static int receive_udp_back (void *cls, struct GNUNET_MESH_Tunnel* tunnel, void **tunnel_ctx, const struct GNUNET_PeerIdentity *sender, const struct GNUNET_MessageHeader *message, const struct GNUNET_TRANSPORT_ATS_Information *atsi) { GNUNET_HashCode *desc = (GNUNET_HashCode *) (message + 1); struct udp_pkt *pkt = (struct udp_pkt *) (desc + 1); const struct GNUNET_PeerIdentity* other = GNUNET_MESH_get_peer(tunnel); size_t size = sizeof(struct ip6_udp) + ntohs(pkt->len) - 1 - sizeof(struct udp_pkt); struct ip6_udp* pkt6 = alloca(size); GNUNET_assert(pkt6 != NULL); new_ip6addr(pkt6->ip6_hdr.sadr, &other->hashPubKey, desc); GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Relaying calc:%d gnu:%d udp:%d bytes!\n", size, ntohs(message->size), ntohs(pkt->len)); pkt6->shdr.type = htons(GNUNET_MESSAGE_TYPE_VPN_HELPER); pkt6->shdr.size = htons(size); pkt6->tun.flags = 0; pkt6->tun.type = htons(0x86dd); pkt6->ip6_hdr.version = 6; pkt6->ip6_hdr.tclass_h = 0; pkt6->ip6_hdr.tclass_l = 0; pkt6->ip6_hdr.flowlbl = 0; pkt6->ip6_hdr.paylgth = pkt->len; pkt6->ip6_hdr.nxthdr = 0x11; pkt6->ip6_hdr.hoplmt = 0xff; { char* ipv6addr; GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, "vpn", "IPV6ADDR", &ipv6addr)); inet_pton (AF_INET6, ipv6addr, pkt6->ip6_hdr.dadr); GNUNET_free(ipv6addr); } memcpy(&pkt6->udp_hdr, pkt, ntohs(pkt->len)); GNUNET_HashCode* key = address_mapping_exists(pkt6->ip6_hdr.sadr); GNUNET_assert (key != NULL); struct map_entry *me = GNUNET_CONTAINER_multihashmap_get(hashmap, key); GNUNET_CONTAINER_heap_update_cost (heap, me->heap_node, GNUNET_TIME_absolute_get ().abs_value); GNUNET_free(key); GNUNET_assert (me != NULL); GNUNET_assert (me->desc.service_type & htonl(GNUNET_DNS_SERVICE_TYPE_UDP)); if (!port_in_ports(me->desc.ports, pkt6->udp_hdr.spt) && !testBit(me->additional_ports, ntohs(pkt6->udp_hdr.spt))) { add_additional_port(me, ntohs(pkt6->udp_hdr.spt)); } pkt6->udp_hdr.crc = 0; uint32_t sum = 0; sum = calculate_checksum_update(sum, (uint16_t*)&pkt6->ip6_hdr.sadr, 16); sum = calculate_checksum_update(sum, (uint16_t*)&pkt6->ip6_hdr.dadr, 16); uint32_t tmp = (pkt6->udp_hdr.len & 0xffff); sum = calculate_checksum_update(sum, (uint16_t*)&tmp, 4); tmp = htons(((pkt6->ip6_hdr.nxthdr & 0x00ff))); sum = calculate_checksum_update(sum, (uint16_t*)&tmp, 4); sum = calculate_checksum_update(sum, (uint16_t*)&pkt6->udp_hdr, ntohs(pkt->len)); pkt6->udp_hdr.crc = calculate_checksum_end(sum); write_to_helper(pkt6, size); return GNUNET_OK; } static int receive_tcp_back (void *cls, struct GNUNET_MESH_Tunnel* tunnel, void **tunnel_ctx, const struct GNUNET_PeerIdentity *sender, const struct GNUNET_MessageHeader *message, const struct GNUNET_TRANSPORT_ATS_Information *atsi) { GNUNET_HashCode *desc = (GNUNET_HashCode *) (message + 1); struct tcp_pkt *pkt = (struct tcp_pkt *) (desc + 1); const struct GNUNET_PeerIdentity* other = GNUNET_MESH_get_peer(tunnel); size_t pktlen = ntohs(message->size) - sizeof(struct GNUNET_MessageHeader) - sizeof(GNUNET_HashCode); size_t size = pktlen + sizeof(struct ip6_tcp) - 1; struct ip6_tcp* pkt6 = alloca(size); GNUNET_assert(pkt6 != NULL); new_ip6addr(pkt6->ip6_hdr.sadr, &other->hashPubKey, desc); pkt6->shdr.type = htons(GNUNET_MESSAGE_TYPE_VPN_HELPER); pkt6->shdr.size = htons(size); pkt6->tun.flags = 0; pkt6->tun.type = htons(0x86dd); pkt6->ip6_hdr.version = 6; pkt6->ip6_hdr.tclass_h = 0; pkt6->ip6_hdr.tclass_l = 0; pkt6->ip6_hdr.flowlbl = 0; pkt6->ip6_hdr.paylgth = htons(pktlen); pkt6->ip6_hdr.nxthdr = 0x06; pkt6->ip6_hdr.hoplmt = 0xff; { char* ipv6addr; GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, "vpn", "IPV6ADDR", &ipv6addr)); inet_pton (AF_INET6, ipv6addr, pkt6->ip6_hdr.dadr); GNUNET_free(ipv6addr); } memcpy(&pkt6->tcp_hdr, pkt, pktlen); GNUNET_HashCode* key = address_mapping_exists(pkt6->ip6_hdr.sadr); GNUNET_assert (key != NULL); struct map_entry *me = GNUNET_CONTAINER_multihashmap_get(hashmap, key); GNUNET_CONTAINER_heap_update_cost (heap, me->heap_node, GNUNET_TIME_absolute_get ().abs_value); GNUNET_free(key); GNUNET_assert (me != NULL); GNUNET_assert (me->desc.service_type & htonl(GNUNET_DNS_SERVICE_TYPE_TCP)); pkt6->tcp_hdr.crc = 0; uint32_t sum = 0; uint32_t tmp; sum = calculate_checksum_update (sum, (uint16_t *) & pkt6->ip6_hdr.sadr, 16); sum = calculate_checksum_update (sum, (uint16_t *) & pkt6->ip6_hdr.dadr, 16); tmp = htonl(pktlen); sum = calculate_checksum_update (sum, (uint16_t *) & tmp, 4); tmp = htonl (((pkt6->ip6_hdr.nxthdr & 0x000000ff))); sum = calculate_checksum_update (sum, (uint16_t *) & tmp, 4); sum = calculate_checksum_update (sum, (uint16_t *) & pkt6->tcp_hdr, ntohs (pkt6->ip6_hdr.paylgth)); pkt6->tcp_hdr.crc = calculate_checksum_end (sum); write_to_helper(pkt6, size); return GNUNET_OK; } /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param cfg_ configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg_) { const static struct GNUNET_MESH_MessageHandler handlers[] = { {receive_udp_back, GNUNET_MESSAGE_TYPE_SERVICE_UDP_BACK, 0}, {receive_tcp_back, GNUNET_MESSAGE_TYPE_SERVICE_TCP_BACK, 0}, {NULL, 0, 0} }; mesh_handle = GNUNET_MESH_connect(cfg_, NULL, NULL, handlers, NULL); cfg = cfg_; restart_hijack = 0; hashmap = GNUNET_CONTAINER_multihashmap_create(65536); heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); GNUNET_CONFIGURATION_get_value_number (cfg, "vpn", "MAX_MAPPINGg", &max_mappings); udp_connections = GNUNET_CONTAINER_multihashmap_create(65536); GNUNET_SCHEDULER_TaskIdentifier conn_task = GNUNET_SCHEDULER_add_now (connect_to_service_dns, NULL); GNUNET_SCHEDULER_add_after (conn_task, start_helper_and_schedule, NULL); GNUNET_SCHEDULER_add_delayed(GNUNET_TIME_UNIT_FOREVER_REL, &cleanup, cls); } /** * The main function to obtain template from gnunetd. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { static const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; return (GNUNET_OK == GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-vpn", gettext_noop ("help text"), options, &run, NULL)) ? ret : 1; } /* end of gnunet-daemon-vpn.c */