Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ames: add stun client for informal pings #509

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/c3/motes.h
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,7 @@
# define c3__sard c3_s4('s','a','r','d')
# define c3__sav c3_s3('s','a','v')
# define c3__save c3_s4('s','a','v','e')
# define c3__saxo c3_s4('s','a','x','o')
# define c3__scam c3_s4('s','c','a','m')
# define c3__scan c3_s4('s','c','a','n')
# define c3__scry c3_s4('s','c','r','y')
Expand Down
227 changes: 223 additions & 4 deletions pkg/vere/io/ames.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@

#define QUEUE_MAX 30 // max number of packets in queue

typedef enum u3_stun_state {
STUN_OFF = 0,
STUN_TRYING = 1,
STUN_KEEPALIVE = 2,
} u3_stun_state;

/* u3_fine: fine networking
*/
typedef struct _u3_fine {
Expand Down Expand Up @@ -45,6 +51,16 @@
c3_w imp_w[256]; // imperial IPs
time_t imp_t[256]; // imperial IP timestamps
c3_o imp_o[256]; // imperial print status
struct { // stun client state:
u3_stun_state sat_y; // formal state
c3_y tid_y[16]; // last transaction id
c3_d dad_d[2]; // sponsoring ship @p
u3_lane lan_u; // sponsoring ship IP and port
uv_timer_t tim_u; // keepalive timer handle
struct timeval las_u; // last sent date
struct timeval sar_u; // date we started trying to send
u3_lane sef_u; // our lane, if we know it
} sun_u; //
struct { // config:
c3_o net_o; // can send
c3_o see_o; // can scry
Expand Down Expand Up @@ -1170,6 +1186,205 @@ _fine_put_cache(u3_ames* sam_u, u3_noun pax, c3_w lop_w, u3_noun lis)
}
}

static void
_stun_stop(u3_ames* sam_u)
{
switch ( sam_u->sun_u.sat_y ) {
case STUN_OFF: break; // ignore; already stopped
case STUN_TRYING: {
uv_timer_stop(&sam_u->sun_u.tim_u);
} break;
case STUN_KEEPALIVE: {
uv_timer_stop(&sam_u->sun_u.tim_u);
} break;
default: u3_assert(!"programmer error");
}
sam_u->sun_u.sat_y = STUN_OFF;
}

static void _stun_send_request(u3_ames*); // forward declaration

static void
_stun_timer_cb(uv_timer_t* tim_u)
{
u3_ames* sam_u = (u3_ames*)(tim_u->data);
switch ( sam_u->sun_u.sat_y ) {
case STUN_OFF: {
// ignore; stray timer (although this shouldn't happen)
u3l_log("stun: stray timer STUN_OFF");
} break;
case STUN_KEEPALIVE: {
sam_u->sun_u.sat_y = STUN_TRYING;
_stun_send_request(sam_u);
} break;
case STUN_TRYING: {
c3_d gap_d;
{
struct timeval tim_tv;
gettimeofday(&tim_tv, 0);
u3_noun now = u3_time_in_tv(&tim_tv);
u3_noun den = u3_time_in_tv(&sam_u->sun_u.sar_u);
gap_d = u3_time_gap_ms(den, now);
}
if ( gap_d >= (5 * 1000) ) {
_stun_stop(sam_u);
// TODO inject event into arvo to ping repeatedly
}
else {
sam_u->sun_u.tim_u.data = sam_u;
uv_timer_start(&sam_u->sun_u.tim_u, _stun_timer_cb, 1*1000, 0);
}
} break;
default: u3_assert(!"programmer error");
}
}

typedef struct _u3_stun_send {
uv_udp_send_t req_u; // uv udp request handle
u3_ames* sam_u; // backpointer to driver state
} u3_stun_send;

static void
_stun_send_cb(uv_udp_send_t *req_u, c3_i sas_i)
{
u3_stun_send* snd_u = (u3_stun_send*)req_u;
if ( sas_i ) {
// TODO take error handling action
}
else {
u3_ames* sam_u = snd_u->sam_u;
gettimeofday(&sam_u->sun_u.las_u, 0); // overwrite last sent date
}
}

static void
_stun_on_response(u3_ames* sam_u) // TODO read arg
{
u3_stun_state old_y = sam_u->sun_u.sat_y;
switch ( sam_u->sun_u.sat_y ) {
case STUN_OFF: break; // ignore; stray response
case STUN_KEEPALIVE: break; // ignore; duplicate response
case STUN_TRYING: {
sam_u->sun_u.sat_y = STUN_KEEPALIVE;
sam_u->sun_u.tid_y[1]++;
uv_timer_stop(&sam_u->sun_u.tim_u);
sam_u->sun_u.tim_u.data = sam_u;
uv_timer_start(&sam_u->sun_u.tim_u, _stun_timer_cb, 25*1000, 0);
} break;
default: assert("programmer error");
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should either be called from _ames_recv_cb() (if we can always distinguish STUN responses preemptively), or from _ames_hear() (as a fallback if a packet is not a valid ames packet).

I believe the response will look like:

c3_y rep_y[20] = {
  0x1, 0x1,                // binding resp
  0x??, 0x??,              // len (including optional attributes)
  0x21, 0x12, 0xA4, 0x42,  // cookie
  ... original 12 bytes of randomness
  ... XOR-mapped address attribute
}

An ames header will never begin with { 0x1, 0x1 } (binding success response) or { 0x1, 0x11 } (binding error response), so that plus a sanity check of the constant "STUN cookie" should be enough to distinguish protocols.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going off the rfc (https://www.rfc-editor.org/rfc/rfc8489), and sanity checking that with packet captures (https://www.cloudshark.org/captures/4686b8430477).


static void
_stun_send_request(u3_ames* sam_u)
{
u3_assert( STUN_OFF != sam_u->sun_u.sat_y );

struct sockaddr_in add_u;
memset(&add_u, 0, sizeof(add_u));
add_u.sin_family = AF_INET;
add_u.sin_addr.s_addr = htonl(sam_u->sun_u.lan_u.pip_w);
add_u.sin_port = htons(sam_u->sun_u.lan_u.por_s);

// see STUN RFC 8489
// https://datatracker.ietf.org/doc/html/rfc8489#section-5
c3_y buf_y[20] = {0};

// STUN message type: "binding request"
buf_y[1] = 0x01;

// STUN message length: 0 (header-only)
// (implicit in initialization)

// STUN "magic cookie" 0x2112A442 in network byte order
buf_y[4] = 0x21; buf_y[5] = 0x12; buf_y[6] = 0xa4; buf_y[7] = 0x42;

// STUN "transaction id"
memcpy(buf_y + 8, sam_u->sun_u.tid_y, 12);

uv_buf_t buf_u = uv_buf_init((c3_c*)buf_y, 20);
u3_stun_send* snd_u = c3_calloc(sizeof(*snd_u));
snd_u->sam_u = sam_u;
c3_i sas_i = uv_udp_send(
(uv_udp_send_t*)snd_u, &sam_u->wax_u, &buf_u, 1,
(const struct sockaddr*)&add_u, _stun_send_cb
);

if ( sas_i ) {
u3l_log("stun: send fail_sync: %s", uv_strerror(sas_i));
// TODO take error handling actions
}
}

static void
_stun_czar_cb(uv_getaddrinfo_t* adr_u,
c3_i sas_i,
struct addrinfo* aif_u)
{
u3_ames* sam_u; // TODO get sam_u from args
gettimeofday(&sam_u->sun_u.sar_u, 0); // set start time to now
_stun_send_request(sam_u);
uv_timer_start(&sam_u->sun_u.tim_u, _stun_timer_cb, 1, 0);

}

static void
_stun_czar(u3_ames* sam_u)
{
// TODO rip off _ames_czar()
}

static void
_stun_start(u3_ames* sam_u, u3_noun dad)
{
sam_u->sun_u.sat_y = STUN_TRYING;
u3r_chubs(0, 2, sam_u->sun_u.dad_d, dad);
u3z(dad);

// initialize STUN transaction id to mug of now
{
u3_noun mug;
struct timeval tim_u;
gettimeofday(&tim_u, 0);

mug = u3r_mug(u3_time_in_tv(&tim_u));
memcpy(sam_u->sun_u.tid_y, (c3_y*)&mug, 4);
u3z(mug);
}

_stun_czar(sam_u);
}

static c3_o
_ames_is_czar(u3_noun who)
{
u3_noun rac = u3do("clan:title", u3k(who));
c3_o zar = ( c3y == (c3__czar == rac) );
u3z(rac);
return zar;
}

/* _ames_ef_saxo(): handle sponsorship chain notification
*/
static void
_ames_ef_saxo(u3_ames* sam_u, u3_noun zad)
{
u3_noun daz, dad;

daz = u3qb_flop(zad);
if ( u3_nul == daz ) {
u3l_log("ames: empty sponsorship chain");
return;
}

dad = u3k(u3h(daz));
if ( c3y == _ames_is_czar(dad) ) {
_stun_stop(sam_u);
_stun_start(sam_u, u3k(dad));
}

u3z(zad); u3z(daz); u3z(dad);
}

/* _ames_ef_send(): send packet to network (v4).
*/
Expand Down Expand Up @@ -2063,10 +2278,11 @@ _ames_io_start(u3_ames* sam_u)
{
c3_s por_s = sam_u->pir_u->por_s;
u3_noun who = u3i_chubs(2, sam_u->pir_u->who_d);
u3_noun rac = u3do("clan:title", u3k(who));
c3_o zar_o = _ames_is_czar(who);
c3_i ret_i;

if ( c3__czar == rac ) {

if ( c3y == zar_o ) {
c3_y num_y = (c3_y)sam_u->pir_u->who_d[0];
c3_s zar_s = _ames_czar_port(num_y);

Expand Down Expand Up @@ -2096,7 +2312,7 @@ _ames_io_start(u3_ames* sam_u)
{
u3l_log("ames: bind: %s", uv_strerror(ret_i));

if ( (c3__czar == rac) &&
if ( (c3y == zar_o) &&
(UV_EADDRINUSE == ret_i) )
{
u3l_log(" ...perhaps you've got two copies of vere running?");
Expand All @@ -2123,7 +2339,6 @@ _ames_io_start(u3_ames* sam_u)
uv_udp_recv_start(&sam_u->wax_u, _ames_alloc, _ames_recv_cb);

sam_u->car_u.liv_o = c3y;
u3z(rac);
u3z(who);
}

Expand Down Expand Up @@ -2272,6 +2487,10 @@ _ames_kick_newt(u3_ames* sam_u, u3_noun tag, u3_noun dat)
_ames_ef_turf(sam_u, u3k(dat));
ret_o = c3y;
} break;

case c3__saxo: {
_ames_ef_saxo(sam_u, u3k(dat));
} break;
}

u3z(tag); u3z(dat);
Expand Down