static void selected_row_changed(GtkCTree *ctree) { int row; if (selected_record) { search_gui_unref_record(selected_record); selected_record = NULL; } row = clist_get_cursor_row(GTK_CLIST(ctree)); if (row >= 0) { GtkCTreeNode *node; node = gtk_ctree_node_nth(GTK_CTREE(ctree), row); selected_record = search_gui_get_record(ctree, GTK_CTREE_NODE(node)); if (selected_record) { search_gui_ref_record(selected_record); } } if (row_selected_ev) { cq_resched(row_selected_ev, ROW_SELECT_TIMEOUT); } else { row_selected_ev = cq_main_insert(ROW_SELECT_TIMEOUT, row_selected_expire, NULL); } }
/** * Send time synchronization request to specified node. * * When node_id is non-zero, it refers to the connected node to which * we're sending the time synchronization request. */ void tsync_send(struct gnutella_node *n, const struct nid *node_id) { struct tsync *ts; g_return_if_fail(n->port != 0); if (!NODE_IS_WRITABLE(n)) return; WALLOC(ts); ts->magic = TSYNC_MAGIC; tm_now_exact(&ts->sent); ts->sent.tv_sec = clock_loc2gmt(ts->sent.tv_sec); ts->node_id = nid_ref(node_id); ts->udp = booleanize(NODE_IS_UDP(n)); /* * As far as time synchronization goes, we must get the reply within * the next TSYNC_EXPIRE_MS millisecs. */ ts->expire_ev = cq_main_insert(TSYNC_EXPIRE_MS, tsync_expire, ts); hevset_insert(tsync_by_time, ts); vmsg_send_time_sync_req(n, GNET_PROPERTY(ntp_detected), &ts->sent); }
/** * Start watchdog timer. */ static void wd_start(watchdog_t *wd) { watchdog_check(wd); /* watchdog period given in seconds */ wd->last_kick = 0; wd->ev = cq_main_insert(wd->period * 1000, wd_expired, wd); }
/** * Start a "discovery rpc" sequence. * * @param np existing NAT-PMP gateway (NULL if unknown yet) * @param retries amount of retries before timeouting * @param cb callback to invoke on completion / timeout * @param arg user-defined callback argument */ static void natpmp_rpc_discover(natpmp_t *np, unsigned retries, natpmp_discover_cb_t cb, void *arg) { struct natpmp_rpc *rd; host_addr_t addr; pmsg_t *mb; if (np != NULL) { natpmp_check(np); addr = np->gateway; } else { /* * If we can't determine the default gateway, we can't go much further. * We notify of the discovery failure synchronously. */ if (0 != getgateway(&addr)) { if (GNET_PROPERTY(natpmp_debug)) g_warning("NATPMP cannot find default gateway"); (*cb)(FALSE, NULL, arg); return; } else { if (GNET_PROPERTY(natpmp_debug)) { g_info("NATPMP gateway is %s", host_addr_to_string(addr)); } } } /* * Build the discovery request: * * 0 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Vers = 0 | OP = 0 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ mb = pmsg_new(PMSG_P_DATA, NULL, 2); pmsg_write_u8(mb, NATPMP_VERSION); pmsg_write_u8(mb, NATPMP_OP_DISCOVERY); /* * Initiate asynchronous iteration discovery. */ rd = natpmp_rpc_alloc(np, addr, NATPMP_OP_DISCOVERY, mb); rd->cb.discovery = cb; rd->arg = arg; if (retries != 0) rd->retries = MIN(retries, rd->retries); cq_main_insert(1, natpmp_rpc_iterate, rd); }
/** * Callout queue callback fired when waiting event times out. */ static void wq_timed_out(cqueue_t *cq, void *arg) { wq_event_t *we = arg; hash_list_t *hl; wq_status_t status; wq_event_check(we); g_assert(we->tm != NULL); cq_zero(cq, &we->tm->timeout_ev); hl = htable_lookup(waitqueue, we->key); g_assert(hl != NULL); /* * Invoke the callback with the sentinel data signalling a timeout. */ status = (*we->cb)(we->arg, WQ_TIMED_OUT); /* * When the callback returns WQ_SLEEP, we re-instantiate the initial * timeout. * * Otherwise the event is discarded (removed from the wait queue) and * the callback will never be invoked again for this event. */ switch (status) { case WQ_SLEEP: we->tm->timeout_ev = cq_main_insert(we->tm->delay, wq_timed_out, we); return; case WQ_EXCLUSIVE: s_critical("weird status WQ_EXCLUSIVE on timeout invocation of %s()", stacktrace_function_name(we->cb)); /* FALL THROUGH */ case WQ_REMOVE: hash_list_remove(hl, we); /* * Cleanup the table if it ends-up being empty. */ if (0 == hash_list_length(hl)) { hash_list_free(&hl); htable_remove(waitqueue, we->key); } wq_event_free(we); return; } g_assert_not_reached(); }
/** * Record a waiting event, limited in time by a timeout. * * If no wq_wakeup() occurs before the timeout limit, a wq_wakeup() is forced * with the WQ_TIMED_OUT value. The callback must be prepared to handle * this value explicitly. * * @param key waiting key * @param delay delay in ms before timeout occurs * @param cb callback to invoke on wakeup * @param arg additional callback argument * * @return the registered event, whose reference must be kept if it is meant * to be cancelled. */ wq_event_t * wq_sleep_timeout(const void *key, int delay, wq_callback_t cb, void *arg) { wq_event_t *we; we = wq_sleep(key, cb, arg); WALLOC(we->tm); we->tm->delay = delay; we->tm->timeout_ev = cq_main_insert(delay, wq_timed_out, we); return we; }
/** * Create a value for the `used' table. */ static struct used_val * val_create(const host_addr_t addr, int precision) { struct used_val *v; g_assert(is_host_addr(addr)); WALLOC(v); v->addr = addr; v->precision = precision; v->cq_ev = cq_main_insert(REUSE_DELAY * 1000, val_destroy, v); return v; }
/** * HTTP async callback, invoked on errors. */ static void soap_error_ind(http_async_t *ha, http_errtype_t type, void *val) { soap_rpc_t *sr = http_async_get_opaque(ha); soap_error_t err = SOAP_E_OK; soap_rpc_check(sr); if (GNET_PROPERTY(soap_debug)) { http_async_log_error_dbg(ha, type, val, "SOAP", GNET_PROPERTY(soap_debug) > 1); } if (HTTP_ASYNC_ERROR == type) { switch (GPOINTER_TO_INT(val)) { case HTTP_ASYNC_CANCELLED: /* * Retry with M-POST if cancelled with sr->retry set to TRUE. */ if (sr->retry) { g_assert(NULL == sr->delay_ev); sr->delay_ev = cq_main_insert(1, soap_rpc_launch, sr); if (GNET_PROPERTY(soap_debug) > 1) { g_message("SOAP \"%s\" at \"%s\": retrying with M-POST", sr->action, sr->url); } } break; /* No callback on explicit user cancel */ case HTTP_ASYNC_DATA2BIG: err = SOAP_E_DATA2BIG; break; case HTTP_ASYNC_CONN_TIMEOUT: case HTTP_ASYNC_TIMEOUT: err = SOAP_E_TIMEOUT; break; default: err = SOAP_E_TRANSPORT; break; } } else { err = SOAP_E_TRANSPORT; } if (err != SOAP_E_OK) soap_error(sr, err); }
/** * Token key rotating event. */ static void sectoken_rotate(cqueue_t *cq, void *obj) { size_t i; sectoken_gen_t *stg = obj; sectoken_gen_check(stg); cq_zero(cq, &stg->rotate_ev); stg->rotate_ev = cq_main_insert(stg->refresh * 1000, sectoken_rotate, stg); for (i = 0; i < stg->keycnt - 1; i++) stg->keys[i + 1] = stg->keys[i]; /* 0 is most recent key */ random_strong_bytes(&stg->keys[0], sizeof(stg->keys[0])); }
/** * Send an UDP ping to the host cache. */ static void uhc_send_ping(void) { g_assert(uhc_connecting); guid_random_muid(&uhc_ctx.muid); if (udp_send_ping(&uhc_ctx.muid, uhc_ctx.addr, uhc_ctx.port, TRUE)) { if (GNET_PROPERTY(bootstrap_debug) || GNET_PROPERTY(log_uhc_pings_tx)) { g_debug("BOOT sent UDP SCP ping #%s to %s:%u", guid_hex_str(&uhc_ctx.muid), uhc_ctx.host, uhc_ctx.port); } /* * Give GUI feedback. */ { char msg[256]; str_bprintf(msg, sizeof msg, _("Sent ping to UDP host cache %s:%u"), uhc_ctx.host, uhc_ctx.port); gcu_statusbar_message(msg); } /* * Arm a timer to see whether we should not try to ping another * host cache if we don't get a timely reply. */ g_assert(uhc_ctx.timeout_ev == NULL); uhc_ctx.timeout_ev = cq_main_insert(UHC_TIMEOUT, uhc_ping_timeout, NULL); } else { g_warning("BOOT failed to send UDP SCP to %s", host_addr_port_to_string(uhc_ctx.addr, uhc_ctx.port)); uhc_try_next(); } }
/** * Create a new security token generator. */ sectoken_gen_t * sectoken_gen_new(size_t keys, time_delta_t refresh) { sectoken_gen_t *stg; size_t i; g_assert(size_is_positive(keys)); WALLOC0(stg); stg->magic = SECTOKEN_GEN_MAGIC; stg->keys = walloc(keys * sizeof stg->keys[0]); stg->keycnt = keys; stg->refresh = refresh; for (i = 0; i < stg->keycnt; i++) random_bytes(&stg->keys[i], sizeof(stg->keys[0])); stg->rotate_ev = cq_main_insert(refresh * 1000, sectoken_rotate, stg); return stg; }
/** * Request port mapping or deletion. * * @param np the NAT-PMP gateway to which we publish the mapping * @param proto protocol type (TCP or UDP) * @param port internal port, to be mapped to same external port * @param lease requested lease time (0 for deletion) * @param cb completion callback * @param arg additional callback argument */ static void natpmp_rpc_map(natpmp_t *np, enum upnp_map_proto proto, uint16 port, time_delta_t lease, natpmp_map_cb_t cb, void *arg) { pmsg_t *mb; enum natpmp_op op = NATPMP_OP_INVALID; struct natpmp_rpc *rd; natpmp_check(np); switch (proto) { case UPNP_MAP_TCP: op = NATPMP_OP_MAP_TCP; break; case UPNP_MAP_UDP: op = NATPMP_OP_MAP_UDP; break; case UPNP_MAP_MAX: g_assert_not_reached(); } g_assert(NATPMP_OP_INVALID != op); /* * Creating the mapping message. */ mb = natpmp_build_mapping(op, port, lease); /* * Initiate asynchronous publishing only when there is a user callback. */ rd = natpmp_rpc_alloc(np, np->gateway, op, mb); rd->cb.map = cb; rd->arg = arg; rd->iport = port; /* We only accept same internal and external ports */ if (NULL == cb) { natpmp_rpc_iterate(NULL, rd); /* Synchronous */ } else { cq_main_insert(1, natpmp_rpc_iterate, rd); } }
/** * Start a G2 RPC with the specified host. * * @param host the host to which message is sent * @param mb the message to send * @param cb if non-NULL, callback to invoke on reply or timeout * @param arg additional callback argument * @param timeout amount of seconds before timeout * * @return TRUE if we initiated the RPC, FALSE if another of the same * kind was already in progress with the host. */ bool g2_rpc_launch(const gnet_host_t *host, pmsg_t *mb, g2_rpc_cb_t cb, void *arg, unsigned timeout) { struct g2_rpc *gr; struct g2_rpc_key key; gnutella_node_t *n; key.type = g2_msg_type_mb(mb); key.addr = gnet_host_get_addr(host); /* * Because there is no MUID in /PI and /QKR messages, we cannot use that * as a key to detect the RPC reply. Therefore, we use the message type * and the IP address of the host. When a /PO or /QKA comes back, we'll * be able to see whether we had a pending RPC from that host for that * type of transaction. * * The downside is that we can only have one pending RPC at a time of * a given kind towards a given IP address. We don't use the port in * the key because we cannot assume the reply will come from the same port * we sent the message to, if the remote host is behind NAT or does not * use its listening UDP socket to reply. */ if (hevset_contains(g2_rpc_pending, &key)) { if (GNET_PROPERTY(g2_rpc_debug)) { g_debug("%s(): cannot issue /%s RPC to %s: concurrent request", G_STRFUNC, g2_msg_type_name(key.type), gnet_host_to_string(host)); } return FALSE; } /* * Make sure the node is valid. */ n = node_udp_g2_get_addr_port(key.addr, gnet_host_get_port(host)); if (NULL == n) { if (GNET_PROPERTY(g2_rpc_debug)) { g_debug("%s(): cannot issue /%s RPC to %s: cannot get G2 node", G_STRFUNC, g2_msg_type_name(key.type), gnet_host_to_string(host)); } return FALSE; /* Invalid node, or G2 disabled */ } /* * Good, we can issue the RPC. */ WALLOC(gr); gr->magic = G2_RPC_MAGIC; gr->key = key; /* struct copy */ gr->cb = cb; gr->arg = arg; gr->timeout_ev = cq_main_insert(timeout * 1000, g2_rpc_timeout, gr); hevset_insert(g2_rpc_pending, gr); if (GNET_PROPERTY(g2_rpc_debug) > 1) { g_debug("%s(): issuing /%s RPC to %s, timeout %u sec%s", G_STRFUNC, g2_msg_type_name(key.type), gnet_host_to_string(host), timeout, plural(timeout)); } /* * Do not send RPCs reliably: this can cause problems if we don't receive * the ACK backm yet the message was received and processed remotely: the * remote host will send a reply back and the message will still appear to * be "unsent" locally. * * Furthermore, this alleviates the need for the remote side to actually * acknowledge the request: targeted hosts can be busy so it's best to * make the RPC "unreliable" to limit processing and bandwidth requirements. */ g2_node_send(n, mb); return TRUE; }
static void install_periodic_kball(int period) { kball_ev = cq_main_insert(period * 1000, keys_periodic_kball, NULL); }
/** * Initiate a SOAP remote procedure call. * * Call will be launched asynchronously, not immediately upon return so that * callbacks are never called on the same stack frame and to allow further * options to be set on the handle before the call begins. * * Initially the request is sent as a regular POST. It is possible to force * the usage of the HTTP Extension Framework by using the SOAP_RPC_O_MAN_FORCE * option, in which case an M-POST will be sent with the proper SOAP Man: * header. Finally, automatic retry of the request can be requested via the * SOAP_RPC_O_MAN_RETRY option: it will start with POST and switch to M-POST * on 405 or 510 errors. * * @param url the HTTP URL to contact for the RPC * @param action the SOAP action to perform * @param maxlen maximum length of data we accept to receive * @param options user-supplied options * @param xn SOAP RPC data payload (XML tree root, will be freed) * @param soap_ns requested SOAP namespace prefix, NULL to use default * @param reply_cb callback to invoke when we get a reply * @param error_cb callback to invoke on error * @param arg additional user-defined callback parameter * * @return a SOAP RPC handle, NULL if the request cannot be initiated (XML * payload too large). In any case, the XML tree is freed. */ soap_rpc_t * soap_rpc(const char *url, const char *action, size_t maxlen, guint32 options, xnode_t *xn, const char *soap_ns, soap_reply_cb_t reply_cb, soap_error_cb_t error_cb, void *arg) { soap_rpc_t *sr; xnode_t *root, *body; pmsg_t *mb; ostream_t *os; gboolean failed = FALSE; g_assert(url != NULL); g_assert(action != NULL); /* * Create the SOAP XML request. */ root = xnode_new_element(NULL, SOAP_NAMESPACE, SOAP_X_ENVELOPE); xnode_add_namespace(root, soap_ns ? soap_ns : "SOAP", SOAP_NAMESPACE); xnode_prop_ns_set(root, SOAP_NAMESPACE, SOAP_X_ENC_STYLE, SOAP_ENCODING); body = xnode_new_element(root, SOAP_NAMESPACE, SOAP_X_BODY); xnode_add_child(body, xn); /* * Serialize the XML tree to a PDU message buffer. */ mb = pmsg_new(PMSG_P_DATA, NULL, SOAP_MAX_PAYLOAD); os = ostream_open_pmsg(mb); xfmt_tree(root, os, XFMT_O_NO_INDENT); if (!ostream_close(os)) { failed = TRUE; g_warning("SOAP unable to serialize payload within %d bytes", SOAP_MAX_PAYLOAD); if (GNET_PROPERTY(soap_debug) > 1) xfmt_tree_dump(root, stderr); } /* * Free the XML tree, including the supplied user nodes. */ xnode_tree_free(root); if (failed) { pmsg_free(mb); return NULL; } /* * Serialization of the XML payload was successful, prepare the * asynchronous SOAP request. */ sr = soap_rpc_alloc(); sr->url = atom_str_get(url); sr->action = atom_str_get(action); sr->maxlen = maxlen; sr->content_len = maxlen; /* Until we see a Content-Length */ sr->options = options; sr->mb = mb; sr->reply_cb = reply_cb; sr->error_cb = error_cb; sr->arg = arg; /* * Make sure the error callback is not called synchronously, and give * them time to supply other options after creating the request before * it starts. */ sr->delay_ev = cq_main_insert(1, soap_rpc_launch, sr); return sr; }
/** * Initiate an UDP RPC transaction. * * The message held in ``data'' is sent to the specified address and port. * Upon reception of a reply from that host, the callback is invoked. * If no reply is received after some time, the callaback is also invoked. * * @param what type of RPC, for logging (static string) * @param addr address where RPC should be sent to * @param port port where RPC should be sent to * @param data message data to send * @param len length of data to send * @param timeout timeout in milliseconds to get a reply * @param cb callback to invoke on reply or timeout * @param arg additionnal callback argument * * @return 0 if OK, -1 if we could not initiate the RPC, with errno set. */ int urpc_send(const char *what, host_addr_t addr, uint16 port, const void *data, size_t len, unsigned long timeout, urpc_cb_t cb, void *arg) { struct urpc_cb *ucb; struct gnutella_socket *s; host_addr_t bind_addr = zero_host_addr; gnet_host_t to; ssize_t r; /* * Create anonymous socket to send/receive the RPC. */ switch (host_addr_net(addr)) { case NET_TYPE_IPV4: bind_addr = ipv4_unspecified; break; case NET_TYPE_IPV6: bind_addr = ipv6_unspecified; break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: g_assert_not_reached(); } s = socket_udp_listen(bind_addr, 0, urpc_received); if (NULL == s) { if (GNET_PROPERTY(udp_debug)) { g_warning("unable to create anonymous UDP %s socket for %s RPC: %m", net_type_to_string(host_addr_net(bind_addr)), what); } return -1; } /* * Send the message. */ gnet_host_set(&to, addr, port); r = (s->wio.sendto)(&s->wio, &to, data, len); /* * Reset errno if there was no "real" error to prevent getting a * bogus and possibly misleading error message later. */ if ((ssize_t) -1 == r) { if (GNET_PROPERTY(udp_debug)) { g_warning("unable to send UDP %s RPC to %s: %m", what, host_addr_port_to_string(addr, port)); } } else { errno = 0; } if (len != UNSIGNED(r)) { if ((ssize_t) -1 != r) { if (GNET_PROPERTY(udp_debug)) { g_warning("unable to send whole %zu-byte UDP %s RPC to %s: " "only sent %zu byte%s", len, what, host_addr_port_to_string(addr, port), r, 1 == r ? "" : "s"); } } socket_free_null(&s); errno = EIO; return -1; } /* * Make sure socket_udp_event() will only process replies one at a time * since we're going to close the anonymous UDP socket as soon as we * get a reply. */ socket_set_single(s, TRUE); /* * Message was sent, wait for the answer. */ WALLOC(ucb); ucb->magic = URPC_CB_MAGIC; ucb->addr = addr; ucb->port = port; ucb->s = s; ucb->cb = cb; ucb->arg = arg; ucb->timeout_ev = cq_main_insert(timeout, urpc_timed_out, ucb); ucb->what = what; htable_insert(pending, s, ucb); return 0; }