$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit bd42696cbf942aef538fe9056c70221670c95a83
Author: Alicia <...>
Date: Tue Jun 13 23:35:47 2017 +0200
Added lists of verified and quarantined addresses to prevent abuse of the webpeer service for DDoS attempts. The verified-list also doubles as a list of bootstrap peers.
diff --git a/Makefile b/Makefile
index 7d4533f..1c1b200 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ JSCFLAGS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --cflags
all: webpeer libsocial.js
-webpeer: daemon.o webpeer.o
+webpeer: daemon.o webpeer.o addrlist.o
$(CC) $^ $(LIBS) -o $@
toolchain/usr/bin/emcc: emscripten-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-clang-$(EMSCRIPTVERSION).tar.gz
diff --git a/addrlist.c b/addrlist.c
new file mode 100644
index 0000000..bd0d5d7
--- /dev/null
+++ b/addrlist.c
@@ -0,0 +1,154 @@
+/*
+ webpeer, a websocket-udpstream proxy for socialnetwork-web
+ Copyright (C) 2017 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <unistd.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include "addrlist.h"
+
+struct addrlist verified={0,0,0};
+static struct addrlist quarantine={0,0,0};
+
+static int getlock(const char* path)
+{
+ int f;
+ while((f=open(path, O_CREAT|O_EXCL|O_WRONLY, 0600))<0)
+ {
+ usleep(100);
+ }
+ return f;
+}
+
+static void list_update(struct addrlist* list, const char* file)
+{
+ list->updated=time(0);
+ struct stat st;
+ if(stat(file, &st)){return;}
+ int f=open(file, O_RDONLY);
+ if(f<0){return;}
+ free(list->addr);
+ list->count=st.st_size/sizeof(struct sockaddr_storage);
+ list->addr=malloc(list->count*sizeof(struct sockaddr_storage));
+ read(f, list->addr, list->count*sizeof(struct sockaddr_storage));
+ close(f);
+}
+
+// Check if addresses match, with or without port numbers
+static char addrcmp(struct sockaddr_storage* a, struct sockaddr_storage* b, char port)
+{
+ if(a->ss_family!=b->ss_family){return 0;}
+ #define checkmember(st,x) if(memcmp(&((st*)a)->x, &((st*)b)->x, sizeof(((st*)a)->x))){return 0;}
+ switch(a->ss_family)
+ {
+ case AF_INET: checkmember(struct sockaddr_in, sin_addr); if(port){checkmember(struct sockaddr_in, sin_port);} break;
+ case AF_INET6: checkmember(struct sockaddr_in6, sin6_addr); if(port){checkmember(struct sockaddr_in6, sin6_port);} break;
+ default: return 0; // Unhandled address families
+ }
+ return 1;
+}
+
+static char list_find(struct addrlist* list, struct sockaddr_storage* addr, char port)
+{
+ unsigned int i;
+ for(i=0; i<list->count; ++i)
+ {
+ if(addrcmp(&list->addr[i], addr, port)){return 1;}
+ }
+ return 0;
+}
+
+static void list_add(struct addrlist* list, struct sockaddr_storage* addr, socklen_t addrlen)
+{
+ ++list->count;
+ list->addr=realloc(list->addr, list->count*sizeof(struct sockaddr_storage));
+ memset(&list->addr[list->count-1], 0, sizeof(struct sockaddr_storage));
+ memcpy(&list->addr[list->count-1], addr, addrlen);
+}
+
+#define list_save(list,f) write(f, (list)->addr, (list)->count*sizeof(struct sockaddr_storage));
+
+static char checkaddr(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+ if(addrlen<=sizeof(sa_family_t)){return 0;}
+ if(addrlen>sizeof(struct sockaddr_storage)){return 0;} // Too big
+ switch(addr->ss_family)
+ {
+ case AF_INET: return addrlen>=sizeof(struct sockaddr_in);
+ case AF_INET6: return addrlen>=sizeof(struct sockaddr_in6);
+ }
+ return 0; // Unhandled address families
+}
+
+// Check if we can connect to this address given the rules that only one connection attempt at a time may be done to a host, with the exception of already verified host/port combinations
+char addrlist_check(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+ // Check that addrlen makes sense (don't accept 0-byte addresses and try to access non-0-byte data within)
+ if(!checkaddr(addr, addrlen)){return 0;}
+ // Check if it's already verified (keep in-memory cache and update based on timestamp?)
+ if(list_find(&verified, addr, 1)){return 1;}
+ time_t now=time(0);
+ if(verified.updated+5<now) // Update and check again
+ {
+ list_update(&verified, "verified.addr");
+ if(list_find(&verified, addr, 1)){return 1;}
+ }
+ // Check if it's quarantined. if it isn't, obtain a lock, update and check again
+ if(list_find(&quarantine, addr, 0)){return 0;}
+ int f=getlock("quarantine.addr.lock");
+ list_update(&quarantine, "quarantine.addr");
+ if(list_find(&quarantine, addr, 0)){close(f); unlink("quarantine.addr.lock"); return 0;}
+ // Not verified and not quarantined, quarantine it and allow this one connection attempt for verification
+ list_add(&quarantine, addr, addrlen);
+ list_save(&quarantine, f);
+ close(f);
+ rename("quarantine.addr.lock", "quarantine.addr");
+ return 1;
+}
+
+// Remove from quarantine and add to verified
+void addrlist_verify(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+ if(!checkaddr(addr, addrlen)){return;} // Invalid address
+ if(!list_find(&quarantine, addr, 1)){return;} // Wasn't quarantined
+ int f=getlock("quarantine.addr.lock");
+ list_update(&quarantine, "quarantine.addr"); // Update so we don't accidentally remove a recently added host
+ unsigned int i;
+ for(i=0; i<quarantine.count; ++i)
+ {
+ if(addrcmp(&quarantine.addr[i], addr, 1)){break;}
+ }
+ if(i==quarantine.count){close(f); unlink("quarantine.addr.lock"); return;} // Wasn't quarantined after all (this shouldn't happen, or maybe it should? remote peer making contact with another session first)
+ // Remove from quarantine
+ --quarantine.count;
+ memmove(&quarantine.addr[i], &quarantine.addr[i+1], (quarantine.count-i)*sizeof(struct sockaddr_storage));
+ list_save(&quarantine, f);
+ close(f);
+ rename("quarantine.addr.lock", "quarantine.addr");
+ // Add to verified
+ f=getlock("verified.addr.lock");
+ list_update(&verified, "verified.addr"); // Update so we don't accidentally remove a recently added host
+ list_add(&verified, addr, addrlen);
+ list_save(&verified, f);
+ close(f);
+ rename("verified.addr.lock", "verified.addr");
+}
diff --git a/addrlist.h b/addrlist.h
new file mode 100644
index 0000000..ac3da67
--- /dev/null
+++ b/addrlist.h
@@ -0,0 +1,28 @@
+/*
+ webpeer, a websocket-udpstream proxy for socialnetwork-web
+ Copyright (C) 2017 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <sys/socket.h>
+
+struct addrlist
+{
+ struct sockaddr_storage* addr;
+ unsigned int count;
+ time_t updated;
+};
+extern struct addrlist verified;
+
+extern char addrlist_check(struct sockaddr_storage* addr, socklen_t addrlen);
+extern void addrlist_verify(struct sockaddr_storage* addr, socklen_t addrlen);
diff --git a/libsocialjs.js b/libsocialjs.js
index f30807e..0bb0e8a 100644
--- a/libsocialjs.js
+++ b/libsocialjs.js
@@ -33,12 +33,17 @@ function handlenet(data)
var f=new FileReader();
f.onload=function()
{
- data=new Int8Array(f.result);
+ data=new Uint8Array(f.result);
if(firstpacket) // Bootstrap
{
firstpacket=false;
- // TODO: Read lengths and pass subarrays until we reach the end of the array. length=data[pos]*256+data[pos+1]
- peer_new_unique(-1, data, data.length);
+ // Read sockaddr lengths and pass subarrays until we reach the end of the array
+ while(data.length>2)
+ {
+ var len=data[0]*256+data[1];
+ peer_new_unique(-1, data.subarray(2, 2+len), len);
+ data=data.subarray(2+len);
+ }
return;
}
if(websockproxy_to)
diff --git a/webpeer.c b/webpeer.c
index 6ccad71..73a129a 100644
--- a/webpeer.c
+++ b/webpeer.c
@@ -20,7 +20,8 @@
#include <netdb.h>
#include <netinet/in.h>
#include <libsocial/udpstream.h>
-#include "libwebsocket/websock.h"
+#include <libwebsocket/websock.h>
+#include "addrlist.h"
const char* verifyproto(const char* path, const char* host, char* protocol, const char* origin)
{
@@ -35,14 +36,41 @@ void webpeer(int sock)
websock_conn* conn=websock_new(sock, 1, "cert.pem", "priv.pem");
if(!websock_handshake_server(conn, verifyproto, 0)){printf("Handshake failed\n"); return;}
int udpsock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
-// TODO: First send a list of bootstrap peers, should this be binary too?
-// websock_write(conn, "127.0.0.1:4000", 14, WEBSOCK_TEXT);
- struct sockaddr_in bootstrapaddr={
- .sin_family=AF_INET,
- .sin_addr.s_addr=0x100007f,
- .sin_port=htons(4000)
- };
- websock_write(conn, &bootstrapaddr, sizeof(bootstrapaddr), WEBSOCK_BINARY);
+ // First send a list of bootstrap peers
+ if(!verified.count) // No verified peers yet, suggest localhost:4000 as a starting point
+ {
+ struct sockaddr_in bootstrapaddr={
+ .sin_family=AF_INET,
+ .sin_addr.s_addr=htonl(0x7f000001),
+ .sin_port=htons(4000)
+ };
+ uint16_t len=htons(sizeof(bootstrapaddr));
+ char buf[sizeof(len)+sizeof(bootstrapaddr)];
+ memcpy(buf, &len, sizeof(len));
+ memcpy(buf+sizeof(len), &bootstrapaddr, sizeof(bootstrapaddr));
+ websock_write(conn, buf, sizeof(len)+sizeof(bootstrapaddr), WEBSOCK_BINARY);
+ }else{
+ // Construct a single message containing all peers
+ unsigned int size=verified.count*(sizeof(uint16_t)+sizeof(struct sockaddr_storage));
+ char buf[size];
+ void* ptr=buf;
+ unsigned int i;
+ for(i=0; i<verified.count; ++i)
+ {
+ uint16_t len=0;
+ switch(verified.addr[i].ss_family)
+ {
+ case AF_INET: len=htons(sizeof(struct sockaddr_in)); break;
+ case AF_INET6: len=htons(sizeof(struct sockaddr_in6)); break;
+ }
+ if(!len){continue;}
+ memcpy(ptr, &len, sizeof(len));
+ ptr+=sizeof(len);
+ len=ntohs(len);
+ memcpy(ptr, &verified.addr[i], len);
+ ptr+=len;
+ }
+ }
// Listen to client and udpstream socket and pass messages back and forth (client handles the TLS within the udpstream)
struct pollfd pfd[]={{.fd=sock, .events=POLLIN, .revents=0}, {.fd=udpsock, .events=POLLIN, .revents=0}};
while(1)
@@ -62,10 +90,11 @@ printf("Handling websocket input...\n");
if(!websock_readhead(conn, &head)){break;}
char buf[head.length];
while(!websock_readcontent(conn, buf, &head));
- struct udpstream* stream=udpstream_find((struct sockaddr*)addr, addrlen);
+ struct udpstream* stream=udpstream_find((struct sockaddr_storage*)addr, addrlen);
if(!stream) // TODO: Check against blocklist (don't let people use this service to flood others over UDP), also add a brief block for the entire host until we know this connection is ok
{
- stream=udpstream_new(udpsock, (struct sockaddr*)addr, addrlen);
+ if(!addrlist_check((struct sockaddr_storage*)addr, addrlen)){continue;}
+ stream=udpstream_new(udpsock, (struct sockaddr_storage*)addr, addrlen);
}
// printf("Writing %u bytes\n", head.length);
udpstream_write(stream, buf, head.length);
@@ -81,9 +110,10 @@ printf("Handling udpsocket input...\n");
char buf[2048];
ssize_t len=udpstream_read(stream, buf, 2048);
if(len<1){udpstream_close(stream); continue;}
- struct sockaddr addr;
+ struct sockaddr_storage addr;
socklen_t addrlen=sizeof(addr);
udpstream_getaddr(stream, &addr, &addrlen);
+ addrlist_verify(&addr, addrlen);
websock_write(conn, &addr, addrlen, WEBSOCK_BINARY);
websock_write(conn, buf, len, WEBSOCK_BINARY);
}