From 86d2f0e0d4b81cc31055a93e65aceb97360633de Mon Sep 17 00:00:00 2001 From: Spoike Date: Fri, 28 Jan 2022 10:48:01 +0000 Subject: [PATCH] Provide support for webrtc's sctp-over-dtls stuff, providing browser+native connectivity (via broker). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6171 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_main.c | 48 +- engine/client/cl_parse.c | 27 +- engine/client/m_master.c | 1 + engine/common/net.h | 13 +- engine/common/net_ice.c | 1162 ++++++++++++++++++++++++++++++-- engine/common/net_ssl_gnutls.c | 201 +++++- engine/common/net_wins.c | 123 +++- engine/common/netinc.h | 26 +- engine/server/sv_user.c | 6 +- engine/web/ftejslib.js | 46 +- plugins/net_ssl_openssl.c | 102 ++- 11 files changed, 1555 insertions(+), 200 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 118ad569c..0556e7ce5 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1188,7 +1188,7 @@ void CL_CheckForResend (void) return; } else - connectinfo.clogged = false; + connectinfo.clogged = false; //do the prints and everything. #ifdef HAVE_DTLS if (connectinfo.numadr>0 && connectinfo.adr[0].prot == NP_DTLS) @@ -1258,6 +1258,9 @@ void CL_CheckForResend (void) Con_TPrintf ("Connecting to %s...\n", cls.servername); } + if (connectinfo.clogged) + connectinfo.clogged = false; + if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) if (!NET_EnsureRoute(cls.sockets, "conn", cls.servername, to)) { @@ -1414,6 +1417,14 @@ void CL_BeginServerReconnect(void) if (connectinfo.numadr>0) NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[0]); connectinfo.dtlsupgrade = 0; +#endif +#ifdef SUPPORT_ICE + while (connectinfo.numadr) //remove any ICE addresses. probably we'll end up with no addresses left leaving us free to re-resolve giving us the original(ish) rtc connection. + { + if (connectinfo.adr[connectinfo.numadr-1].type != NA_ICE) + break; + connectinfo.numadr--; + } #endif if (*cl_disconnectreason.string) Cvar_Set(&cl_disconnectreason, ""); @@ -1426,6 +1437,13 @@ void CL_BeginServerReconnect(void) NET_InitClient(false); } +void CL_Transfer(netadr_t *adr) +{ + connectinfo.adr[0] = *adr; + connectinfo.numadr = 1; + connectinfo.istransfer = true; + CL_CheckForResend(); +} void CL_Transfer_f(void) { char oldguid[64]; @@ -3677,7 +3695,6 @@ void CL_ConnectionlessPacket (void) Validation_Apply_Ruleset(); Netchan_Setup(NS_CLIENT, &cls.netchan, &net_from, connectinfo.qport); CL_ParseEstablished(); - Con_DPrintf ("CL_EstablishConnection: connected to %s\n", cls.servername); cls.netchan.isnqprotocol = true; cls.protocol = CP_NETQUAKE; @@ -3747,11 +3764,14 @@ void CL_ConnectionlessPacket (void) else if (!strcmp(com_token, "tlsopened")) { //server is letting us know that its now listening for a dtls handshake. #ifdef HAVE_DTLS + dtlscred_t cred; Con_Printf (S_COLOR_GRAY"dtlsopened\n"); if (!CL_IsPendingServerAddress(&net_from)) return; - if (NET_DTLS_Create(cls.sockets, &net_from, cls.servername)) + memset(&cred, 0, sizeof(cred)); + cred.peer.name = cls.servername; + if (NET_DTLS_Create(cls.sockets, &net_from, &cred)) { connectinfo.dtlsupgrade = DTLS_ACTIVE; connectinfo.numadr = 1; //fixate on this resolved address. @@ -3863,21 +3883,6 @@ client_connect: //fixme: make function #endif CL_SendClientCommand(true, "new"); cls.state = ca_connected; - if (cls.netchan.remote_address.type != NA_LOOPBACK) - { - switch(cls.protocol) - { - case CP_QUAKEWORLD: Con_DPrintf("QW "); break; - case CP_NETQUAKE: Con_Printf ("NQ "); break; - case CP_QUAKE2: Con_Printf ("Q2 "); break; - case CP_QUAKE3: Con_Printf ("Q3 "); break; - default: break; - } - if (cls.netchan.remote_address.prot == NP_DTLS || cls.netchan.remote_address.prot == NP_TLS || cls.netchan.remote_address.prot == NP_WSS) - Con_TPrintf ("Connected (^[^2encrypted\\tip\\Any passwords will be sent securely, but may still be logged^]).\n"); - else - Con_TPrintf ("Connected (^[^1plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]).\n"); - } #ifdef QUAKESPYAPI allowremotecmd = false; // localid required now for remote cmds #endif @@ -4060,11 +4065,6 @@ void CLNQ_ConnectionlessPacket(void) cls.protocol = CP_NETQUAKE; cls.state = ca_connected; - if (cls.netchan.remote_address.prot == NP_DTLS || cls.netchan.remote_address.prot == NP_TLS || cls.netchan.remote_address.prot == NP_WSS) - Con_TPrintf ("Connected (^[^2encrypted\\tip\\Any passwords will be sent securely, but may still be logged^]).\n"); - else - Con_TPrintf ("Connected (^[^1plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]).\n"); - total_loading_size = 100; current_loading_size = 0; SCR_SetLoadingStage(LS_CLIENT); @@ -4155,7 +4155,7 @@ void CL_ReadPacket(void) if (net_message.cursize == 1 && net_message.data[0] == A2A_ACK) Con_TPrintf ("%s: Ack (Pong)\n", NET_AdrToString(adr, sizeof(adr), &net_from)); else - Con_TPrintf ("%s: Runt packet\n", NET_AdrToString(adr, sizeof(adr), &net_from)); + Con_TPrintf ("%s: Runt packet (%i bytes)\n", NET_AdrToString(adr, sizeof(adr), &net_from), net_message.cursize); return; } diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 1cbfb2315..22fb29492 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -3720,6 +3720,29 @@ void CL_ParseEstablished(void) cl_dp_csqc_progscrc = 0; cl_dp_csqc_progssize = 0; #endif + + if (cls.netchan.remote_address.type != NA_LOOPBACK) + { + const char *security; + switch(cls.protocol) + { + case CP_QUAKEWORLD: Con_DPrintf(S_COLOR_GRAY"QW "); break; + case CP_NETQUAKE: Con_Printf (S_COLOR_GRAY"NQ "); break; + case CP_QUAKE2: Con_Printf (S_COLOR_GRAY"Q2 "); break; + case CP_QUAKE3: Con_Printf (S_COLOR_GRAY"Q3 "); break; + default: break; + } + if ( +#ifdef SUPPORT_ICE + (cls.netchan.remote_address.type == NA_ICE && cls.netchan.remote_address.port) || +#endif + cls.netchan.remote_address.prot == NP_DTLS || cls.netchan.remote_address.prot == NP_TLS || cls.netchan.remote_address.prot == NP_WSS) + security = "^["S_COLOR_GREEN"encrypted\\tip\\Any passwords will be sent securely, but will still be readable by the server admin^]"; + else + security = "^["S_COLOR_RED"plain-text\\tip\\"CON_WARNING"Do not type passwords as they can potentially be seen by network sniffers^]"; + + Con_TPrintf ("Connected to ^["S_COLOR_BLUE"%s\\type\\connect %s^] (%s).\n", cls.servername, cls.servername, security); + } } #ifdef NQPROT @@ -4376,7 +4399,7 @@ static void CL_ParseSoundlist (qboolean lots) { if (cls.demoplayback != DPB_EZTV) { - if (CL_RemoveClientCommands("soundlist")) + if (CL_RemoveClientCommands("soundlist") && !(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) Con_DPrintf("Multiple soundlists\n"); // CL_SendClientCommand("soundlist %i %i", cl.servercount, n); CL_SendClientCommand(true, soundlist_name, cl.servercount, (numsounds&0xff00) + n); @@ -4476,7 +4499,7 @@ static void CL_ParseModellist (qboolean lots) { if (cls.demoplayback != DPB_EZTV) { - if (CL_RemoveClientCommands("modellist")) + if (CL_RemoveClientCommands("modellist") && !(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) Con_DPrintf("Multiple modellists\n"); // CL_SendClientCommand("modellist %i %i", cl.servercount, n); CL_SendClientCommand(true, modellist_name, cl.servercount, (nummodels&0xff00) + n); diff --git a/engine/client/m_master.c b/engine/client/m_master.c index d50f8155c..7e07ca10c 100644 --- a/engine/client/m_master.c +++ b/engine/client/m_master.c @@ -835,6 +835,7 @@ static qboolean SL_Key (int key, emenu_t *menu) if (server) Master_FindRoute(server->adr); serverpreview = SVPV_ROUTE; + return true; } #endif else if (key == 'b' || key == 'o' || key == 'j' || key == K_ENTER || key == K_KP_ENTER || key == K_GP_START) //join diff --git a/engine/common/net.h b/engine/common/net.h index 75e750030..523193424 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -47,6 +47,9 @@ typedef enum { #ifdef HAVE_WEBSOCKCL NA_WEBSOCKET, #endif +#ifdef SUPPORT_ICE + NA_ICE +#endif } netadrtype_t; typedef enum { NP_DGRAM, @@ -80,6 +83,9 @@ typedef struct netadr_s char channel[12]; } irc; #endif +#ifdef SUPPORT_ICE + char icename[16]; +#endif #ifdef HAVE_WEBSOCKCL char websocketurl[64]; #endif @@ -187,12 +193,17 @@ enum certprops_e size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize); #ifdef HAVE_DTLS -qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const char *hostname); +struct dtlscred_s; +struct dtlsfuncs_s; +qboolean NET_DTLS_Create(struct ftenet_connections_s *col, netadr_t *to, const struct dtlscred_s *cred); qboolean NET_DTLS_Decode(struct ftenet_connections_s *col); qboolean NET_DTLS_Disconnect(struct ftenet_connections_s *col, netadr_t *to); void NET_DTLS_Timeouts(struct ftenet_connections_s *col); extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key; #endif +#ifdef SUPPORT_ICE +neterr_t ICE_SendPacket(struct ftenet_connections_s *col, size_t length, const void *data, netadr_t *to); +#endif extern cvar_t timeout; extern cvar_t tls_ignorecertificateerrors; //evil evil evil. struct ftecrypto_s; diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index b85e0a34b..9d0b4a0a4 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -8,6 +8,14 @@ typedef struct unsigned int magiccookie; unsigned int transactid[3]; } stunhdr_t; +//class +#define STUN_REQUEST 0x0000 +#define STUN_REPLY 0x0100 +#define STUN_ERROR 0x0110 +#define STUN_INDICATION 0x0010 +// +#define STUN_BINDING 0x0001 + typedef struct { unsigned short attrtype; @@ -64,7 +72,11 @@ struct icestate_s struct icestate_s *next; void *module; - netadr_t chosenpeer; + netadr_t qadr; //address reported to the rest of the engine (packets from our peer get remapped to this) + netadr_t chosenpeer; //address we're sending our data to. + + void *dtlsstate; + struct sctp_s *sctp; //ffs! extra processing needed. netadr_t pubstunserver; unsigned int stunretry; //once a second, extended to once a minite on reply @@ -98,7 +110,14 @@ struct icestate_s unsigned int tielow; int foundation; - ftenet_connections_t *connections; + qboolean blockcandidates; //don't send candidates yet. + const dtlsfuncs_t *dtlsfuncs; + qboolean dtlspassive; //true=server, false=client (separate from ice controller and whether we're hosting. yay...) + dtlscred_t cred; //credentials info for dtls (both peer and local info) + quint16_t mysctpport; + quint16_t peersctpport; + + ftenet_connections_t *connections; //used only for PRIVATE sockets. struct icecodecslot_s { @@ -107,6 +126,48 @@ struct icestate_s char *name; } codecslot[34]; //96-127. don't really need to care about other ones. }; + +typedef struct sctp_s +{ + quint16_t myport, peerport; + qboolean peerhasfwdtsn; + double nextreinit; + void *cookie; //sent part of the handshake (might need resending). + size_t cookiesize; + struct + { + quint32_t verifycode; + qboolean writable; + quint32_t tsn; //Transmit Sequence Number + + quint32_t ctsn; //acked tsn + quint32_t losttsn; //total gap size... + } o; + struct + { + quint32_t verifycode; + int ackneeded; + quint32_t ctsn; + quint32_t htsn; //so we don't have to walk so many packets to count gaps. +#define SCTP_RCVSIZE 2048 //cannot be bigger than 65536 + qbyte received[SCTP_RCVSIZE/8]; + + struct + { + quint32_t firsttns; //so we only ack fragments that were complete. + quint32_t tsn; //if a continuation doesn't match, we drop it. + quint32_t ppid; + quint16_t sid; + quint16_t seq; + size_t size; + qboolean toobig; + qbyte buf[65536]; + } r; + } i; + unsigned short qstreamid; //in network endian. +} sctp_t; +static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length); + static struct icestate_s *icelist; static struct icecodecslot_s *ICE_GetCodecSlot(struct icestate_s *ice, int slot) @@ -258,13 +319,20 @@ static ftenet_connections_t *ICE_PickConnection(struct icestate_s *con) } return NULL; } +static neterr_t ICE_Transmit(void *cbctx, const qbyte *data, size_t datasize) +{ + struct icestate_s *ice = cbctx; + return NET_SendPacket(ICE_PickConnection(ice), datasize, data, &ice->chosenpeer); +} + static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const char *peername, enum icemode_e mode, enum iceproto_e proto) { ftenet_connections_t *collection; struct icestate_s *con; + static unsigned int icenum; //only allow modes that we actually support. - if (mode != ICEM_RAW && mode != ICEM_ICE) + if (mode != ICEM_RAW && mode != ICEM_ICE && mode != ICEM_WEBRTC) return NULL; //only allow protocols that we actually support. @@ -317,7 +385,7 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); - con->friendlyname = Z_StrDup(peername); + con->friendlyname = peername?Z_StrDup(peername):Z_StrDupf("%i", icenum++); con->proto = proto; con->rpwd = Z_StrDup(""); con->rufrag = Z_StrDup(""); @@ -326,6 +394,28 @@ static struct icestate_s *QDECL ICE_Create(void *module, const char *conname, co Q_strncpyz(con->originaddress, "127.0.0.1", sizeof(con->originaddress)); con->mode = mode; + con->blockcandidates = true; //until offers/answers are sent. + con->dtlspassive = (proto == ICEP_QWSERVER); //note: may change later. + + if (mode == ICEM_WEBRTC) + { //dtls+sctp is a mandatory part of our connection, sadly. + if (!con->dtlsfuncs) + { + if (con->dtlspassive) + con->dtlsfuncs = DTLS_InitServer(); + else + con->dtlsfuncs = DTLS_InitClient(); //credentials are a bit different, though fingerprints make it somewhat irrelevant. + } + if (con->dtlsfuncs && con->dtlsfuncs->GenTempCertificate && !con->cred.local.certsize) + con->dtlsfuncs->GenTempCertificate(NULL, &con->cred.local); + + con->mysctpport = 27500; + } + + con->qadr.type = NA_ICE; + con->qadr.prot = NP_DGRAM; + con->qadr.port = con->mysctpport; + Q_strncpyz(con->qadr.address.icename, con->friendlyname, sizeof(con->qadr.address.icename)); con->next = icelist; icelist = con; @@ -538,27 +628,30 @@ static void ICE_ToStunServer(struct icestate_s *con) NET_SendPacket(collection, buf.cursize, data, &con->pubstunserver); } +int ParsePartialIP(const char *s, netadr_t *a); static void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandinfo_s *n) { struct icecandidate_s *o; qboolean isnew; netadr_t peer; + int peerbits; //I don't give a damn about rtpc. if (n->component != 1) return; if (n->transport != 0) return; //only UDP is supported. - if (!NET_StringToAdr(n->addr, n->port, &peer)) - return; - - if (peer.type == NA_IP) + //don't use the regular string->addr, because browsers seem to shove internal gibberish names in there that waste time to resolve. hostnames don't really make sense here anyway. + peerbits = ParsePartialIP(n->addr, &peer); + peer.prot = NP_DGRAM; + peer.port = n->port; + if (peer.type == NA_IP && peerbits == 32) { //ignore invalid addresses if (!peer.address.ip[0] && !peer.address.ip[1] && !peer.address.ip[2] && !peer.address.ip[3]) return; } - else if (peer.type == NA_IPV6) + else if (peer.type == NA_IPV6 && peerbits == 128) { //ignore invalid addresses int i; @@ -568,6 +661,8 @@ static void QDECL ICE_AddRCandidateInfo(struct icestate_s *con, struct icecandin if (i == countof(peer.address.ip6)) return; //all clear. in6_addr_any } + else + return; //bad address type, or partial. if (*n->candidateid) { @@ -624,6 +719,13 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) ICE_Set(con, "rpwd", value+10); else if (!strncmp(value, "a=ice-ufrag:", 12)) ICE_Set(con, "rufrag", value+12); + else if (!strncmp(value, "a=setup:", 8)) + { //this is their state, so we want the opposite. + if (!strncmp(value+8, "passive", 7)) + con->dtlspassive = false; + else if (!strncmp(value+8, "active", 6)) + con->dtlspassive = true; + } else if (!strncmp(value, "a=rtpmap:", 9)) { char name[64]; @@ -639,6 +741,60 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) *sl = '@'; ICE_Set(con, va("codec%i", codec), name); } + else if (!strncmp(value, "a=fingerprint:", 14)) + { + char name[64]; + value = COM_ParseOut(value+14, name, sizeof(name)); + if (!strcasecmp(name, "sha-1")) + con->cred.peer.hash = &hash_sha1; + else if (!strcasecmp(name, "sha-2224")) + con->cred.peer.hash = &hash_sha224; + else if (!strcasecmp(name, "sha-256")) + con->cred.peer.hash = &hash_sha256; + else if (!strcasecmp(name, "sha-384")) + con->cred.peer.hash = &hash_sha384; + else if (!strcasecmp(name, "sha-512")) + con->cred.peer.hash = &hash_sha512; + else + con->cred.peer.hash = NULL; //hash not recognised + if (con->cred.peer.hash) + { + int b, o, v; + while (*value == ' ') + value++; + for (b = 0; b < con->cred.peer.hash->digestsize; ) + { + v = *value; + if (v >= '0' && v <= '9') + o = (v-'0'); + else if (v >= 'A' && v <= 'F') + o = (v-'A'+10); + else if (v >= 'a' && v <= 'f') + o = (v-'a'+10); + else + break; + o <<= 4; + v = *++value; + if (v >= '0' && v <= '9') + o |= (v-'0'); + else if (v >= 'A' && v <= 'F') + o |= (v-'A'+10); + else if (v >= 'a' && v <= 'f') + o |= (v-'a'+10); + else + break; + con->cred.peer.digest[b++] = o; + v = *++value; + if (v != ':') + break; + value++; + } + if (b != con->cred.peer.hash->digestsize) + con->cred.peer.hash = NULL; //bad! + } + } + else if (!strncmp(value, "a=sctp-port:", 12)) + con->peersctpport = atoi(value+12); else if (!strncmp(value, "a=candidate:", 12)) { struct icecandinfo_s n; @@ -651,7 +807,7 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) n.component = strtoul(value, (char**)&value, 0); if(*value == ' ')value++; - if (!strncmp(value, "UDP ", 4)) + if (!strncasecmp(value, "UDP ", 4)) { n.transport = 0; value += 3; @@ -714,6 +870,7 @@ static void ICE_ParseSDPLine(struct icestate_s *con, const char *value) } } +void CL_Transfer(netadr_t *adr); static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *value) { if (!strcmp(prop, "state")) @@ -732,7 +889,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch Con_Printf("ICE_Set invalid state %s\n", value); con->state = ICE_INACTIVE; } - con->timeout = Sys_Milliseconds(); + con->timeout = Sys_Milliseconds() + 30; con->retries = 0; @@ -742,6 +899,25 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch NET_InitClient(false); #endif + if (con->state >= ICE_CONNECTING) + { + if (con->mode == ICEM_WEBRTC) + { + if (!con->dtlsstate && con->dtlsfuncs) + { + con->dtlsstate = con->dtlsfuncs->CreateContext(&con->cred, con, ICE_Transmit, con->dtlspassive); + } + if (!con->sctp) + { + con->sctp = Z_Malloc(sizeof(*con->sctp)); + con->sctp->myport = htons(con->mysctpport); + con->sctp->peerport = htons(con->peersctpport); + Sys_RandomBytes((void*)&con->sctp->o.verifycode, sizeof(con->sctp->o.verifycode)); + Sys_RandomBytes((void*)&con->sctp->i.verifycode, sizeof(con->sctp->i.verifycode)); + } + } + } + if (oldstate != con->state && con->state == ICE_CONNECTED) { char msg[256]; @@ -752,11 +928,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch } #ifndef SERVERONLY else if (con->proto == ICEP_QWCLIENT) - { - //FIXME: should make a proper connection type for this so we can switch to other candidates if one route goes down -// Con_Printf("Try typing connect %s\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); - Cbuf_AddText(va("\ncl_transfer \"%s\"\n", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)), RESTRICT_LOCAL); - } + CL_Transfer(&con->qadr); //okay, the client should be using this ice connection now. #endif #ifndef CLIENTONLY else if (con->proto == ICEP_QWSERVER) @@ -766,7 +938,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch } #endif if (con->state == ICE_CONNECTED) - Con_Printf("%s connection established (peer %s).\n", con->proto == ICEP_VOICE?"voice":"data", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); + Con_Printf(S_COLOR_GRAY "%s connection established (peer %s).\n", con->proto == ICEP_VOICE?"voice":"data", NET_AdrToString(msg, sizeof(msg), &con->chosenpeer)); } #if !defined(SERVERONLY) && defined(VOICECHAT) @@ -818,7 +990,7 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch if (!NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver)) return false; } - else if (!strcmp(prop, "sdp")) + else if (!strcmp(prop, "sdp") || !strcmp(prop, "sdpoffer") || !strcmp(prop, "sdpanswer")) { char line[8192]; const char *eol; @@ -832,10 +1004,12 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch { memcpy(line, value, eol-value); line[eol-value] = 0; + if (eol>value && line[eol-value-1] == '\r') + line[eol-value-1] = 0; ICE_ParseSDPLine(con, line); } - if (eol) + if (eol && *eol) eol++; } } @@ -923,7 +1097,7 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va } } } - else if (!strcmp(prop, "sdp")) + else if (!strcmp(prop, "sdp") || !strcmp(prop, "sdpoffer") || !strcmp(prop, "sdpanswer")) { struct icecandidate_s *can; netadr_t sender; @@ -942,23 +1116,49 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va sender = *addr; } - Q_strncpyz(value, "v=0\n", valuelen); - Q_strncatz(value, va("o=%s %u %u IN IP4 %s\n", "-", con->originid, con->originversion, con->originaddress), valuelen); //originator - Q_strncatz(value, va("s=%s\n", con->conname), valuelen); - Q_strncatz(value, va("c=IN %s %s\n", sender.type==NA_IPV6?"IP6":"IP4", NET_BaseAdrToString(tmpstr, sizeof(tmpstr), &sender)), valuelen); - Q_strncatz(value, "t=0 0\n", valuelen); + Q_strncpyz(value, "v=0\n", valuelen); //version... + Q_strncatz(value, va("o=%s %u %u IN IP4 %s\n", "-", con->originid, con->originversion, con->originaddress), valuelen); //originator. usually just dummy info. + Q_strncatz(value, va("s=%s\n", con->conname), valuelen); //session name. + Q_strncatz(value, "t=0 0\n", valuelen); //start+end times... + Q_strncatz(value, va("a=ice-options:trickle\n"), valuelen); + + if ((con->proto == ICEP_QWSERVER || con->proto == ICEP_QWCLIENT) && con->mode == ICEM_WEBRTC) + { +#ifdef HAVE_DTLS + if (con->cred.local.certsize) + { + qbyte fingerprint[DIGEST_MAXSIZE]; + int b; + CalcHash(&hash_sha256, fingerprint, sizeof(fingerprint), con->cred.local.cert, con->cred.local.certsize); + Q_strncatz(value, "a=fingerprint:sha-256", valuelen); + for (b = 0; b < hash_sha256.digestsize; b++) + Q_strncatz(value, va(b?":%02X":" %02X", fingerprint[b]), valuelen); + Q_strncatz(value, "\n", valuelen); + } + Q_strncatz(value, "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n", valuelen); +#endif + } +// Q_strncatz(value, va("c=IN %s %s\n", sender.type==NA_IPV6?"IP6":"IP4", NET_BaseAdrToString(tmpstr, sizeof(tmpstr), &sender)), valuelen); + Q_strncatz(value, "c=IN IP4 0.0.0.0\n", valuelen); Q_strncatz(value, va("a=ice-pwd:%s\n", con->lpwd), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); -// if (net_enable_dtls.ival) -// Q_strncatz(value, va("a=fingerprint:SHA-1 XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX\n", con->fingerprint), valuelen); - if (con->proto == ICEP_QWSERVER || con->proto == ICEP_QWCLIENT) + if (con->dtlsfuncs) { -#ifdef HAVE_DTLS -// Q_strncatz(value, "m=application 9 DTLS/SCTP 5000\n", valuelen); -#endif + if (!strcmp(prop, "sdpanswer")) + { //answerer decides. + if (con->dtlspassive) + Q_strncatz(value, va("a=setup:passive\n"), valuelen); + else + Q_strncatz(value, va("a=setup:active\n"), valuelen); + } + else if (!strcmp(prop, "sdpoffer")) + Q_strncatz(value, va("a=setup:actpass\n"), valuelen); //don't care if we're active or passive } + if (con->mysctpport) + Q_strncatz(value, va("a=sctp-port:%i\n", con->mysctpport), valuelen); //stupid hardcoded thing. + /*fixme: merge the codecs into a single media line*/ for (i = 0; i < countof(con->codecslot); i++) { @@ -994,13 +1194,16 @@ static qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *va return true; } -static void ICE_Debug(struct icestate_s *con) +static void ICE_Debug(struct icestate_s *con, qboolean islisten) { struct icecandidate_s *can; char buf[65536]; ICE_Get(con, "state", buf, sizeof(buf)); Con_Printf("ICE \"%s\" (%s):\n", con->friendlyname, buf); - ICE_Get(con, "sdp", buf, sizeof(buf)); + if (islisten) + ICE_Get(con, "sdpanswer", buf, sizeof(buf)); + else + ICE_Get(con, "sdpoffer", buf, sizeof(buf)); Con_Printf(S_COLOR_YELLOW "%s\n", buf); Con_Printf("local:\n"); @@ -1128,6 +1331,10 @@ static void ICE_Destroy(struct icestate_s *con) { if (con->connections) FTENET_CloseCollection(con->connections); + if (con->cred.local.cert) + Z_Free(con->cred.local.cert); + if (con->cred.local.key) + Z_Free(con->cred.local.key); //has already been unlinked Z_Free(con); } @@ -1153,6 +1360,7 @@ void ICE_Tick(void) ICE_Set(con, "state", STRINGIFY(ICE_CONNECTED)); } break; + case ICEM_WEBRTC: case ICEM_ICE: if (con->state == ICE_CONNECTING || con->state == ICE_FAILED) { @@ -1196,9 +1404,12 @@ void ICE_Tick(void) } else if (con->state == ICE_CONNECTED) { - //keepalive - //if (timeout) - // con->state = ICE_CONNECTING; + if (con->sctp) + SCTP_Transmit(con->sctp, con, NULL,0); //try to keep it ticking... + if (con->dtlsfuncs) + con->dtlsfuncs->Timeouts(con->dtlsstate); + + //FIXME: We should be sending a stun binding indication every 15 secs with a fingerprint attribute } break; } @@ -1252,9 +1463,698 @@ icefuncs_t iceapi = }; #endif + + +#if defined(SUPPORT_ICE) +//======================================== +//WebRTC's interpretation of SCTP. its annoying, but hey its only 28 wasted bytes... along with the dtls overhead too. most of this is redundant. +//we only send unreliably. + +struct sctp_header_s +{ + quint16_t srcport; + quint16_t dstport; + quint32_t verifycode; + quint32_t crc; +}; +struct sctp_chunk_s +{ + qbyte type; +#define SCTP_TYPE_DATA 0 +#define SCTP_TYPE_INIT 1 +#define SCTP_TYPE_INITACK 2 +#define SCTP_TYPE_SACK 3 +#define SCTP_TYPE_PING 4 +#define SCTP_TYPE_PONG 5 +#define SCTP_TYPE_ABORT 6 +#define SCTP_TYPE_SHUTDOWN 7 +#define SCTP_TYPE_SHUTDOWNACK 8 +#define SCTP_TYPE_ERROR 9 +#define SCTP_TYPE_COOKIEECHO 10 +#define SCTP_TYPE_COOKIEACK 11 +#define SCTP_TYPE_SHUTDOWNDONE 14 +#define SCTP_TYPE_FORWARDTSN 192 + qbyte flags; + quint16_t length; + //value... +}; +struct sctp_chunk_data_s +{ + struct sctp_chunk_s chunk; + quint32_t tsn; + quint16_t sid; + quint16_t seq; + quint32_t ppid; +#define SCTP_PPID_DCEP 50 //datachannel establishment protocol +#define SCTP_PPID_DATA 53 //our binary quake data. +}; +struct sctp_chunk_init_s +{ + struct sctp_chunk_s chunk; + quint32_t verifycode; + quint32_t arwc; + quint16_t numoutstreams; + quint16_t numinstreams; + quint32_t tsn; +}; +struct sctp_chunk_sack_s +{ + struct sctp_chunk_s chunk; + quint32_t tsn; + quint32_t arwc; + quint16_t gaps; + quint16_t dupes; + /*struct { + quint16_t first; + quint16_t last; + } gapblocks[]; //actually received rather than gaps, but same meaning. + quint32_t dupe_tsns[];*/ +}; +struct sctp_chunk_fwdtsn_s +{ + struct sctp_chunk_s chunk; + quint32_t tsn; + struct + { + quint16_t sid; + quint16_t seq; + } streams[1];//... +}; + +static neterr_t SCTP_PeerSendPacket(struct icestate_s *peer, int length, const void *data) +{ //sends to the dtls layer (which will send to the generic ice dispatcher that'll send to the dgram stuff... layers on layers. + if (length<=12) + return NETERR_DISCONNECTED; + if (peer) + return peer->dtlsfuncs->Transmit(peer->dtlsstate, data, length); + else + return NETERR_NOROUTE; +} + +static quint32_t SCTP_Checksum(const struct sctp_header_s *h, size_t size) +{ + int k; + const qbyte *buf = (const qbyte*)h; + size_t ofs; + uint32_t crc = 0xFFFFFFFF; + + for (ofs = 0; ofs < size; ofs++) + { + if (ofs >= 8 && ofs < 8+4) + ; //the header's crc should be read as 0. + else + crc ^= buf[ofs]; + for (k = 0; k < 8; k++) //CRC-32C polynomial 0x1EDC6F41 in reversed bit order. + crc = crc & 1 ? (crc >> 1) ^ 0x82f63b78 : crc >> 1; + } + return ~crc; +} + +static neterr_t SCTP_Transmit(sctp_t *sctp, struct icestate_s *peer, const void *data, size_t length) +{ + qbyte pkt[65536]; + size_t pktlen = 0; + struct sctp_header_s *h = (void*)pkt; + struct sctp_chunk_data_s *d; + struct sctp_chunk_fwdtsn_s *fwd; + if (length > sizeof(pkt)) + return NETERR_MTU; + + h->dstport = sctp->peerport; + h->srcport = sctp->myport; + h->verifycode = sctp->o.verifycode; + pktlen += sizeof(*h); + + if (!sctp->o.writable) + { + double time = Sys_DoubleTime(); + if (time > sctp->nextreinit) + { + sctp->nextreinit = time + 0.5; + if (!sctp->cookie) + { + struct sctp_chunk_init_s *init = (struct sctp_chunk_init_s*)&pkt[pktlen]; + struct { + quint16_t ptype; + quint16_t plen; + } *ftsn = (void*)(init+1); + h->verifycode = 0; + init->chunk.type = SCTP_TYPE_INIT; + init->chunk.flags = 0; + init->chunk.length = BigShort(sizeof(*init)+sizeof(*ftsn)); + init->verifycode = sctp->i.verifycode; + init->arwc = BigLong(65535); + init->numoutstreams = BigShort(2); + init->numinstreams = BigShort(2); + init->tsn = sctp->o.tsn; + ftsn->ptype = BigShort(49152); + ftsn->plen = BigShort(sizeof(*ftsn)); + pktlen += sizeof(*init) + sizeof(*ftsn); + + h->crc = SCTP_Checksum(h, pktlen); + return SCTP_PeerSendPacket(peer, pktlen, h); + } + else + { + struct sctp_chunk_s *cookie = (struct sctp_chunk_s*)&pkt[pktlen]; + + if (pktlen + sizeof(*cookie) + sctp->cookiesize > sizeof(pkt)) + return NETERR_DISCONNECTED; + cookie->type = SCTP_TYPE_COOKIEECHO; + cookie->flags = 0; + cookie->length = BigShort(sizeof(*cookie)+sctp->cookiesize); + memcpy(cookie+1, sctp->cookie, sctp->cookiesize); + pktlen += sizeof(*cookie) + sctp->cookiesize; + + h->crc = SCTP_Checksum(h, pktlen); + return SCTP_PeerSendPacket(peer, pktlen, h); + } + } + + return NETERR_CLOGGED; //nope, not ready yet + } + + if (sctp->peerhasfwdtsn && sctp->o.ctsn < sctp->o.tsn && sctp->o.losttsn) + { + fwd = (struct sctp_chunk_fwdtsn_s*)&pkt[pktlen]; + fwd->chunk.type = SCTP_TYPE_FORWARDTSN; + fwd->chunk.flags = 0; + fwd->chunk.length = BigShort(sizeof(*fwd)); + fwd->tsn = BigLong(sctp->o.tsn-1); + fwd->streams[0].sid = sctp->qstreamid; + fwd->streams[0].seq = BigShort(0); + pktlen += sizeof(*fwd); + } + + if (sctp->i.ackneeded >= 2) + { + struct sctp_chunk_sack_s *rsack; + struct sctp_chunk_sack_gap_s { + uint16_t first; + uint16_t last; + } *rgap; + quint32_t otsn; + + rsack = (struct sctp_chunk_sack_s*)&pkt[pktlen]; + rsack->chunk.type = SCTP_TYPE_SACK; + rsack->chunk.flags = 0; + rsack->chunk.length = BigShort(sizeof(*rsack)); + rsack->tsn = BigLong(sctp->i.ctsn); + rsack->arwc = BigLong(65535); + rsack->gaps = 0; + rsack->dupes = BigShort(0); + pktlen += sizeof(*rsack); + + rgap = (struct sctp_chunk_sack_gap_s*)&pkt[pktlen]; + for (otsn = 0; otsn < sctp->i.htsn; otsn++) + { + quint32_t tsn = sctp->i.ctsn+otsn; + if (!(sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7))) + continue; //missing, don't report it in the 'gaps'... yeah, backwards naming. + if (rsack->gaps && rgap[-1].last == otsn-1) + rgap[-1].last = otsn; //merge into the last one. + else + { + rgap->first = otsn; //these values are Offset from the Cumulative TSN, to save storage. + rgap->last = otsn; + rgap++; + rsack->gaps++; + pktlen += sizeof(*rgap); + if (pktlen >= 500) + break; //might need fragmentation... just stop here. + } + } + for (otsn = 0, rgap = (struct sctp_chunk_sack_gap_s*)&pkt[pktlen]; otsn < rsack->gaps; otsn++) + { + rgap[otsn].first = BigShort(rgap[otsn].first); + rgap[otsn].last = BigShort(rgap[otsn].last); + } + rsack->gaps = BigShort(rsack->gaps); + + sctp->i.ackneeded = 0; + } + + if (pktlen + sizeof(*d) + length >= 500 && length && pktlen != sizeof(*h)) + { //probably going to result in fragmentation issues. send separate packets. + h->crc = SCTP_Checksum(h, pktlen); + SCTP_PeerSendPacket(peer, pktlen, h); + + //reset to the header + pktlen = sizeof(*h); + } + + if (length) + { + d = (void*)&pkt[pktlen]; + d->chunk.type = SCTP_TYPE_DATA; + d->chunk.flags = 3|4; + d->chunk.length = BigShort(sizeof(*d) + length); + d->tsn = BigLong(sctp->o.tsn++); + d->sid = sctp->qstreamid; + d->seq = BigShort(0); //not needed for unordered + d->ppid = BigLong(SCTP_PPID_DATA); + memcpy(d+1, data, length); + pktlen += sizeof(*d) + length; + + //chrome insists on pointless padding at the end. its annoying. + while(pktlen&3) + pkt[pktlen++]=0; + } + if (pktlen == sizeof(*h)) + return NETERR_SENT; //nothing to send... + + h->crc = SCTP_Checksum(h, pktlen); + return SCTP_PeerSendPacket(peer, pktlen, h); +} + +static void SCTP_DecodeDCEP(sctp_t *sctp, struct icestate_s *peer, qbyte *resp) +{ //send an ack... + size_t pktlen = 0; + struct sctp_header_s *h = (void*)resp; + struct sctp_chunk_data_s *d; + char *data = "\02"; + size_t length = 1; //*sigh*... + + struct + { + qbyte type; + qbyte chantype; + quint16_t priority; + quint32_t relparam; + quint16_t labellen; + quint16_t protocollen; + } *dcep = (void*)sctp->i.r.buf; + + if (dcep->type == 3) + { + char *label = (qbyte*)(dcep+1); + char *prot = label + strlen(label)+1; + + sctp->qstreamid = sctp->i.r.sid; + Con_DPrintf("New SCTP Channel: \"%s\" (%s)\n", label, prot); + + h->dstport = sctp->peerport; + h->srcport = sctp->myport; + h->verifycode = sctp->o.verifycode; + pktlen += sizeof(*h); + + pktlen = (pktlen+3)&~3; //pad. + d = (void*)&resp[pktlen]; + d->chunk.type = SCTP_TYPE_DATA; + d->chunk.flags = 3; + d->chunk.length = BigShort(sizeof(*d) + length); + d->tsn = BigLong(sctp->o.tsn++); + d->sid = sctp->qstreamid; + d->seq = BigShort(0); //not needed for unordered + d->ppid = BigLong(SCTP_PPID_DCEP); + memcpy(d+1, data, length); + pktlen += sizeof(*d) + length; + + h->crc = SCTP_Checksum(h, pktlen); + SCTP_PeerSendPacket(peer, pktlen, h); + } +} + +struct sctp_errorcause_s +{ + quint16_t cause; + quint16_t length; +}; +static void SCTP_ErrorChunk(const char *errortype, struct sctp_errorcause_s *s, size_t totallen) +{ + quint16_t cc, cl; + while(totallen > 0) + { + if (totallen < sizeof(*s)) + return; //that's an error in its own right + cc = BigShort(s->cause); + cl = BigShort(s->length); + if (totallen < cl) + return; //err.. + + switch(cc) + { + case 1: Con_Printf("%s: Invalid Stream Identifier\n", errortype); break; + case 2: Con_Printf("%s: Missing Mandatory Parameter\n", errortype); break; + case 3: Con_Printf("%s: Stale Cookie Error\n", errortype); break; + case 4: Con_Printf("%s: Out of Resource\n", errortype); break; + case 5: Con_Printf("%s: Unresolvable Address\n", errortype); break; + case 6: Con_Printf("%s: Unrecognized Chunk Type\n", errortype); break; + case 7: Con_Printf("%s: Invalid Mandatory Parameter\n", errortype); break; + case 8: Con_Printf("%s: Unrecognized Parameters\n", errortype); break; + case 9: Con_Printf("%s: No User Data\n", errortype); break; + case 10: Con_Printf("%s: Cookie Received While Shutting Down\n", errortype); break; + case 11: Con_Printf("%s: Restart of an Association with New Addresses\n", errortype); break; + case 12: Con_Printf("%s: User Initiated Abort\n", errortype); break; + case 13: Con_Printf("%s: Protocol Violation [%s]\n", errortype, (char*)(s+1)); break; + default: Con_Printf("%s: Unknown Reason\n", errortype); break; + } + + totallen -= cl; + totallen &= ~3; + s = (struct sctp_errorcause_s*)((qbyte*)s + ((cl+3)&~3)); + } +} + +static void SCTP_Decode(sctp_t *sctp, struct icestate_s *peer) +{ + qbyte resp[4096]; + qboolean finished = false; + + qbyte *msg = net_message.data; + qbyte *msgend = net_message.data+net_message.cursize; + struct sctp_header_s *h = (struct sctp_header_s*)msg; + struct sctp_chunk_s *c = (struct sctp_chunk_s*)(h+1); + quint16_t clen; + if ((qbyte*)c+1 > msgend) + return; //runt + if (h->dstport != sctp->myport) + return; //not for us... + if (h->srcport != sctp->peerport) + return; //not from them... we could check for a INIT but its over dtls anyway so why give a damn. + if (h->verifycode != ((c->type == SCTP_TYPE_INIT)?0:sctp->i.verifycode)) + return; //wrong cookie... (be prepared to parse dupe inits if our ack got lost... + if (h->crc != SCTP_Checksum(h, net_message.cursize)) + return; //crc wrong. assume corruption. + if (net_message.cursize&3) + { + Con_DPrintf("SCTP: packet not padded\n"); + return; //mimic chrome, despite it being pointless. + } + + while ((qbyte*)(c+1) <= msgend) + { + clen = BigShort(c->length); + if ((qbyte*)c + clen > msgend) + break; //corrupt + safeswitch(c->type) + { + case SCTP_TYPE_DATA: + if (clen >= sizeof(struct sctp_chunk_data_s)) + { + struct sctp_chunk_data_s *dc = (void*)c; + quint32_t tsn = BigLong(dc->tsn), u; + qint32_t adv = tsn - sctp->i.ctsn; + sctp->i.ackneeded++; + if (adv >= SCTP_RCVSIZE) + Con_DPrintf("SCTP: Future Packet\n");/*too far in the future. we can't track such things*/ + else if (adv <= 0) + Con_DPrintf("SCTP: PreCumulative\n");/*already acked this*/ + else if (sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7)) + Con_DPrintf("SCTP: Dupe\n");/*already processed it*/ + else + { + qboolean err = false; + + if (c->flags & 2) + { //beginning... + sctp->i.r.firsttns = tsn; + sctp->i.r.tsn = tsn; + sctp->i.r.size = 0; + sctp->i.r.ppid = dc->ppid; + sctp->i.r.sid = dc->sid; + sctp->i.r.seq = dc->seq; + sctp->i.r.toobig = false; + if (finished) + Con_Printf("SCTP: Multiple data chunks\n"); + finished = false; + } + else + { + if (sctp->i.r.tsn != tsn || sctp->i.r.ppid != dc->ppid) + err = true; + } + if (err) + ; //don't corrupt anything in case we get a quick resend that fixes it. + else + { + size_t dlen = clen-sizeof(*dc); + if (adv > sctp->i.htsn) //weird maths in case it wraps. + sctp->i.htsn = adv; + sctp->i.r.tsn++; + if (sctp->i.r.size + clen-sizeof(*dc) > sizeof(sctp->i.r.buf)) + { + Con_DPrintf("SCTP: Oversized\n"); + sctp->i.r.toobig = true; //reassembled packet was too large, just corrupt it. + } + else + { + memcpy(sctp->i.r.buf+sctp->i.r.size, dc+1, dlen); //include the dc header + sctp->i.r.size += dlen; + } + if (c->flags & 1) //an ending. we have the complete packet now. + { + for (u = sctp->i.r.tsn - sctp->i.r.firsttns; u --> 0; ) + { + tsn = sctp->i.r.firsttns + u; + sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] |= 1<<(tsn&7); + } + if (sctp->i.r.toobig) + ;/*ignore it when it cannot be handled*/ + else if (sctp->i.r.ppid == BigLong(SCTP_PPID_DATA)) + finished = true; //FIXME: handle multiple small packets + else if (sctp->i.r.ppid == BigLong(SCTP_PPID_DCEP)) + SCTP_DecodeDCEP(sctp, peer, resp); + } + } + + //FIXME: we don't handle reordering properly at all. + +// if (c->flags & 4) +// Con_Printf("\tUnordered\n"); +// Con_Printf("\tStream Id %i\n", BigShort(dc->sid)); +// Con_Printf("\tStream Seq %i\n", BigShort(dc->seq)); +// Con_Printf("\tPPID %i\n", BigLong(dc->ppid)); + + while(sctp->i.htsn) + { + tsn = sctp->i.ctsn+1; + if (!(sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] & 1<<(tsn&7))) + break; + //advance our cumulative ack. + sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] &= ~(1<<(tsn&7)); + sctp->i.ctsn = tsn; + sctp->i.htsn--; + } + } + } + break; + case SCTP_TYPE_INIT: + case SCTP_TYPE_INITACK: + if (clen >= sizeof(struct sctp_chunk_init_s)) + { + qboolean isack = c->type==SCTP_TYPE_INITACK; + struct sctp_chunk_init_s *init = (void*)c; + struct { + quint16_t ptype; + quint16_t plen; + } *p = (void*)(init+1); + + sctp->i.ctsn = BigLong(init->tsn)-1; + sctp->i.htsn = 0; + sctp->o.verifycode = init->verifycode; + (void)BigLong(init->arwc); + (void)BigShort(init->numoutstreams); + (void)BigShort(init->numinstreams); + + while ((qbyte*)p+sizeof(*p) <= (qbyte*)c+clen) + { + unsigned short ptype = BigShort(p->ptype); + unsigned short plen = BigShort(p->plen); + switch(ptype) + { + case 7: //init cookie + if (sctp->cookie) + Z_Free(sctp->cookie); + sctp->cookiesize = plen - sizeof(*p); + sctp->cookie = Z_Malloc(sctp->cookiesize); + memcpy(sctp->cookie, p+1, sctp->cookiesize); + break; + case 32776: //ASCONF + break; + case 49152: + sctp->peerhasfwdtsn = true; + break; + default: + Con_DPrintf("SCTP: Found unknown init parameter %i||%#x\n", ptype, ptype); + break; + } + p = (void*)((qbyte*)p + ((plen+3)&~3)); + } + + if (isack) + { + sctp->nextreinit = 0; + if (sctp->cookie) + SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + } + else + { + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_init_s *rinit = (void*)(rh+1); + struct { + quint16_t ptype; + quint16_t plen; + struct { + qbyte data[16]; + } cookie; + } *rinitcookie = (void*)(rinit+1); + struct { + quint16_t ptype; + quint16_t plen; + } *rftsn = (void*)(rinitcookie+1); + qbyte *end = sctp->peerhasfwdtsn?(void*)(rftsn+1):(void*)(rinitcookie+1); + + rh->srcport = sctp->myport; + rh->dstport = sctp->peerport; + rh->verifycode = sctp->o.verifycode; + rh->crc = 0; + *rinit = *init; + rinit->chunk.type = SCTP_TYPE_INITACK; + rinit->chunk.flags = 0; + rinit->chunk.length = BigShort(end-(qbyte*)rinit); + rinit->verifycode = sctp->i.verifycode; + rinit->arwc = BigLong(65536); + rinit->numoutstreams = init->numoutstreams; + rinit->numinstreams = init->numinstreams; + rinit->tsn = BigLong(sctp->o.tsn); + rinitcookie->ptype = BigShort(7); + rinitcookie->plen = BigShort(sizeof(*rinitcookie)); + memcpy(&rinitcookie->cookie, "deadbeefdeadbeef", sizeof(rinitcookie->cookie)); //frankly the contents of the cookie are irrelevant to anything. we've already verified the peer's ice pwd/ufrag stuff as well as their dtls certs etc. + rftsn->ptype = BigShort(49152); + rftsn->plen = BigShort(sizeof(*rftsn)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + SCTP_PeerSendPacket(peer, end-resp, rh); + } + } + break; + case SCTP_TYPE_SACK: + if (clen >= sizeof(struct sctp_chunk_sack_s)) + { + struct sctp_chunk_sack_s *sack = (void*)c; + quint32_t tsn = BigLong(sack->tsn); + sctp->o.ctsn = tsn; + + sctp->o.losttsn = BigShort(sack->gaps); //if there's a gap then they're telling us they got a later one. + + //Con_Printf(CON_ERROR"Sack %#x (%i in flight)\n" + // "\tgaps: %i, dupes %i\n", + // tsn, sctp->o.tsn-tsn, + // BigShort(sack->gaps), BigShort(sack->dupes)); + } + break; + case SCTP_TYPE_PING: + if (clen >= sizeof(struct sctp_chunk_s)) + { + struct sctp_chunk_s *ping = (void*)c; + struct sctp_header_s *pongh = Z_Malloc(sizeof(*pongh) + clen); + + pongh->srcport = sctp->myport; + pongh->dstport = sctp->peerport; + pongh->verifycode = sctp->o.verifycode; + pongh->crc = 0; + memcpy(pongh+1, ping, clen); + ((struct sctp_chunk_s*)(pongh+1))->type = SCTP_TYPE_PONG; + + //complete. calc the proper crc and send it off. + pongh->crc = SCTP_Checksum(pongh, sizeof(*pongh) + clen); + SCTP_PeerSendPacket(peer, sizeof(*pongh) + clen, pongh); + Z_Free(pongh); + } + break; +// case SCTP_TYPE_PONG: //we don't send pings + case SCTP_TYPE_ABORT: + ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + SCTP_ErrorChunk("Abort", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + break; + case SCTP_TYPE_SHUTDOWN: //FIXME. we should send an ack... + ICE_Set(peer, "state", STRINGIFY(ICE_FAILED)); + Con_DPrintf(CON_ERROR"SCTP: Shutdown\n"); + break; +// case SCTP_TYPE_SHUTDOWNACK: //we don't send shutdowns, cos we're lame like that... + case SCTP_TYPE_ERROR: + //not fatal... + SCTP_ErrorChunk("Error", (struct sctp_errorcause_s*)(c+1), clen-sizeof(*c)); + break; + case SCTP_TYPE_COOKIEECHO: + if (clen >= sizeof(struct sctp_chunk_s)) + { + struct sctp_header_s *rh = (void*)resp; + struct sctp_chunk_s *rack = (void*)(rh+1); + qbyte *end = (void*)(rack+1); + + rh->srcport = sctp->myport; + rh->dstport = sctp->peerport; + rh->verifycode = sctp->o.verifycode; + rh->crc = 0; + rack->type = SCTP_TYPE_COOKIEACK; + rack->flags = 0; + rack->length = BigShort(sizeof(*rack)); + + //complete. calc the proper crc and send it off. + rh->crc = SCTP_Checksum(rh, end-resp); + SCTP_PeerSendPacket(peer, end-resp, rh); + + sctp->o.writable = true; //channel SHOULD now be open for data. + } + break; + case SCTP_TYPE_COOKIEACK: + sctp->o.writable = true; //we know the other end is now open. + break; + case SCTP_TYPE_FORWARDTSN: + if (clen >= sizeof(struct sctp_chunk_fwdtsn_s)) + { + struct sctp_chunk_fwdtsn_s *fwd = (void*)c; + quint32_t tsn = BigLong(fwd->tsn), count; + count = tsn - sctp->i.ctsn; + if ((int)count < 0) + break; //overflow? don't go backwards. + if (count > 1024) + count = 1024; //don't advance too much in one go. we'd block and its probably an error anyway. + while(count --> 0) + { + tsn = ++sctp->i.ctsn; + sctp->i.received[(tsn>>3)%sizeof(sctp->i.received)] &= ~(1<<(tsn&7)); + if (sctp->i.htsn) + sctp->i.htsn--; + } + } + break; +// case SCTP_TYPE_SHUTDOWNDONE: + safedefault: + //no idea what this chunk is, just ignore it... + Con_DPrintf("SCTP: Unsupported chunk %i\n", c->type); + break; + } + c = (struct sctp_chunk_s*)((qbyte*)c + ((clen+3)&~3)); //next chunk is 4-byte aligned. + } + + if (sctp->i.ackneeded >= 5) + SCTP_Transmit(sctp, peer, NULL, 0); //make sure we send acks occasionally even if we have nothing else to say. + + //if we read something, spew it out and return to caller. + if (finished) + { + memmove(net_message.data, sctp->i.r.buf, sctp->i.r.size); + net_message.cursize = sctp->i.r.size; + } + else + net_message.cursize = 0; //nothing to read. +} + +//======================================== +#endif + #if defined(SUPPORT_ICE) || defined(MASTERONLY) qboolean ICE_WasStun(ftenet_connections_t *col) { +#ifdef SUPPORT_ICE + if (net_from.type == NA_ICE) + return false; //this stuff over an ICE connection doesn't make sense. +#endif + #if defined(HAVE_CLIENT) && defined(VOICECHAT) if (col == cls.sockets) { @@ -1263,12 +2163,12 @@ qboolean ICE_WasStun(ftenet_connections_t *col) } #endif - if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20) + if ((net_from.type == NA_IP || net_from.type == NA_IPV6) && net_message.cursize >= 20 && *net_message.data<2) { stunhdr_t *stun = (stunhdr_t*)net_message.data; int stunlen = BigShort(stun->msglen); #ifdef SUPPORT_ICE - if ((stun->msgtype == BigShort(0x0101) || stun->msgtype == BigShort(0x0111)) && net_message.cursize == stunlen + sizeof(*stun)) + if ((stun->msgtype == BigShort(STUN_BINDING|STUN_REPLY) || stun->msgtype == BigShort(STUN_BINDING|STUN_ERROR)) && net_message.cursize == stunlen + sizeof(*stun)) { //binding reply (or error) netadr_t adr = net_from; @@ -1326,7 +2226,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) { char str[256]; struct icecandidate_s *rc; - if (con->mode != ICEM_ICE) + if (con->mode == ICEM_RAW) continue; if (NET_CompareAdr(&net_from, &con->pubstunserver)) @@ -1411,14 +2311,14 @@ qboolean ICE_WasStun(ftenet_connections_t *col) } return true; } - else if (stun->msgtype == BigShort(0x0011) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + else if (stun->msgtype == BigShort(STUN_BINDING|STUN_INDICATION))// && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) { - //binding indication. used as an rtp keepalive. + //binding indication. used as an rtp keepalive. should have a fingerprint return true; } else #endif - if (stun->msgtype == BigShort(0x0001) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) + if (stun->msgtype == BigShort(STUN_BINDING|STUN_REQUEST) && net_message.cursize == stunlen + sizeof(*stun) && stun->magiccookie == BigLong(0x2112a442)) { char username[256]; char integrity[20]; @@ -1447,7 +2347,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) if (alen+sizeof(*attr) > stunlen) return false; switch((unsigned short)BigShort(attr->attrtype)) - { + { case 0xc057: /*'network cost'*/ break; default: //unknown attributes < 0x8000 are 'mandatory to parse', and such packets must be dropped in their entirety. //other ones are okay. @@ -1512,6 +2412,11 @@ qboolean ICE_WasStun(ftenet_connections_t *col) Con_DPrintf("Received STUN request from unknown user \"%s\"\n", username); return true; } + /*else if (con->chosenpeer.type != NA_INVALID) + { //got one. + if (!NET_CompareAdr(&net_from, &con->chosenpeer)) + return true; //FIXME: we're too stupid to handle switching. pretend to be dead. + }*/ else if (con->state == ICE_INACTIVE) return true; //bad timing else @@ -1586,7 +2491,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) //this is problematic, however, as we don't actually know the real priority that the peer thinks we'll nominate it with. if (con->chosenpeer.type != NA_INVALID && !NET_CompareAdr(&net_from, &con->chosenpeer)) - Con_DPrintf("ICE: Duplicate use-candidate\n"); + Con_DPrintf(CON_WARNING"ICE: Alternative use-candidate\n"); con->chosenpeer = net_from; Con_DPrintf("ICE: use-candidate: %s\n", NET_AdrToString(data, sizeof(data), &net_from)); @@ -1636,7 +2541,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col) //Con_DPrintf("STUN from %s\n", NET_AdrToString(data, sizeof(data), &net_from)); - MSG_WriteShort(&buf, BigShort(error?0x0111:0x0101)); + MSG_WriteShort(&buf, BigShort(error?(STUN_BINDING|STUN_ERROR):(STUN_BINDING|STUN_REPLY))); MSG_WriteShort(&buf, BigShort(0)); //fill in later MSG_WriteLong(&buf, stun->magiccookie); MSG_WriteLong(&buf, stun->transactid[0]); @@ -1711,8 +2616,74 @@ qboolean ICE_WasStun(ftenet_connections_t *col) return true; } } + + +#ifdef SUPPORT_ICE + { + struct icestate_s *con; + struct icecandidate_s *rc; + for (con = icelist; con; con = con->next) + { + for (rc = con->rc; rc; rc = rc->next) + { + if (NET_CompareAdr(&net_from, &rc->peer)) + { + // if (rc->reachable) + { //found it. fix up its source address to our ICE connection (so we don't have path-switching issues) and keep chugging along. + + con->timeout = Sys_Milliseconds() + 32; //not dead yet... + + if (con->dtlsstate) + { + switch(con->dtlsfuncs->Received(con->dtlsstate, &net_message)) + { + case NETERR_SENT: + break; // + case NETERR_NOROUTE: + return false; //not a dtls packet at all. don't de-ICE it when we're meant to be using ICE. + case NETERR_DISCONNECTED: //dtls failure. ICE failed. + iceapi.ICE_Set(con, "state", STRINGIFY(ICE_FAILED)); + return true; + default: //some kind of failure decoding the dtls packet. drop it. + return true; + } + } + net_from = con->qadr; + if (con->sctp) + SCTP_Decode(con->sctp, con); + if (net_message.cursize) + col->ReadGamePacket(); + return true; + } + } + } + } + } +#endif return false; } +#ifdef SUPPORT_ICE +neterr_t ICE_SendPacket(ftenet_connections_t *col, size_t length, const void *data, netadr_t *to) +{ + struct icestate_s *con; + for (con = icelist; con; con = con->next) + { + if (NET_CompareAdr(to, &con->qadr)) + { + if (con->sctp) + return SCTP_Transmit(con->sctp, con, data, length); + if (con->dtlsstate) + return SCTP_PeerSendPacket(con, length, data); + if (con->chosenpeer.type != NA_INVALID) + return NET_SendPacket(col, length, data, &con->chosenpeer); + if (con->state < ICE_CONNECTING) + return NETERR_DISCONNECTED; + return NETERR_CLOGGED; //still pending + } + } + return NETERR_DISCONNECTED; +} +#endif #endif @@ -1741,6 +2712,8 @@ typedef struct { size_t outsize; int error; //outgoing data is corrupt. kill it. + + //client state... struct icestate_s *ice; int serverid; @@ -1862,13 +2835,33 @@ static void FTENET_ICE_Heartbeat(ftenet_ice_connection_t *b) } #endif } +static void FTENET_ICE_SendOffer(ftenet_ice_connection_t *b, int cl, struct icestate_s *ice, const char *type) +{ + char buf[8192]; + //okay, now send the sdp to our peer. + if (iceapi.ICE_Get(ice, type, buf, sizeof(buf))) + { + char json[8192+256]; + if (ice->mode == ICEM_WEBRTC) + { + Q_strncpyz(json, va("{\"type\":\"%s\",\"sdp\":\"", type+3), sizeof(json)); + COM_QuotedString(buf, json+strlen(json), sizeof(json)-strlen(json)-2, true); + Q_strncatz(json, "\"}", sizeof(json)); + FTENET_ICE_SplurgeCmd(b, ICEMSG_OFFER, cl, json); + } + else + FTENET_ICE_SplurgeCmd(b, ICEMSG_OFFER, cl, buf); + + ice->blockcandidates = false; + } +} static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct icestate_s **ret) { //sends offer - char buf[8192]; + char buf[256]; struct icestate_s *ice; if (*ret) iceapi.ICE_Close(*ret); - ice = *ret = iceapi.ICE_Create(b, NULL, "", ICEM_ICE, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT); + ice = *ret = iceapi.ICE_Create(b, NULL, b->generic.islisten?NULL:va("/%s", b->gamename), ICEM_WEBRTC, b->generic.islisten?ICEP_QWSERVER:ICEP_QWCLIENT); if (!*ret) return; //some kind of error?!? iceapi.ICE_Set(ice, "controller", b->generic.islisten?"0":"1"); @@ -1877,15 +2870,27 @@ static void FTENET_ICE_Establish(ftenet_ice_connection_t *b, int cl, struct ices iceapi.ICE_Set(ice, "stunport", buf); iceapi.ICE_Set(ice, "stunip", b->brokername); - //okay, now send the sdp to our peer. - if (iceapi.ICE_Get(ice, "sdp", buf, sizeof(buf))) - FTENET_ICE_SplurgeCmd(b, ICEMSG_OFFER, cl, buf); + if (!b->generic.islisten) + FTENET_ICE_SendOffer(b, cl, ice, "sdpoffer"); } static void FTENET_ICE_Refresh(ftenet_ice_connection_t *b, int cl, struct icestate_s *ice) { //sends offer char buf[8192]; + if (ice->blockcandidates) + return; //don't send candidates before the offers... while (ice && iceapi.ICE_GetLCandidateSDP(ice, buf, sizeof(buf))) - FTENET_ICE_SplurgeCmd(b, ICEMSG_CANDIDATE, cl, buf); + { + char json[8192+256]; + if (ice->mode == ICEM_WEBRTC) + { + Q_strncpyz(json, "{\"candidate\":\"", sizeof(json)); + COM_QuotedString(buf+2, json+strlen(json), sizeof(json)-strlen(json)-2, true); + Q_strncatz(json, "\",\"sdpMid\":\"0\",\"sdpMLineIndex\":0}", sizeof(json)); + FTENET_ICE_SplurgeCmd(b, ICEMSG_CANDIDATE, cl, json); + } + else + FTENET_ICE_SplurgeCmd(b, ICEMSG_CANDIDATE, cl, buf); + } } static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon) { @@ -1893,14 +2898,7 @@ static qboolean FTENET_ICE_GetPacket(ftenet_generic_connection_t *gcon) int ctrl, len, cmd, cl, ofs; char *data, n; -#ifdef HAVE_CLIENT - //there's no point in hanging on to the ICE connection if we're not going to do anything with it now that we're connected. - //(shutting off the tcp connection will notify the server to shut down too) - if (!b->generic.islisten && !CL_TryingToConnect()) - b->error = true; - else -#endif - if (!b->broker) + if (!b->broker) { const char *s; if (b->timeout > realtime) @@ -2068,13 +3066,17 @@ handleerror: // Con_Printf("Broker closing connection: %s\n", data); } break; + case ICEMSG_NAMEINUSE: + Con_Printf("Unable to listen on /%s - name already taken\n", b->gamename); + b->error = true; //try again later. + break; case ICEMSG_GREETING: //reports the trailing url we're 'listening' on. anyone else using that url will connect to us. data = strchr(data, '/'); if (data++) Q_strncpyz(b->gamename, data, sizeof(b->gamename)); Con_Printf("Publicly listening on /%s\n", b->gamename); break; - case ICEMSG_NEWPEER: //connection established with a new peer + case ICEMSG_NEWPEER: //relay connection established with a new peer //note that the server ought to wait for an offer from the client before replying with any ice state, but it doesn't really matter for our use-case. if (b->generic.islisten) { @@ -2094,26 +3096,47 @@ handleerror: } break; case ICEMSG_OFFER: //we received an offer from a client + if (!strncmp(data, "{\"type\":\"offer\",\"sdp\":\"", 23)) + { + data += 22; + COM_ParseCString(data, com_token, sizeof(com_token), NULL); + data = com_token; + } + else if (!strncmp(data, "{\"type\":\"answer\",\"sdp\":\"", 24)) + { + data += 23; + COM_ParseCString(data, com_token, sizeof(com_token), NULL); + data = com_token; + } if (b->generic.islisten) { -// Con_Printf("Client offered: %s\n", data); + Con_Printf("Client offered: %s\n", data); if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) { - iceapi.ICE_Set(b->clients[cl].ice, "sdp", data); + iceapi.ICE_Set(b->clients[cl].ice, "sdpoffer", data); iceapi.ICE_Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING)); + + FTENET_ICE_SendOffer(b, cl, b->clients[cl].ice, "sdpanswer"); } } else { -// Con_Printf("Server offered: %s\n", data); if (b->ice) { - iceapi.ICE_Set(b->ice, "sdp", data); + iceapi.ICE_Set(b->ice, "sdpanswer", data); iceapi.ICE_Set(b->ice, "state", STRINGIFY(ICE_CONNECTING)); } } break; case ICEMSG_CANDIDATE: + if (!strncmp(data, "{\"candidate\":\"", 14)) + { + data += 13; + com_token[0]='a'; + com_token[1]='='; + COM_ParseCString(data, com_token+2, sizeof(com_token)-2, NULL); + data = com_token; + } // Con_Printf("Candidate update: %s\n", data); if (b->generic.islisten) { @@ -2150,13 +3173,17 @@ static void FTENET_ICE_PrintStatus(ftenet_generic_connection_t *gcon) size_t c; if (b->ice) - ICE_Debug(b->ice); + ICE_Debug(b->ice, b->generic.islisten); if (b->numclients) { - Con_Printf("%u clients\n", (unsigned)b->numclients); + size_t activeice = 0; for (c = 0; c < b->numclients; c++) if (b->clients[c].ice) - ICE_Debug(b->clients[c].ice); + { + activeice++; + ICE_Debug(b->clients[c].ice, b->generic.islisten); + } + Con_Printf("%u ICE connections\n", (unsigned)activeice); } } static int FTENET_ICE_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, const char **adrparms, int maxaddresses) @@ -2236,6 +3263,7 @@ ftenet_generic_connection_t *FTENET_ICE_EstablishConnection(ftenet_connections_t newcon->timeout = realtime; newcon->heartbeat = realtime; newcon->nextping = realtime; + newcon->generic.owner = col; newcon->generic.thesocket = INVALID_SOCKET; newcon->generic.addrtype[0] = NA_INVALID; diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index 4554052f0..7b941f1a6 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -167,7 +167,7 @@ static ssize_t (VARGS *qgnutls_record_recv)(gnutls_session_t session, void *data static void (VARGS *qgnutls_certificate_set_verify_function)(gnutls_certificate_credentials_t cred, gnutls_certificate_verify_function *func); static void *(VARGS *qgnutls_session_get_ptr)(gnutls_session_t session); static void (VARGS *qgnutls_session_set_ptr)(gnutls_session_t session, void *ptr); -int (VARGS *qgnutls_session_channel_binding)(gnutls_session_t session, gnutls_channel_binding_t cbtype, gnutls_datum_t * cb); +static int (VARGS *qgnutls_session_channel_binding)(gnutls_session_t session, gnutls_channel_binding_t cbtype, gnutls_datum_t * cb); #ifdef GNUTLS_HAVE_SYSTEMTRUST static int (VARGS *qgnutls_certificate_set_x509_system_trust)(gnutls_certificate_credentials_t cred); #else @@ -477,6 +477,10 @@ typedef struct int pullerror; //adding these two because actual networking errors are not getting represented properly, at least with regard to timeouts. int pusherror; + gnutls_certificate_credentials_t certcred; + hashfunc_t *peerhashfunc; + qbyte peerdigest[DIGEST_MAXSIZE]; + qboolean challenging; //not sure this is actually needed, but hey. void *cbctx; neterr_t(*cbpush)(void *cbctx, const qbyte *data, size_t datasize); @@ -571,6 +575,8 @@ static qboolean QDECL SSL_CloseFile(vfsfile_t *vfs) VFS_CLOSE(file->stream); file->stream = NULL; } + if (file->certcred) + qgnutls_certificate_free_credentials(file->certcred); Z_Free(vfs); return true; } @@ -754,6 +760,33 @@ static int QDECL SSL_CheckCert(gnutls_session_t session) return GNUTLS_E_CERTIFICATE_ERROR; } +static int QDECL SSL_CheckFingerprint(gnutls_session_t session) +{ //actual certificate doesn't matter so long as it matches the hash we expect. + gnutlsfile_t *file = qgnutls_session_get_ptr (session); + unsigned int certcount, j; + const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(session, &certcount); + if (certlist && certcount) + { + qbyte digest[DIGEST_MAXSIZE]; + void *ctx = alloca(file->peerhashfunc->contextsize); + + file->peerhashfunc->init(ctx); + for (j = 0; j < certcount; j++) + file->peerhashfunc->process(ctx, certlist[j].data, certlist[j].size); + file->peerhashfunc->terminate(digest, ctx); + +{ +vfsfile_t *f = FS_OpenVFS("/tmp/cert", "wb", FS_SYSTEM); +VFS_WRITE(f, certlist[0].data, certlist[0].size); +VFS_CLOSE(f); +} + if (!memcmp(digest, file->peerdigest, file->peerhashfunc->digestsize)) + return 0; + } + Con_DPrintf(CON_ERROR "%s: rejecting certificate\n", file->certname); + return GNUTLS_E_CERTIFICATE_ERROR; +} + //return 1 to read data. //-1 for error //0 for not ready @@ -1352,40 +1385,48 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole qgnutls_server_name_set(newf->session, GNUTLS_NAME_DNS, newf->certname, strlen(newf->certname)); qgnutls_session_set_ptr(newf->session, newf); -#ifdef USE_ANON - //qgnutls_kx_set_priority (newf->session, kx_prio); - qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]); -#else -#ifdef HAVE_DTLS - if (datagram && !isserver) - { //do psk as needed. we can still do the cert stuff if the server isn't doing psk. - gnutls_psk_client_credentials_t pskcred; - qgnutls_psk_allocate_client_credentials(&pskcred); - qgnutls_psk_set_client_credentials_function(pskcred, GetPSKForServer); - - qgnutls_set_default_priority_append (newf->session, "+ECDHE-PSK:+DHE-PSK:+PSK", NULL, 0); - qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred); - } - else if (datagram && isserver && (*dtls_psk_user.string || servercertfail)) - { //offer some arbitrary PSK for dtls clients. - gnutls_psk_server_credentials_t pskcred; - qgnutls_psk_allocate_server_credentials(&pskcred); - qgnutls_psk_set_server_credentials_function(pskcred, GetPSKForUser); - if (*dtls_psk_hint.string) - qgnutls_psk_set_server_credentials_hint(pskcred, dtls_psk_hint.string); - - qgnutls_set_default_priority_append (newf->session, ("-KX-ALL:+ECDHE-PSK:+DHE-PSK:+PSK")+(servercertfail?0:8), NULL, 0); - qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred); - } - else -#endif + if (newf->certcred) { - // Use default priorities for regular tls sessions + qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, newf->certcred); qgnutls_set_default_priority (newf->session); } + else + { +#ifdef USE_ANON + //qgnutls_kx_set_priority (newf->session, kx_prio); + qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]); +#else +#ifdef HAVE_DTLS + if (datagram && !isserver) + { //do psk as needed. we can still do the cert stuff if the server isn't doing psk. + gnutls_psk_client_credentials_t pskcred; + qgnutls_psk_allocate_client_credentials(&pskcred); + qgnutls_psk_set_client_credentials_function(pskcred, GetPSKForServer); + + qgnutls_set_default_priority_append (newf->session, "+ECDHE-PSK:+DHE-PSK:+PSK", NULL, 0); + qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred); + } + else if (datagram && isserver && (*dtls_psk_user.string || servercertfail)) + { //offer some arbitrary PSK for dtls clients. + gnutls_psk_server_credentials_t pskcred; + qgnutls_psk_allocate_server_credentials(&pskcred); + qgnutls_psk_set_server_credentials_function(pskcred, GetPSKForUser); + if (*dtls_psk_hint.string) + qgnutls_psk_set_server_credentials_hint(pskcred, dtls_psk_hint.string); + + qgnutls_set_default_priority_append (newf->session, ("-KX-ALL:+ECDHE-PSK:+DHE-PSK:+PSK")+(servercertfail?0:8), NULL, 0); + qgnutls_credentials_set(newf->session, GNUTLS_CRD_PSK, pskcred); + } + else #endif - if (xcred[isserver]) - qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, xcred[isserver]); + { + // Use default priorities for regular tls sessions + qgnutls_set_default_priority (newf->session); + } +#endif + if (xcred[isserver]) + qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, xcred[isserver]); + } // tell gnutls how to send/receive data qgnutls_transport_set_ptr (newf->session, newf); @@ -1542,7 +1583,7 @@ static void GNUDTLS_DestroyContext(void *ctx) { SSL_Close(ctx); } -static void *GNUDTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) +static void *GNUDTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) { gnutlsfile_t *newf; @@ -1559,7 +1600,19 @@ static void *GNUDTLS_CreateContext(const char *remotehost, void *cbctx, neterr_t // Sys_Printf("DTLS_CreateContext: server=%i\n", isserver); - SSL_SetCertificateName(newf, remotehost); + if (credinfo && credinfo->local.cert && credinfo->local.key && credinfo->peer.hash) + { + gnutls_datum_t pub = {credinfo->local.cert, credinfo->local.certsize}, + priv = {credinfo->local.key, credinfo->local.keysize}; + qgnutls_certificate_allocate_credentials (&newf->certcred); + qgnutls_certificate_set_x509_key_mem(newf->certcred, &pub, &priv, GNUTLS_X509_FMT_DER); + + newf->peerhashfunc = credinfo->peer.hash; + memcpy(newf->peerdigest, credinfo->peer.digest, newf->peerhashfunc->digestsize); + qgnutls_certificate_set_verify_function (newf->certcred, SSL_CheckFingerprint); + } + + SSL_SetCertificateName(newf, credinfo?credinfo->peer.name:NULL); if (!SSL_InitConnection(newf, isserver, true)) { @@ -1613,7 +1666,7 @@ static neterr_t GNUDTLS_Received(void *ctx, sizebuf_t *message) message->data, message->cursize, &f->prestate); - if (ret < 0) + if (ret == GNUTLS_E_BAD_COOKIE) { qgnutls_dtls_cookie_send(&cookie_key, &cli_addr, sizeof(cli_addr), @@ -1621,6 +1674,8 @@ static neterr_t GNUDTLS_Received(void *ctx, sizebuf_t *message) (gnutls_transport_ptr_t)f, DTLS_Push); return NETERR_CLOGGED; } + else if (ret < 0) + return NETERR_NOROUTE; f->challenging = false; qgnutls_dtls_prestate_set(f->session, &f->prestate); @@ -1741,6 +1796,80 @@ static neterr_t GNUDTLS_Timeouts(void *ctx) return NETERR_SENT; } +static qboolean GNUDTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *qcred) +{ + gnutls_datum_t priv = {NULL}, pub = {NULL}; + gnutls_x509_privkey_t key; + gnutls_x509_crt_t cert; + char serial[64]; + char randomsub[32+1]; + const char *errstr; + gnutls_pk_algorithm_t privalgo = GNUTLS_PK_RSA; + int ret; + + qgnutls_x509_privkey_init(&key); + ret = qgnutls_x509_privkey_generate(key, privalgo, qgnutls_sec_param_to_pk_bits(privalgo, GNUTLS_SEC_PARAM_HIGH), 0); + if (ret < 0) + Con_Printf(CON_ERROR"gnutls_x509_privkey_generate failed: %i\n", ret); + ret = qgnutls_x509_privkey_export2(key, GNUTLS_X509_FMT_DER, &priv); + if (ret < 0) + Con_Printf(CON_ERROR"gnutls_x509_privkey_export2 failed: %i\n", ret); + + //stoopid browsers insisting that serial numbers are different even on throw-away self-signed certs. + //we should probably just go and make our own root ca/master. post it a cert and get a signed one (with sequential serial) back or something. + //we'll probably want something like that for client certs anyway, for stat tracking. + Q_snprintfz(serial, sizeof(serial), "%u", (unsigned)time(NULL)); + + qgnutls_x509_crt_init(&cert); + qgnutls_x509_crt_set_version(cert, 1); + qgnutls_x509_crt_set_activation_time(cert, time(NULL)-1); + qgnutls_x509_crt_set_expiration_time(cert, time(NULL)+(time_t)10*365*24*60*60); + qgnutls_x509_crt_set_serial(cert, serial, strlen(serial)); +// qgnutls_x509_crt_set_key_usage(cert, GNUTLS_KEY_DIGITAL_SIGNATURE); + if (!subject) + { + qbyte tmp[16]; + Sys_RandomBytes(tmp, sizeof(tmp)); + randomsub[Base16_EncodeBlock(tmp, sizeof(tmp), randomsub, sizeof(randomsub))] = 0; + subject = randomsub; + } + + if (qgnutls_x509_crt_set_dn(cert, va("CN=%s", subject), &errstr) < 0) + Con_Printf(CON_ERROR"gnutls_x509_crt_set_dn failed: %s\n", errstr); + if (qgnutls_x509_crt_set_issuer_dn(cert, va("CN=%s", subject), &errstr) < 0) + Con_Printf(CON_ERROR"gnutls_x509_crt_set_issuer_dn failed: %s\n", errstr); +// qgnutls_x509_crt_set_key_usage(cert, GNUTLS_KEY_KEY_ENCIPHERMENT|GNUTLS_KEY_DATA_ENCIPHERMENT|); + + qgnutls_x509_crt_set_key(cert, key); + + /*sign it with our private key*/ + { + gnutls_privkey_t akey; + qgnutls_privkey_init(&akey); + qgnutls_privkey_import_x509(akey, key, GNUTLS_PRIVKEY_IMPORT_COPY); + ret = qgnutls_x509_crt_privkey_sign(cert, cert, akey, GNUTLS_DIG_SHA256, 0); + if (ret < 0) + Con_Printf(CON_ERROR"gnutls_x509_crt_privkey_sign failed: %i\n", ret); + qgnutls_privkey_deinit(akey); + } + ret = qgnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &pub); + qgnutls_x509_crt_deinit(cert); + qgnutls_x509_privkey_deinit(key); + if (ret < 0) + Con_Printf(CON_ERROR"gnutls_x509_crt_export2 failed: %i\n", ret); + + //okay, we have them in memory, make sure the rest of the engine can play with it. + qcred->certsize = pub.size; + memcpy(qcred->cert = Z_Malloc(pub.size), pub.data, pub.size); + + qcred->keysize = priv.size; + memcpy(qcred->key = Z_Malloc(priv.size), priv.data, priv.size); + + (*qgnutls_free)(priv.data); + (*qgnutls_free)(pub.data); + + return true; +} static const dtlsfuncs_t dtlsfuncs_gnutls = { GNUDTLS_CreateContext, @@ -1749,6 +1878,8 @@ static const dtlsfuncs_t dtlsfuncs_gnutls = GNUDTLS_Transmit, GNUDTLS_Received, GNUDTLS_Timeouts, + NULL, + GNUDTLS_GenTempCertificate }; static const dtlsfuncs_t *GNUDTLS_InitServer(void) { @@ -1760,6 +1891,8 @@ static const dtlsfuncs_t *GNUDTLS_InitServer(void) } static const dtlsfuncs_t *GNUDTLS_InitClient(void) { + if (!SSL_InitGlobal(false)) + return NULL; return &dtlsfuncs_gnutls; } #else diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 14601ef78..9c1d59db7 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -556,6 +556,15 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) } #endif +#ifdef SUPPORT_ICE + if (a->type == NA_ICE) + { + if (!strcmp(a->address.icename, b->address.icename)) + return true; + return false; + } +#endif + if (a->type == NA_INVALID && a->prot) return true; //mneh... @@ -673,6 +682,15 @@ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) } #endif +#ifdef SUPPORT_ICE + if (a->type == NA_ICE) + { + if (!strcmp(a->address.icename, b->address.icename)) + return true; + return false; + } +#endif + if (a->type == NA_INVALID && a->prot) return true; //mneh... @@ -1007,6 +1025,12 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) break; #endif +#ifdef SUPPORT_ICE + case NA_ICE: + snprintf (s, len, "%s[%s]", prot, a->address.icename); + break; +#endif + default: if (a->prot == NP_RTC_TCP || a->prot == NP_RTC_TLS) Q_strncpyz(s, prot, len); @@ -1144,6 +1168,12 @@ char *NET_BaseAdrToString (char *s, int len, netadr_t *a) return NET_AdrToString(s, len, a); #endif +#ifdef SUPPORT_ICE + case NA_ICE: + snprintf (s, len, "%s[%s]", prot, a->address.icename); + break; +#endif + default: if (a->prot == NP_RTC_TCP || a->prot == NP_RTC_TLS) Q_strncpyz(s, prot, len); @@ -1898,6 +1928,9 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) #endif #ifdef IRCCONNECT case NA_IRC: +#endif +#ifdef SUPPORT_ICE + case NA_ICE: #endif break; @@ -1916,29 +1949,37 @@ int ParsePartialIP(const char *s, netadr_t *a) memset (a, 0, sizeof(*a)); + //if its ::ffff:a.b.c.d then parse it as ipv4 by just skipping the prefix. + //we ought to leave it as ipv6, but any printing will show it as ipv4 anyway. + if (!strncasecmp(s, "::ffff:", 7) && strchr(s+7, '.') && !strchr(s+7, ':')) + s += 7; + //multiple colons == ipv6 + //single colon = ipv4:port colon = strchr(s, ':'); if (colon && strchr(colon+1, ':')) { qbyte *address = a->address.ip6; unsigned long tmp; + int gapstart = -1; //in bytes... bits = 0; - //FIXME: check for ::ffff:a.b.c.d - //FIXME: check for xx::xx - if (s[0] == ':' && s[1] == ':' && !s[2]) - { - s+=2; - bits = 1; - } - else while(*s) + while(*s) { tmp = strtoul(s, &colon, 16); - if (tmp > 0xffff) - return 0; //invalid - *address++ = (tmp>>8)&0xff; - *address++ = (tmp>>0)&0xff; - bits += 16; + if (colon == s) + { + if (bits) + return 0; + } + else + { + if (tmp > 0xffff) + return 0; //invalid + *address++ = (tmp>>8)&0xff; + *address++ = (tmp>>0)&0xff; + bits += 16; + } if (bits == 128) { @@ -1948,16 +1989,35 @@ int ParsePartialIP(const char *s, netadr_t *a) } - //double-colon ends it here. we can't parse xx::xx + //double-colon is a gap (or partial end). //hopefully the last 64 bits or whatever will be irrelevant anyway, so such addresses won't be common - if (colon[0] == ':' && colon[1] == ':' && !colon[2]) - break; - if (*colon == ':') + if (colon[0] == ':' && colon[1] == ':') + { + if (gapstart >= 0) + return 0; //only one gap... + if (!colon[2]) + break; //nothing after. its partial. + gapstart = bits/8; + colon+=2; + } + else if (*colon == ':' && bits) colon++; + else if (*colon) + return 0; //gibberish here... else - return 0; //don't allow it if it just ended without a double-colon. + break; //end of address... anything more is a partial. s = colon; } + if (gapstart >= 0) + { + int tailsize = (bits/8)-gapstart; //bits to move to the end + int gapsize = 16 - gapstart - tailsize; + memmove(a->address.ip6+gapstart+gapsize, a->address.ip6+gapstart, tailsize); //move the bits we found to the end + memset(a->address.ip6+gapstart, 0, gapsize); //and make sure the gap is cleared + bits = 128; //found it all, or something. + } + if (!bits) + bits = 1; //FIXME: return of 0 is an error, but :: is 0-length... lie. a->type = NA_IPV6; a->port = 0; } @@ -3018,7 +3078,7 @@ static neterr_t FTENET_DTLS_DoSendPacket(void *cbctx, const qbyte *data, size_t struct dtlspeer_s *peer = cbctx; return NET_SendPacketCol(peer->col, length, data, &peer->addr); } -qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const char *hostname) +qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const dtlscred_t *cred) { extern cvar_t timeout; struct dtlspeer_s *peer; @@ -3040,7 +3100,7 @@ qboolean NET_DTLS_Create(ftenet_connections_t *col, netadr_t *to, const char *ho else peer->funcs = DTLS_InitClient(); if (peer->funcs) - peer->dtlsstate = peer->funcs->CreateContext(hostname, peer, FTENET_DTLS_DoSendPacket, col->islisten); + peer->dtlsstate = peer->funcs->CreateContext(cred, peer, FTENET_DTLS_DoSendPacket, col->islisten); peer->timeout = realtime+timeout.value; if (peer->dtlsstate) @@ -3171,6 +3231,7 @@ qboolean NET_DTLS_Decode(ftenet_connections_t *col) break; case NETERR_SENT: //we decoded it properly + net_from.prot = NP_DTLS; break; } net_from.prot = NP_DTLS; @@ -3231,7 +3292,7 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char if (col->conn[i]) if (*col->conn[i]->name && !strcmp(col->conn[i]->name, name)) { - if (adr && adr->type != NA_INVALID && col->islisten) + if (adr && (adr->type != NA_INVALID||adr->prot != NP_INVALID) && col->islisten) if (col->conn[i]->ChangeLocalAddress) { if (col->conn[i]->ChangeLocalAddress(col->conn[i], address, adr)) @@ -5179,6 +5240,10 @@ qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcp_connection_t *con, ftenet_tcp_st { if (o != st && o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) { + net_message_buffer[0] = ICEMSG_NAMEINUSE; + strcpy(net_message_buffer+1, st->webrtc.resource); + FTENET_TCP_WebSocket_Splurge(st, WS_PACKETTYPE_BINARYFRAME, net_message_buffer, strlen(net_message_buffer)); + *st->webrtc.resource = 0; //don't trigger shutdown broadcasts to valid clients. return false; //conflict! can't have two servers listening on the same url } @@ -7862,6 +7927,11 @@ void NET_ReadPackets (ftenet_connections_t *collection) while ((p = collection->delayed_packets) && (int)(Sys_Milliseconds()-p->sendtime) > 0) { collection->delayed_packets = p->next; +#ifdef SUPPORT_ICE + if (p->dest.type == NA_ICE) + NET_SendPacketCol (collection, p->cursize, p->data, &p->dest); + else +#endif #ifdef HAVE_DTLS if (p->dest.prot == NP_DTLS) FTENET_DTLS_SendPacket(collection, p->cursize, p->data, &p->dest); @@ -8010,6 +8080,10 @@ neterr_t NET_SendPacket (ftenet_connections_t *collection, int length, const voi } #endif +#ifdef SUPPORT_ICE + if (to->type == NA_ICE) + return ICE_SendPacket(collection, length, data, to); +#endif #ifdef HAVE_DTLS if (to->prot == NP_DTLS) return FTENET_DTLS_SendPacket(collection, length, data, to); @@ -8031,11 +8105,16 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char #ifdef HAVE_DTLS adr->prot = NP_DGRAM; if (NET_EnsureRoute(collection, routename, host, adr)) - if (NET_DTLS_Create(collection, adr, host)) + { + dtlscred_t cred; + memset(&cred, 0, sizeof(cred)); + cred.peer.name = host; + if (NET_DTLS_Create(collection, adr, &cred)) { adr->prot = NP_DTLS; return true; } + } adr->prot = NP_DTLS; #endif return false; diff --git a/engine/common/netinc.h b/engine/common/netinc.h index 32245bc3a..155f9be2f 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -266,8 +266,9 @@ enum iceproto_e }; enum icemode_e { - ICEM_RAW, //not actually interactive beyond a simple handshake. - ICEM_ICE //rfc5245. meant to be able to holepunch, but not implemented properly yet. + ICEM_RAW, //not actually interactive beyond a simple handshake. + ICEM_ICE, //rfc5245. meant to be able to holepunch, but not implemented properly yet. + ICEM_WEBRTC, //IP+UDP+ICE+DTLS+SCTP... no more layers? :o }; enum icestate_e { @@ -346,15 +347,33 @@ enum hashvalidation_e }; struct dtlsfuncs_s; #ifdef HAVE_DTLS +typedef struct dtlscred_s +{ + struct dtlslocalcred_s + { + void *cert; + size_t certsize; + void *key; + size_t keysize; + } local; + struct dtlspeercred_s + { + const char *name; //cert must match this if specified + + hashfunc_t *hash; //if set peer's cert MUST match the specified digest (with this hash function) + qbyte digest[DIGEST_MAXSIZE]; + } peer; +} dtlscred_t; typedef struct dtlsfuncs_s { - void *(*CreateContext)(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver); //if remotehost is null then their certificate will not be validated. + void *(*CreateContext)(const dtlscred_t *credinfo, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver); //the certificate to advertise. qboolean (*CheckConnection)(void *cbctx, void *peeraddr, size_t peeraddrsize, void *indata, size_t insize, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), void (*EstablishTrueContext)(void **cbctx, void *state)); void (*DestroyContext)(void *ctx); neterr_t (*Transmit)(void *ctx, const qbyte *data, size_t datasize); neterr_t (*Received)(void *ctx, sizebuf_t *message); //operates in-place... neterr_t (*Timeouts)(void *ctx); void (*GetPeerCertificate)(void *ctx); + qboolean (*GenTempCertificate)(const char *subject, struct dtlslocalcred_s *cred); } dtlsfuncs_t; const dtlsfuncs_t *DTLS_InitServer(void); const dtlsfuncs_t *DTLS_InitClient(void); @@ -428,6 +447,7 @@ enum icemsgtype_s ICEMSG_ACCEPT=5, //go go go (response from offer) ICEMSG_SERVERINFO=6,//server->broker (for advertising the server properly) ICEMSG_SERVERUPDATE=7,//broker->browser (for querying available server lists) + ICEMSG_NAMEINUSE=8, //requested resource is unavailable. }; enum websocketpackettype_e diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 86e6634af..7cacbecb6 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -3144,6 +3144,10 @@ void SV_Voice_UnmuteAll_f(void) { host_client->voice_active = true; } +#else +void SV_Voice_UnmuteAll_f(void) +{ //no-op +} #endif qboolean SV_FindRemotePackage(const char *package, char *url, size_t urlsize) @@ -6385,8 +6389,8 @@ ucmd_t ucmds[] = {"voicetarg", SV_Voice_Target_f}, {"vignore", SV_Voice_Ignore_f}, /*ignore/mute specific player*/ {"muteall", SV_Voice_MuteAll_f}, /*disables*/ - {"unmuteall", SV_Voice_UnmuteAll_f}, /*reenables*/ #endif + {"unmuteall", SV_Voice_UnmuteAll_f}, /*reenables*/ {NULL, NULL} }; diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js index 63a893d83..aa872a424 100644 --- a/engine/web/ftejslib.js +++ b/engine/web/ftejslib.js @@ -774,6 +774,8 @@ mergeInto(LibraryManager.library, return -1; if (s.con == 0) return 0; //not connected yet + if (len == 0) + return 0; //... s.ws.send(HEAPU8.subarray(data, data+len)); return len; }, @@ -838,60 +840,35 @@ mergeInto(LibraryManager.library, s.ws.binaryType = 'arraybuffer'; s.ws.onclose = function(event) { -//console.log("webrtc datachannel closed:") -//console.log(event); s.con = 0; s.err = 1; }; s.ws.onopen = function(event) { -//console.log("webrtc datachannel opened:"); -//console.log(event); s.con = 1; }; s.ws.onmessage = function(event) { -//console.log("webrtc datachannel message:"); -//console.log(event); assert(typeof event.data !== 'string' && event.data.byteLength); s.inq.push(new Uint8Array(event.data)); }; s.pc.onicecandidate = function(e) { -//console.log("onicecandidate: "); -//console.log(e); var desc; if (1) desc = JSON.stringify(e.candidate); else desc = e.candidate.candidate; + if (desc == null) + return; //no more... s.callcb(4, desc); }; - s.pc.oniceconnectionstatechange = function(e) - { -//console.log("oniceconnectionstatechange: "); -//console.log(e); - }; - s.pc.onaddstream = function(e) - { -//console.log("onaddstream: "); -//console.log(e); - }; s.pc.ondatachannel = function(e) { -//console.log("ondatachannel: "); -//console.log(e); - - s.recvchan = e.channel; - s.recvchan.binaryType = 'arraybuffer'; - s.recvchan.onmessage = s.ws.onmessage; - - }; - s.pc.onnegotiationneeded = function(e) - { -//console.log("onnegotiationneeded: "); -//console.log(e); + s.recvchan = e.channel; + s.recvchan.binaryType = 'arraybuffer'; + s.recvchan.onmessage = s.ws.onmessage; }; if (clientside) @@ -900,8 +877,6 @@ mergeInto(LibraryManager.library, function(desc) { s.pc.setLocalDescription(desc); -// console.log("gotlocaldescription: "); -// console.log(desc); if (1) desc = JSON.stringify(desc); @@ -912,8 +887,6 @@ mergeInto(LibraryManager.library, }, function(event) { -// console.log("createOffer error:"); -// console.log(event); s.err = 1; } ); @@ -945,8 +918,6 @@ mergeInto(LibraryManager.library, function(desc) { s.pc.setLocalDescription(desc); -// console.log("gotlocaldescription: "); -// console.log(desc); if (1) desc = JSON.stringify(desc); @@ -957,7 +928,6 @@ mergeInto(LibraryManager.library, }, function(event) { -// console.log("createAnswer error:" + event.toString()); s.err = 1; } ); @@ -977,8 +947,6 @@ mergeInto(LibraryManager.library, desc = JSON.parse(offer); else desc = {candidate:offer, sdpMid:null, sdpMLineIndex:0}; -//console.log("addIceCandidate:"); -//console.log(desc); s.pc.addIceCandidate(desc); } catch(err) { console.log(err); } }, diff --git a/plugins/net_ssl_openssl.c b/plugins/net_ssl_openssl.c index a2ef9d070..f5b460c0f 100644 --- a/plugins/net_ssl_openssl.c +++ b/plugins/net_ssl_openssl.c @@ -13,7 +13,7 @@ static cvar_t *pdtls_psk_hint, *pdtls_psk_user, *pdtls_psk_key; #include "openssl/err.h" #include "openssl/conf.h" -#define assert(c) {if (!(c)) Con_Printf("assert failed: "STRINGIFY(c)"\n");} +#define assert(c) do{if (!(c)) Con_Printf("assert failed: "STRINGIFY(c)"\n");}while(0) static qboolean OSSL_Init(void); static int ossl_fte_certctx; @@ -21,6 +21,9 @@ struct fte_certctx_s { const char *peername; qboolean dtls; + + hashfunc_t *hash; //if set peer's cert MUST match the specified digest (with this hash function) + qbyte digest[DIGEST_MAXSIZE]; }; static struct @@ -221,6 +224,27 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx) SSL *ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); struct fte_certctx_s *uctx = SSL_get_ex_data(ssl, ossl_fte_certctx); + if (uctx->hash) + { //our special 'must-match-digest' mode without any other kind of trust. + X509* cert = X509_STORE_CTX_get_current_cert(x509_ctx); + size_t blobsize; + qbyte *blob; + qbyte *end; + qbyte digest[DIGEST_MAXSIZE]; + void *hctx = alloca(uctx->hash->contextsize); + blobsize = i2d_X509(cert, NULL); + blob = alloca(blobsize); + end = blob; + i2d_X509(cert, &end); + + uctx->hash->init(hctx); + uctx->hash->process(hctx, blob, blobsize); + uctx->hash->terminate(digest, hctx); + + //return 1 for success + return !memcmp(digest, uctx->digest, uctx->hash->digestsize); + } + if(preverify_ok == 0) { int depth = X509_STORE_CTX_get_error_depth(x509_ctx); @@ -279,6 +303,7 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx) } if (netfuncs->CertLog_ConnectOkay && netfuncs->CertLog_ConnectOkay(uctx->peername, blob, blobsize, probs)) return 1; //ignore the errors... + return 0; //allow it. } #endif } @@ -708,10 +733,11 @@ unsigned int OSSL_CL_Validate_PSK(SSL *ssl, const char *hint, char *identity, un return 0; //we don't know what to report. } -static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) +static void *OSSL_CreateContext(const dtlscred_t *cred, void *cbctx, neterr_t(*push)(void *cbctx, const qbyte *data, size_t datasize), qboolean isserver) { //if remotehost is null then their certificate will not be validated. ossldtls_t *n; BIO *sink; + const char *remotehost = cred?cred->peer.name:NULL; if (!remotehost) remotehost = ""; @@ -724,6 +750,8 @@ static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*p n->cert.peername = OSSL_SetCertificateName((char*)(n+1), remotehost); n->cert.dtls = true; + n->cert.hash = cred->peer.hash; + memcpy(n->cert.digest, cred->peer.digest, sizeof(cred->peer.digest)); if (n->ctx) { @@ -731,11 +759,25 @@ static void *OSSL_CreateContext(const char *remotehost, void *cbctx, neterr_t(*p SSL_CTX_set_session_cache_mode(n->ctx, SSL_SESS_CACHE_OFF); - SSL_CTX_set_verify(n->ctx, SSL_VERIFY_PEER, OSSL_Verify_Peer); + SSL_CTX_set_verify(n->ctx, SSL_VERIFY_PEER|(cred->peer.hash?SSL_VERIFY_FAIL_IF_NO_PEER_CERT:0), OSSL_Verify_Peer); SSL_CTX_set_verify_depth(n->ctx, 5); - SSL_CTX_set_options(n->ctx, SSL_OP_NO_COMPRESSION); //compression allows guessing the contents of the stream somehow. + SSL_CTX_set_options(n->ctx, SSL_OP_NO_COMPRESSION| //compression allows guessing the contents of the stream somehow. + SSL_OP_NO_RENEGOTIATION); - if (isserver) + if (cred->local.certsize||cred->local.keysize) + { + X509 *cert = NULL; + EVP_PKEY *key = NULL; + const unsigned char *ffs; + ffs = cred->local.cert; + d2i_X509(&cert, &ffs, cred->local.certsize); + SSL_CTX_use_certificate(n->ctx, cert); + + ffs = cred->local.key; + d2i_PrivateKey(EVP_PKEY_RSA, &key, &ffs, cred->local.keysize); + SSL_CTX_use_PrivateKey(n->ctx, key); + } + else if (isserver) { if (*pdtls_psk_user->string) { @@ -821,7 +863,7 @@ qboolean OSSL_CheckConnection(void *cbctx, void *peeraddr, size_t peeraddrsize, if (!pending) { - pending = OSSL_CreateContext("localhost", cbctx, push, true); + pending = OSSL_CreateContext(NULL, cbctx, push, true); SSL_CTX_set_cookie_generate_cb(pending->ctx, OSSL_GenCookie); SSL_CTX_set_cookie_verify_cb(pending->ctx, OSSL_VerifyCookie); @@ -929,6 +971,50 @@ static neterr_t OSSL_Timeouts(void *ctx) return OSSL_Received(ctx, NULL); } +qboolean OSSL_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred) +{ + EVP_PKEY*pkey = EVP_PKEY_new(); + RSA *rsa = RSA_new(); + BIGNUM *pkexponent = BN_new(); + qbyte *ffs; + //The pseudo-random number generator must be seeded prior to calling RSA_generate_key_ex(). + BN_set_word(pkexponent, RSA_F4); + RSA_generate_key_ex(rsa, 2048, pkexponent, NULL); + BN_free(pkexponent); + + EVP_PKEY_assign_RSA(pkey, rsa); + + cred->keysize = i2d_PrivateKey(pkey, NULL); + cred->key = ffs = plugfuncs->Malloc(cred->keysize); + cred->keysize = i2d_PrivateKey(pkey, &ffs); + + { + X509 *x509 = X509_new(); + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 365*24*60*60); //lots of validity + X509_set_pubkey(x509, pkey); + + { + X509_NAME *name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (subject?subject:"localhost"), -1, -1, 0); + X509_set_issuer_name(x509, name); + } + + X509_sign(x509, pkey, EVP_sha1()); + + cred->certsize = i2d_X509(x509, NULL); + cred->cert = ffs = plugfuncs->Malloc(cred->certsize); + cred->certsize = i2d_X509(x509, &ffs); + + X509_free(x509); + } + + EVP_PKEY_free(pkey); //also frees the rsa pointer. + + return true; +} + static dtlsfuncs_t ossl_dtlsfuncs = { OSSL_CreateContext, @@ -936,7 +1022,9 @@ static dtlsfuncs_t ossl_dtlsfuncs = OSSL_DestroyContext, OSSL_Transmit, OSSL_Received, - OSSL_Timeouts + OSSL_Timeouts, + NULL, + OSSL_GenTempCertificate, }; static const dtlsfuncs_t *OSSL_InitClient(void) {