/* * This file Copyright (C) Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #define __LIBTRANSMISSION_ANNOUNCER_MODULE___ #include /* memcpy(), memset() */ #include #include #include #include "transmission.h" #include "announcer.h" #include "announcer-common.h" #include "crypto.h" /* tr_cryptoRandBuf() */ #include "peer-io.h" #include "peer-mgr.h" /* tr_peerMgrCompactToPex() */ #include "ptrarray.h" #include "tr-udp.h" #include "utils.h" #define dbgmsg( name, ... ) \ if( tr_deepLoggingIsActive( ) ) do { \ tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \ } while( 0 ) /**** ***** ****/ static void tau_sockaddr_setport( struct sockaddr * sa, tr_port port ) { if( sa->sa_family == AF_INET ) ((struct sockaddr_in *)sa)->sin_port = htons(port); else if (sa->sa_family == AF_INET6) ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); } static int tau_sendto( tr_session * session, struct evutil_addrinfo * ai, tr_port port, const void * buf, size_t buflen ) { int sockfd; if( ai->ai_addr->sa_family == AF_INET ) sockfd = session->udp_socket; else if( ai->ai_addr->sa_family == AF_INET6 ) sockfd = session->udp6_socket; else sockfd = -1; if( sockfd < 0 ) { errno = EAFNOSUPPORT; return -1; } tau_sockaddr_setport( ai->ai_addr, port ); return sendto( sockfd, buf, buflen, 0, ai->ai_addr, ai->ai_addrlen ); } /**** ***** ****/ static uint32_t evbuffer_read_ntoh_32( struct evbuffer * buf ) { uint32_t val; evbuffer_remove( buf, &val, sizeof( uint32_t ) ); return ntohl( val ); } static uint64_t evbuffer_read_ntoh_64( struct evbuffer * buf ) { uint64_t val; evbuffer_remove( buf, &val, sizeof( uint64_t ) ); return tr_ntohll( val ); } /**** ***** ****/ typedef uint64_t tau_connection_t; enum { TAU_CONNECTION_TTL_SECS = 60 }; typedef uint32_t tau_transaction_t; static tau_transaction_t tau_transaction_new( void ) { tau_transaction_t tmp; tr_cryptoRandBuf( &tmp, sizeof( tau_transaction_t ) ); return tmp; } /* used in the "action" field of a request */ typedef enum { TAU_ACTION_CONNECT = 0, TAU_ACTION_ANNOUNCE = 1, TAU_ACTION_SCRAPE = 2, TAU_ACTION_ERROR = 3 } tau_action_t; static bool is_tau_response_message( int action, int msglen ) { if( action == TAU_ACTION_CONNECT ) return msglen == 16; if( action == TAU_ACTION_ANNOUNCE ) return msglen >= 20; if( action == TAU_ACTION_SCRAPE ) return msglen >= 20; if( action == TAU_ACTION_ERROR ) return msglen >= 8; return false; } enum { TAU_REQUEST_TTL = 60 }; /**** ***** ***** SCRAPE ***** ****/ struct tau_scrape_request { void * payload; size_t payload_len; time_t sent_at; time_t created_at; tau_transaction_t transaction_id; tr_scrape_response response; tr_scrape_response_func * callback; void * user_data; }; static struct tau_scrape_request * tau_scrape_request_new( const tr_scrape_request * in, tr_scrape_response_func callback, void * user_data ) { int i; struct evbuffer * buf; struct tau_scrape_request * req; const tau_transaction_t transaction_id = tau_transaction_new( ); /* build the payload */ buf = evbuffer_new( ); evbuffer_add_hton_32( buf, TAU_ACTION_SCRAPE ); evbuffer_add_hton_32( buf, transaction_id ); for( i=0; iinfo_hash_count; ++i ) evbuffer_add( buf, in->info_hash[i], SHA_DIGEST_LENGTH ); /* build the tau_scrape_request */ req = tr_new0( struct tau_scrape_request, 1 ); req->created_at = tr_time( ); req->transaction_id = transaction_id; req->callback = callback; req->user_data = user_data; req->response.url = tr_strdup( in->url ); req->response.row_count = in->info_hash_count; req->payload_len = evbuffer_get_length( buf ); req->payload = tr_memdup( evbuffer_pullup( buf, -1 ), req->payload_len ); for( i=0; iresponse.row_count; ++i ) memcpy( req->response.rows[i].info_hash, in->info_hash[i], SHA_DIGEST_LENGTH ); /* cleanup */ evbuffer_free( buf ); return req; } static void tau_scrape_request_free( struct tau_scrape_request * req ) { tr_free( req->response.errmsg ); tr_free( req->response.url ); tr_free( req->payload ); tr_free( req ); } static void tau_scrape_request_finished( const struct tau_scrape_request * request ) { if( request->callback != NULL ) request->callback( &request->response, request->user_data ); } static void tau_scrape_request_fail( struct tau_scrape_request * request, bool did_connect, bool did_timeout, const char * errmsg ) { request->response.did_connect = did_connect; request->response.did_timeout = did_timeout; request->response.errmsg = tr_strdup( errmsg ); tau_scrape_request_finished( request ); } static void on_scrape_response( struct tau_scrape_request * request, tau_action_t action, struct evbuffer * buf ) { request->response.did_connect = true; request->response.did_timeout = false; if( action == TAU_ACTION_SCRAPE ) { int i; for( i=0; iresponse.row_count; ++i ) { struct tr_scrape_response_row * row; if( evbuffer_get_length( buf ) < ( sizeof( uint32_t ) * 3 ) ) break; row = &request->response.rows[i]; row->seeders = evbuffer_read_ntoh_32( buf ); row->downloads = evbuffer_read_ntoh_32( buf ); row->leechers = evbuffer_read_ntoh_32( buf ); } tau_scrape_request_finished( request ); } else { char * errmsg; const size_t buflen = evbuffer_get_length( buf ); if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) ) errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen ); else errmsg = tr_strdup( _( "Unknown error" ) ); tau_scrape_request_fail( request, true, false, errmsg ); tr_free( errmsg ); } } /**** ***** ***** ANNOUNCE ***** ****/ struct tau_announce_request { void * payload; size_t payload_len; time_t created_at; time_t sent_at; tau_transaction_t transaction_id; tr_announce_response response; tr_announce_response_func * callback; void * user_data; }; typedef enum { /* used in the "event" field of an announce request */ TAU_ANNOUNCE_EVENT_NONE = 0, TAU_ANNOUNCE_EVENT_COMPLETED = 1, TAU_ANNOUNCE_EVENT_STARTED = 2, TAU_ANNOUNCE_EVENT_STOPPED = 3 } tau_announce_event; static tau_announce_event get_tau_announce_event( tr_announce_event e ) { switch( e ) { case TR_ANNOUNCE_EVENT_COMPLETED: return TAU_ANNOUNCE_EVENT_COMPLETED; case TR_ANNOUNCE_EVENT_STARTED: return TAU_ANNOUNCE_EVENT_STARTED; case TR_ANNOUNCE_EVENT_STOPPED: return TAU_ANNOUNCE_EVENT_STOPPED; default: return TAU_ANNOUNCE_EVENT_NONE; } } static struct tau_announce_request * tau_announce_request_new( const tr_announce_request * in, tr_announce_response_func callback, void * user_data ) { struct evbuffer * buf; struct tau_announce_request * req; const tau_transaction_t transaction_id = tau_transaction_new( ); /* build the payload */ buf = evbuffer_new( ); evbuffer_add_hton_32( buf, TAU_ACTION_ANNOUNCE ); evbuffer_add_hton_32( buf, transaction_id ); evbuffer_add ( buf, in->info_hash, SHA_DIGEST_LENGTH ); evbuffer_add ( buf, in->peer_id, PEER_ID_LEN ); evbuffer_add_hton_64( buf, in->down ); evbuffer_add_hton_64( buf, in->left ); evbuffer_add_hton_64( buf, in->up ); evbuffer_add_hton_32( buf, get_tau_announce_event( in->event ) ); evbuffer_add_hton_32( buf, 0 ); evbuffer_add_hton_32( buf, in->key ); evbuffer_add_hton_32( buf, in->numwant ); evbuffer_add_hton_16( buf, in->port ); /* build the tau_announce_request */ req = tr_new0( struct tau_announce_request, 1 ); req->created_at = tr_time( ); req->transaction_id = transaction_id; req->callback = callback; req->user_data = user_data; req->payload_len = evbuffer_get_length( buf ); req->payload = tr_memdup( evbuffer_pullup( buf, -1 ), req->payload_len ); memcpy( req->response.info_hash, in->info_hash, SHA_DIGEST_LENGTH ); evbuffer_free( buf ); return req; } static void tau_announce_request_free( struct tau_announce_request * req ) { tr_free( req->response.tracker_id_str ); tr_free( req->response.warning ); tr_free( req->response.errmsg ); tr_free( req->response.pex6 ); tr_free( req->response.pex ); tr_free( req->payload ); tr_free( req ); } static void tau_announce_request_finished( const struct tau_announce_request * request ) { if( request->callback != NULL ) request->callback( &request->response, request->user_data ); } static void tau_announce_request_fail( struct tau_announce_request * request, bool did_connect, bool did_timeout, const char * errmsg ) { request->response.did_connect = did_connect; request->response.did_timeout = did_timeout; request->response.errmsg = tr_strdup( errmsg ); tau_announce_request_finished( request ); } static void on_announce_response( struct tau_announce_request * request, tau_action_t action, struct evbuffer * buf ) { const size_t buflen = evbuffer_get_length( buf ); request->response.did_connect = true; request->response.did_timeout = false; if( ( action == TAU_ACTION_ANNOUNCE ) && ( buflen >= 3*sizeof(uint32_t) ) ) { tr_announce_response * resp = &request->response; resp->interval = evbuffer_read_ntoh_32( buf ); resp->leechers = evbuffer_read_ntoh_32( buf ); resp->seeders = evbuffer_read_ntoh_32( buf ); resp->pex = tr_peerMgrCompactToPex( evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ), NULL, 0, &request->response.pex_count ); tau_announce_request_finished( request ); } else { char * errmsg; if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) ) errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen ); else errmsg = tr_strdup( _( "Unknown error" ) ); tau_announce_request_fail( request, true, false, errmsg ); tr_free( errmsg ); } } /**** ***** ***** TRACKERS ***** ****/ struct tau_tracker { tr_session * session; char * key; char * host; int port; bool is_asking_dns; struct evutil_addrinfo * addr; time_t addr_expiration_time; time_t connecting_at; time_t connection_expiration_time; tau_connection_t connection_id; tau_transaction_t connection_transaction_id; time_t close_at; tr_ptrArray announces; tr_ptrArray scrapes; }; static void tau_tracker_upkeep( struct tau_tracker * ); static void tau_tracker_free( struct tau_tracker * t ) { if( t->addr ) evutil_freeaddrinfo( t->addr ); tr_ptrArrayDestruct( &t->announces, (PtrArrayForeachFunc)tau_announce_request_free ); tr_ptrArrayDestruct( &t->scrapes, (PtrArrayForeachFunc)tau_scrape_request_free ); tr_free( t->host ); tr_free( t->key ); tr_free( t ); } static void tau_tracker_fail_all( struct tau_tracker * tracker, bool did_connect, bool did_timeout, const char * errmsg ) { int i; int n; tr_ptrArray * reqs; /* fail all the scrapes */ reqs = &tracker->scrapes; for( i=0, n=tr_ptrArraySize(reqs); iannounces; for( i=0, n=tr_ptrArraySize(reqs); iis_asking_dns = false; if ( errcode ) { char * errmsg = tr_strdup_printf( _( "DNS Lookup failed: %s" ), evdns_err_to_string( errcode ) ); dbgmsg( tracker->key, "%s", errmsg ); tau_tracker_fail_all( tracker, false, false, errmsg ); tr_free( errmsg ); } else { dbgmsg( tracker->key, "DNS lookup succeeded" ); tracker->addr = addr; tracker->addr_expiration_time = tr_time() + (60*60); /* one hour */ tau_tracker_upkeep( tracker ); } } static void tau_tracker_send_request( struct tau_tracker * tracker, const void * payload, size_t payload_len ) { struct evbuffer * buf = evbuffer_new( ); dbgmsg( tracker->key, "sending request w/connection id %"PRIu64"\n", tracker->connection_id ); evbuffer_add_hton_64( buf, tracker->connection_id ); evbuffer_add_reference( buf, payload, payload_len, NULL, NULL ); tau_sendto( tracker->session, tracker->addr, tracker->port, evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ) ); evbuffer_free( buf ); } static void tau_tracker_send_reqs( struct tau_tracker * tracker ) { int i, n; tr_ptrArray * reqs; const time_t now = tr_time( ); assert( tracker->is_asking_dns == false ); assert( tracker->connecting_at == 0 ); assert( tracker->addr != NULL ); assert( tracker->connection_expiration_time > now ); reqs = &tracker->announces; for( i=0, n=tr_ptrArraySize(reqs); isent_at ) { dbgmsg( tracker->key, "sending announce req %p", req ); req->sent_at = now; tau_tracker_send_request( tracker, req->payload, req->payload_len ); if( req->callback == NULL ) { tau_announce_request_free( req ); tr_ptrArrayRemove( reqs, i ); --i; --n; } } } reqs = &tracker->scrapes; for( i=0, n=tr_ptrArraySize(reqs); isent_at ) { dbgmsg( tracker->key, "sending scrape req %p", req ); req->sent_at = now; tau_tracker_send_request( tracker, req->payload, req->payload_len ); if( req->callback == NULL ) { tau_scrape_request_free( req ); tr_ptrArrayRemove( reqs, i ); --i; --n; } } } } static void on_tracker_connection_response( struct tau_tracker * tracker, tau_action_t action, struct evbuffer * buf ) { const time_t now = tr_time( ); tracker->connecting_at = 0; tracker->connection_transaction_id = 0; if( action == TAU_ACTION_CONNECT ) { tracker->connection_id = evbuffer_read_ntoh_64( buf ); tracker->connection_expiration_time = now + TAU_CONNECTION_TTL_SECS; dbgmsg( tracker->key, "Got a new connection ID from tracker: %"PRIu64, tracker->connection_id ); } else { char * errmsg; const size_t buflen = buf ? evbuffer_get_length( buf ) : 0; if( ( action == TAU_ACTION_ERROR ) && ( buflen > 0 ) ) errmsg = tr_strndup( evbuffer_pullup( buf, -1 ), buflen ); else errmsg = tr_strdup( _( "Connection failed" ) ); dbgmsg( tracker->key, "%s", errmsg ); tau_tracker_fail_all( tracker, true, false, errmsg ); tr_free( errmsg ); } tau_tracker_upkeep( tracker ); } static void tau_tracker_timeout_reqs( struct tau_tracker * tracker ) { int i, n; tr_ptrArray * reqs; const time_t now = time( NULL ); const bool cancel_all = tracker->close_at && ( tracker->close_at <= now ); if( tracker->connecting_at && ( tracker->connecting_at + TAU_REQUEST_TTL < now ) ) { on_tracker_connection_response( tracker, TAU_ACTION_ERROR, NULL ); } reqs = &tracker->announces; for( i=0, n=tr_ptrArraySize(reqs); icreated_at + TAU_REQUEST_TTL < now ) ) { dbgmsg( tracker->key, "timeout announce req %p", req ); tau_announce_request_fail( req, false, true, NULL ); tau_announce_request_free( req ); tr_ptrArrayRemove( reqs, i ); --i; --n; } } reqs = &tracker->scrapes; for( i=0, n=tr_ptrArraySize(reqs); icreated_at + TAU_REQUEST_TTL < now ) ) { dbgmsg( tracker->key, "timeout scrape req %p", req ); tau_scrape_request_fail( req, false, true, NULL ); tau_scrape_request_free( req ); tr_ptrArrayRemove( reqs, i ); --i; --n; } } } static bool tau_tracker_is_idle( const struct tau_tracker * tracker ) { return tr_ptrArrayEmpty( &tracker->announces ) && tr_ptrArrayEmpty( &tracker->scrapes ); } static void tau_tracker_upkeep( struct tau_tracker * tracker ) { const time_t now = tr_time( ); /* if the address info is too old, expire it */ if( tracker->addr && ( tracker->addr_expiration_time <= now ) ) { dbgmsg( tracker->host, "Expiring old DNS result" ); evutil_freeaddrinfo( tracker->addr ); tracker->addr = NULL; } /* are there any requests pending? */ if( tau_tracker_is_idle( tracker ) ) return; /* if we don't have an address yet, try & get one now. */ if( !tracker->addr && !tracker->is_asking_dns ) { struct evutil_addrinfo hints; memset( &hints, 0, sizeof( hints ) ); hints.ai_family = AF_UNSPEC; hints.ai_flags = EVUTIL_AI_CANONNAME; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; tracker->is_asking_dns = true; dbgmsg( tracker->host, "Trying a new DNS lookup" ); evdns_getaddrinfo( tracker->session->evdns_base, tracker->host, NULL, &hints, tau_tracker_on_dns, tracker ); return; } dbgmsg( tracker->key, "addr %p -- connected %d (%zu %zu) -- connecting_at %zu", tracker->addr, (int)(tracker->connection_expiration_time > now), (size_t)tracker->connection_expiration_time, (size_t)now, (size_t)tracker->connecting_at ); /* also need a valid connection ID... */ if( tracker->addr && ( tracker->connection_expiration_time <= now ) && ( !tracker->connecting_at ) ) { struct evbuffer * buf = evbuffer_new( ); tracker->connecting_at = now; tracker->connection_transaction_id = tau_transaction_new( ); dbgmsg( tracker->key, "Trying to connect. Transaction ID is %u", tracker->connection_transaction_id ); evbuffer_add_hton_64( buf, 0x41727101980LL ); evbuffer_add_hton_32( buf, TAU_ACTION_CONNECT ); evbuffer_add_hton_32( buf, tracker->connection_transaction_id ); tau_sendto( tracker->session, tracker->addr, tracker->port, evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ) ); evbuffer_free( buf ); return; } tau_tracker_timeout_reqs( tracker ); if( ( tracker->addr != NULL ) && ( tracker->connection_expiration_time > now ) ) tau_tracker_send_reqs( tracker ); } /**** ***** ***** SESSION ***** ****/ struct tr_announcer_udp { /* tau_tracker */ tr_ptrArray trackers; tr_session * session; }; static struct tr_announcer_udp* announcer_udp_get( tr_session * session ) { struct tr_announcer_udp * tau; if( session->announcer_udp != NULL ) return session->announcer_udp; tau = tr_new0( struct tr_announcer_udp, 1 ); tau->trackers = TR_PTR_ARRAY_INIT; tau->session = session; session->announcer_udp = tau; return tau; } /* Finds the tau_tracker struct that corresponds to this url. If it doesn't exist yet, create one. */ static struct tau_tracker * tau_session_get_tracker( struct tr_announcer_udp * tau, const char * url ) { int i; int n; int port; char * host; char * key; struct tau_tracker * tracker = NULL; /* see if we've already got a tracker that matches this host + port */ tr_urlParse( url, -1, NULL, &host, &port, NULL ); key = tr_strdup_printf( "%s:%d", host, port ); for( i=0, n=tr_ptrArraySize( &tau->trackers ); !tracker && itrackers, i ); if( !tr_strcmp0( tmp->key, key ) ) tracker = tmp; } /* if we don't have a match, build a new tracker */ if( tracker == NULL ) { tracker = tr_new0( struct tau_tracker, 1 ); tracker->session = tau->session; tracker->key = key; tracker->host = host; tracker->port = port; tracker->scrapes = TR_PTR_ARRAY_INIT; tracker->announces = TR_PTR_ARRAY_INIT; tr_ptrArrayAppend( &tau->trackers, tracker ); dbgmsg( tracker->key, "New tau_tracker created" ); } else { tr_free( key ); tr_free( host ); } return tracker; } /**** ***** ***** PUBLIC API ***** ****/ void tr_tracker_udp_upkeep( tr_session * session ) { struct tr_announcer_udp * tau = session->announcer_udp; if( tau != NULL ) tr_ptrArrayForeach( &tau->trackers, (PtrArrayForeachFunc)tau_tracker_upkeep ); } bool tr_tracker_udp_is_idle( const tr_session * session ) { int i; int n; struct tr_announcer_udp * tau = session->announcer_udp; if( tau != NULL ) for( i=0, n=tr_ptrArraySize(&tau->trackers); itrackers, i ) ) ) return false; return true; } /* drop dead now. */ void tr_tracker_udp_close( tr_session * session ) { struct tr_announcer_udp * tau = session->announcer_udp; if( tau != NULL ) { session->announcer_udp = NULL; tr_ptrArrayDestruct( &tau->trackers, (PtrArrayForeachFunc)tau_tracker_free ); tr_free( tau ); } } /* start shutting down. This doesn't destroy everything if there are requests, but sets a deadline on how much longer to wait for the remaining ones */ void tr_tracker_udp_start_shutdown( tr_session * session ) { const time_t now = time( NULL ); struct tr_announcer_udp * tau = session->announcer_udp; if( tau != NULL ) { int i, n; for( i=0, n=tr_ptrArraySize(&tau->trackers); itrackers, i ); tracker->close_at = now + 3; tau_tracker_upkeep( tracker ); } } } /* @brief process an incoming udp message if it's a tracker response. * @return true if msg was a tracker response; false otherwise */ bool tau_handle_message( tr_session * session, const uint8_t * msg, size_t msglen ) { int i; int n; struct tr_announcer_udp * tau; tau_action_t action_id; tau_transaction_t transaction_id; struct evbuffer * buf; /*fprintf( stderr, "got an incoming udp message w/len %zu\n", msglen );*/ if( !session || !session->announcer_udp ) return false; if( msglen < (sizeof(uint32_t)*2) ) return false; /* extract the action_id and see if it makes sense */ buf = evbuffer_new( ); evbuffer_add_reference( buf, msg, msglen, NULL, NULL ); action_id = evbuffer_read_ntoh_32( buf ); if( !is_tau_response_message( action_id, msglen ) ) { evbuffer_free( buf ); return false; } /* extract the transaction_id and look for a match */ tau = session->announcer_udp; transaction_id = evbuffer_read_ntoh_32( buf ); /*fprintf( stderr, "UDP got a transaction_id %u...\n", transaction_id );*/ for( i=0, n=tr_ptrArraySize( &tau->trackers ); itrackers, i ); /* is it a connection response? */ if( tracker->connecting_at && ( transaction_id == tracker->connection_transaction_id ) ) { dbgmsg( tracker->key, "%"PRIu32" is my connection request!", transaction_id ); on_tracker_connection_response( tracker, action_id, buf ); evbuffer_free( buf ); return true; } /* is it a response to one of this tracker's announces? */ reqs = &tracker->announces; for( j=0, jn=tr_ptrArraySize(reqs); jsent_at && ( transaction_id == req->transaction_id ) ) { dbgmsg( tracker->key, "%"PRIu32" is an announce request!", transaction_id ); tr_ptrArrayRemove( reqs, j ); on_announce_response( req, action_id, buf ); tau_announce_request_free( req ); evbuffer_free( buf ); return true; } } /* is it a response to one of this tracker's scrapes? */ reqs = &tracker->scrapes; for( j=0, jn=tr_ptrArraySize(reqs); jsent_at && ( transaction_id == req->transaction_id ) ) { dbgmsg( tracker->key, "%"PRIu32" is a scrape request!", transaction_id ); tr_ptrArrayRemove( reqs, j ); on_scrape_response( req, action_id, buf ); tau_scrape_request_free( req ); evbuffer_free( buf ); return true; } } } /* no match... */ evbuffer_free( buf ); return false; } void tr_tracker_udp_announce( tr_session * session, const tr_announce_request * request, tr_announce_response_func response_func, void * user_data ) { struct tr_announcer_udp * tau = announcer_udp_get( session ); struct tau_tracker * tracker = tau_session_get_tracker( tau, request->url ); struct tau_announce_request * r = tau_announce_request_new( request, response_func, user_data ); tr_ptrArrayAppend( &tracker->announces, r ); tau_tracker_upkeep( tracker ); } void tr_tracker_udp_scrape( tr_session * session, const tr_scrape_request * request, tr_scrape_response_func response_func, void * user_data ) { struct tr_announcer_udp * tau = announcer_udp_get( session ); struct tau_tracker * tracker = tau_session_get_tracker( tau, request->url ); struct tau_scrape_request * r = tau_scrape_request_new( request, response_func, user_data ); tr_ptrArrayAppend( &tracker->scrapes, r ); tau_tracker_upkeep( tracker ); }