#include <arpa/inet.h> #include <linux/if.h> #include <netinet/in.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <unistd.h> #include <atomic> #include <mutex> #include <thread> #include "utils/RWLock.h" // Defined only in ifc_utils.c, in the kernel, and in the NDK. #ifndef SIOCKILLADDR #define SIOCKILLADDR 0x8939 #endif #ifndef TCP_LINGER2 #define TCP_LINGER2 8 #endif #define KILL_INTERVAL_MS 10 #define CONNECT_THREADS 1 #define PERROR_EXIT(msg) { do { perror((msg)); exit(1); } while (0); }; // Ensures that sockets don't stay in TIME_WAIT state. void setSoLinger(int s) { const struct linger l = { 0, // off 0, // 0 seconds }; if (setsockopt(s, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) { PERROR_EXIT("SO_LINGER"); } const int nolinger = -1; if (setsockopt(s, SOL_TCP, TCP_LINGER2, &nolinger, sizeof(nolinger)) == -1) { PERROR_EXIT("TCP_LINGER2"); } } // Binds to a random port on a random loopback address. We don't just use 127.0.0.1 because we don't // want this test to kill unrelated connections on loopback. int bindRandomAddr() { sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = 0; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); while (sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { arc4random_buf( ((uint8_t *) &sin.sin_addr.s_addr) + 1, sizeof(sin.sin_addr.s_addr) - 1); } int listensock; if ((listensock = socket(AF_INET, SOCK_STREAM, 0)) == -1) PERROR_EXIT("listensock"); if (bind(listensock, (sockaddr *) &sin, sizeof(sin)) == -1) PERROR_EXIT("bind"); if (listen(listensock, 10) == -1) PERROR_EXIT("listen"); return listensock; } // Thread that calls SIOCKILLADDR in a loop. void killSockets(sockaddr_in listenaddr, int intervalMs, android::RWLock *lock) { ifreq ifr; memset(&ifr, 0, sizeof(ifr)); listenaddr.sin_port = 0; strncpy(ifr.ifr_name, "lo", strlen("lo")); memcpy(&ifr.ifr_addr, &listenaddr, sizeof(listenaddr)); int ioctlsock = socket(AF_INET, SOCK_DGRAM, 0); if (ioctlsock == -1) PERROR_EXIT("ioctlsock"); while(true) { lock->writeLock(); if (ioctl(ioctlsock, SIOCKILLADDR, &ifr) != 0) { PERROR_EXIT("SIOCKILLADDR failed, did you run 32-bit userspace on a 64-bit kernel?"); } lock->unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); } } // Thread that calls connect() in a loop. void connectLoop(sockaddr_in listenaddr, int listensock, android::RWLock *lock, std::atomic<unsigned int> *attempts) { while(true) { int s = socket(AF_INET, SOCK_STREAM, 0); setSoLinger(s); // Don't call SIOCKILLADDR while connect() is running, or we'll end up with lots of // connections in state FIN_WAITx or TIME_WAIT, which will then slow down future // due to SYN retransmits. lock->readLock(); if (connect(s, (sockaddr *) &listenaddr, sizeof(listenaddr)) == -1) PERROR_EXIT("connect"); lock->unlock(); send(s, "foo", 3, 0); int acceptedsock = accept(listensock, NULL, 0); if (close(acceptedsock) == -1) PERROR_EXIT("close"); if (close(s) == -1) PERROR_EXIT("close"); attempts->fetch_add(1); } } // Thread that prints progress every second. void progressThread(std::atomic<unsigned int> *attempts) { uint32_t elapsed = 0; uint32_t total, previous = 0; while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); elapsed++; total = attempts->load(); printf("%ds: %u cps, total %u\n", elapsed, total-previous, total); fflush(stdout); previous = total; } } int main() { int listensock = bindRandomAddr(); struct sockaddr_in sin; socklen_t len = sizeof(sin); if (getsockname(listensock, (sockaddr *) &sin, &len) == -1) PERROR_EXIT("getsockname"); printf("Using address %s:%d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); android::RWLock lock; std::atomic<unsigned int> attempts; attempts.store(0); std::thread t0(killSockets, sin, KILL_INTERVAL_MS, &lock); std::thread *connectThreads[CONNECT_THREADS]; for (size_t i = 0; i < CONNECT_THREADS; i++) { connectThreads[i] = new std::thread(connectLoop, sin, listensock, &lock, &attempts); } std::thread t1(progressThread, &attempts); t1.join(); return 0; }