Add TCP proxying to emsocket
This commit is contained in:
parent
1e2e7d3b12
commit
1500883fa2
@ -1,6 +1,6 @@
|
|||||||
add_library(emsocket emsocket.cpp)
|
add_library(emsocket emsocket.cpp emsocketctl.cpp ProxyLink.cpp VirtualSocket.cpp)
|
||||||
|
|
||||||
set_target_properties(emsocket PROPERTIES PUBLIC_HEADER "emsocket.h")
|
set_target_properties(emsocket PROPERTIES PUBLIC_HEADER "emsocket.h;emsocketctl.h")
|
||||||
INSTALL(TARGETS emsocket
|
INSTALL(TARGETS emsocket
|
||||||
LIBRARY DESTINATION lib
|
LIBRARY DESTINATION lib
|
||||||
PUBLIC_HEADER DESTINATION include
|
PUBLIC_HEADER DESTINATION include
|
||||||
|
38
src/emsocket/Link.h
Normal file
38
src/emsocket/Link.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
class Link {
|
||||||
|
public:
|
||||||
|
using Receiver = std::function<void(const void *, size_t)>;
|
||||||
|
|
||||||
|
virtual ~Link() { }
|
||||||
|
virtual void send(const void *data, size_t len) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
239
src/emsocket/ProxyLink.cpp
Normal file
239
src/emsocket/ProxyLink.cpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define EMSOCKET_INTERNAL
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <emscripten/websocket.h>
|
||||||
|
#include "VirtualSocket.h"
|
||||||
|
#include "ProxyLink.h"
|
||||||
|
#include "emsocketctl.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
// Callbacks to re-enter C/C++ from javascript
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onopen(void *thisPtr);
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onerror(void *thisPtr);
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onclose(void *thisPtr);
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onmessage(void *thisPtr, const void *buf, size_t n);
|
||||||
|
}
|
||||||
|
|
||||||
|
EM_JS(int, setup_proxylink_websocket, (const char* url, void *thisPtr), {
|
||||||
|
if (!self.hasOwnProperty('mywebsockets')) {
|
||||||
|
self.mywebsockets = [null];
|
||||||
|
self.w_proxylink_onopen = Module.cwrap('proxylink_onopen', null, ['number']);
|
||||||
|
self.w_proxylink_onerror = Module.cwrap('proxylink_onerror', null, ['number']);
|
||||||
|
self.w_proxylink_onclose = Module.cwrap('proxylink_onclose', null, ['number']);
|
||||||
|
self.w_proxylink_onmessage = Module.cwrap('proxylink_onmessage', null, ['number', 'number', 'number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = new WebSocket(UTF8ToString(url));
|
||||||
|
const index = mywebsockets.length;
|
||||||
|
mywebsockets.push(ws);
|
||||||
|
ws.binaryType = "arraybuffer";
|
||||||
|
ws.onopen = (e) => {
|
||||||
|
w_proxylink_onopen(thisPtr);
|
||||||
|
};
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
w_proxylink_onerror(thisPtr);
|
||||||
|
};
|
||||||
|
ws.onclose = (e) => {
|
||||||
|
w_proxylink_onclose(thisPtr);
|
||||||
|
};
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
var len = e.data.byteLength;
|
||||||
|
var buf = _malloc(len);
|
||||||
|
HEAPU8.set(new Uint8Array(e.data), buf);
|
||||||
|
w_proxylink_onmessage(thisPtr, buf, len);
|
||||||
|
_free(buf);
|
||||||
|
};
|
||||||
|
|
||||||
|
return index;
|
||||||
|
});
|
||||||
|
|
||||||
|
EM_JS(void, send_proxylink_websocket, (int index, const void *data, int len), {
|
||||||
|
const ws = mywebsockets[index];
|
||||||
|
if (ws) {
|
||||||
|
ws.send(new Uint8Array(HEAPU8.subarray(data, data + len)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EM_JS(void, delete_proxylink_websocket, (int index), {
|
||||||
|
const ws = mywebsockets[index];
|
||||||
|
if (ws) {
|
||||||
|
delete mywebsockets[index];
|
||||||
|
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = null;
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static void* memdup(const void *data, size_t len) {
|
||||||
|
void* buf = malloc(len);
|
||||||
|
memcpy(buf, data, len);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
class ProxyLink : public Link {
|
||||||
|
public:
|
||||||
|
ProxyLink() = delete;
|
||||||
|
ProxyLink(const ProxyLink &) = delete;
|
||||||
|
ProxyLink& operator=(const ProxyLink &) = delete;
|
||||||
|
|
||||||
|
ProxyLink(VirtualSocket *vs_, const std::string &proxyUrl, const SocketAddr &addr_, bool udp_)
|
||||||
|
: wsIndex(-1),
|
||||||
|
vs(vs_),
|
||||||
|
addr(addr_),
|
||||||
|
udp(udp_),
|
||||||
|
sentProxyRequest(false),
|
||||||
|
receivedProxyAuth(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
//std::cerr << "Initialized proxy websocket" << std::endl;
|
||||||
|
emsocket_run_on_io_thread(true, [this, proxyUrl]() {
|
||||||
|
wsIndex = setup_proxylink_websocket(proxyUrl.c_str(), this);
|
||||||
|
});
|
||||||
|
assert(wsIndex > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ProxyLink() {
|
||||||
|
emsocket_run_on_io_thread(true, [this]() {
|
||||||
|
hangup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from external thread
|
||||||
|
virtual void send(const void *data, size_t len) {
|
||||||
|
// This can be called from another thread. Move it to the I/O thread.
|
||||||
|
int wsIndex_ = wsIndex;
|
||||||
|
void *dataCopy = memdup(data, len);
|
||||||
|
emsocket_run_on_io_thread(false, [wsIndex_, dataCopy, len]() {
|
||||||
|
send_proxylink_websocket(wsIndex_, dataCopy, len);
|
||||||
|
free(dataCopy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Called from I/O thread
|
||||||
|
void onopen() {
|
||||||
|
// Send a proxy request
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "PROXY IPV4 %s %s %u", (udp ? "UDP" : "TCP"), addr.getIP().c_str(), addr.getPort());
|
||||||
|
send_proxylink_websocket(wsIndex, buf, strlen(buf));
|
||||||
|
//std::cerr << "Sent websocket PROXY handshake" << std::endl;
|
||||||
|
sentProxyRequest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from I/O thread
|
||||||
|
void onerror() {
|
||||||
|
//std::cerr << "ProxyLink got websocket error" << std::endl;
|
||||||
|
hangup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from I/O thread
|
||||||
|
void onclose() {
|
||||||
|
//std::cerr << "ProxyLink got websocket close" << std::endl;
|
||||||
|
hangup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from I/O thread
|
||||||
|
void onmessage(const void *buf, int n) {
|
||||||
|
if (!sentProxyRequest) {
|
||||||
|
//std::cerr << "ProxyLink got invalid message before proxy request" << std::endl;
|
||||||
|
hangup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!receivedProxyAuth) {
|
||||||
|
// Check for proxy auth
|
||||||
|
if (n > 16) {
|
||||||
|
//std::cerr << "ProxyLink unexpected auth message length (" << n << ")" << std::endl;
|
||||||
|
hangup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string response((const char*)buf, n);
|
||||||
|
if (response == "PROXY OK") {
|
||||||
|
receivedProxyAuth = true;
|
||||||
|
vs->linkConnected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//std::cerr << "ProxyLink received bad auth: '" << response << "' of length " << n << std::endl;
|
||||||
|
hangup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Regular message
|
||||||
|
vs->linkReceived(addr, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from I/O thread
|
||||||
|
void hangup() {
|
||||||
|
delete_proxylink_websocket(wsIndex);
|
||||||
|
vs->linkShutdown();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
// vs is protected by vs_mutex
|
||||||
|
VirtualSocket *vs;
|
||||||
|
SocketAddr addr;
|
||||||
|
bool udp;
|
||||||
|
int wsIndex;
|
||||||
|
//EMSCRIPTEN_WEBSOCKET_T ws;
|
||||||
|
bool sentProxyRequest;
|
||||||
|
bool receivedProxyAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
Link* make_proxy_link(VirtualSocket* vs, const std::string &proxyUrl, const SocketAddr &addr, bool udp) {
|
||||||
|
return new ProxyLink(vs, proxyUrl, addr, udp);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
using namespace emsocket;
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onopen(void *thisPtr) {
|
||||||
|
return reinterpret_cast<ProxyLink*>(thisPtr)->onopen();
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onerror(void *thisPtr) {
|
||||||
|
return reinterpret_cast<ProxyLink*>(thisPtr)->onerror();
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onclose(void *thisPtr) {
|
||||||
|
return reinterpret_cast<ProxyLink*>(thisPtr)->onclose();
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE
|
||||||
|
void proxylink_onmessage(void *thisPtr, const void *buf, size_t n) {
|
||||||
|
return reinterpret_cast<ProxyLink*>(thisPtr)->onmessage(buf, n);
|
||||||
|
}
|
35
src/emsocket/ProxyLink.h
Normal file
35
src/emsocket/ProxyLink.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SocketAddr.h"
|
||||||
|
#include "Link.h"
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
class VirtualSocket;
|
||||||
|
|
||||||
|
Link* make_proxy_link(VirtualSocket* vs, const std::string &proxyUrl, const SocketAddr &addr, bool udp);
|
||||||
|
|
||||||
|
} // namespace
|
109
src/emsocket/SocketAddr.h
Normal file
109
src/emsocket/SocketAddr.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
class SocketAddr {
|
||||||
|
public:
|
||||||
|
SocketAddr() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddr(const std::string &ip, uint16_t port) {
|
||||||
|
setIP(ip);
|
||||||
|
setPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddr(const struct sockaddr *addr, socklen_t addrlen) {
|
||||||
|
if (addr->sa_family == AF_INET && addrlen >= sizeof(sin)) {
|
||||||
|
memcpy(&sin, addr, sizeof(sin));
|
||||||
|
} else {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getIP() const {
|
||||||
|
char buf[INET_ADDRSTRLEN];
|
||||||
|
inet_ntop(AF_INET, &(sin.sin_addr), buf, sizeof(buf));
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setIP(std::string ip) {
|
||||||
|
if (1 == inet_pton(AF_INET, ip.c_str(), &(sin.sin_addr))) {
|
||||||
|
return true; // success
|
||||||
|
}
|
||||||
|
return false; // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getPort() const {
|
||||||
|
return ntohs(sin.sin_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPort(uint16_t port) {
|
||||||
|
sin.sin_port = htons(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddr(const SocketAddr &o) {
|
||||||
|
memcpy(&sin, &o.sin, sizeof(sin));
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddr& operator=(const SocketAddr &o) {
|
||||||
|
memcpy(&sin, &o.sin, sizeof(sin));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
memset(&sin, 0, sizeof(sin));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLocalHost() const {
|
||||||
|
return (sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK));
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyTo(struct sockaddr *addr, socklen_t *addr_len) const {
|
||||||
|
if (!addr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(addr, &sin, std::min((size_t)*addr_len, sizeof(sin)));
|
||||||
|
*addr_len = sizeof(sin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
sockaddr_in sin;
|
||||||
|
bool valid = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline std::ostream& operator<<(std::ostream &os, const SocketAddr &addr) {
|
||||||
|
os << addr.getIP() << ":" << addr.getPort();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
268
src/emsocket/VirtualSocket.cpp
Normal file
268
src/emsocket/VirtualSocket.cpp
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#define EMSOCKET_INTERNAL
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <random>
|
||||||
|
#include <cassert>
|
||||||
|
#include <emscripten/threading.h>
|
||||||
|
#include "VirtualSocket.h"
|
||||||
|
#include "ProxyLink.h"
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
// This mutex protects:
|
||||||
|
// 1) Allocating a new socket
|
||||||
|
// 2) Adding/removing a port binding
|
||||||
|
// 3) Adding/removing packets from a receive buffer
|
||||||
|
//
|
||||||
|
// The VirtualSocket's themselves are not protected by a mutex.
|
||||||
|
// POSIX sockets are not thread-safe, so it is assumed that
|
||||||
|
// every fd will be used only by a single thread at a time.
|
||||||
|
//
|
||||||
|
static std::mutex vs_mutex;
|
||||||
|
static std::condition_variable vs_event;
|
||||||
|
VirtualSocket VirtualSocket::sockets[EMSOCKET_NSOCKETS];
|
||||||
|
static std::unordered_map<uint16_t, VirtualSocket*> vs_port_map;
|
||||||
|
|
||||||
|
#define VSLOCK() const std::lock_guard<std::mutex> lock(vs_mutex)
|
||||||
|
|
||||||
|
static std::random_device rd;
|
||||||
|
|
||||||
|
void VirtualSocket::open(int fd, bool udp) {
|
||||||
|
assert(fd_ == -1);
|
||||||
|
reset();
|
||||||
|
fd_ = fd;
|
||||||
|
is_udp = udp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::reset() {
|
||||||
|
fd_ = -1;
|
||||||
|
is_connected = false;
|
||||||
|
is_shutdown = false;
|
||||||
|
is_blocking = true;
|
||||||
|
bindAddr.clear();
|
||||||
|
remoteAddr.clear();
|
||||||
|
recvbuf.clear();
|
||||||
|
link = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VirtualSocket::close() {
|
||||||
|
VSLOCK();
|
||||||
|
uint16_t port = bindAddr.getPort();
|
||||||
|
if (link) {
|
||||||
|
delete link;
|
||||||
|
link = nullptr;
|
||||||
|
}
|
||||||
|
if (port != 0) {
|
||||||
|
vs_port_map.erase(port);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::linkConnected() {
|
||||||
|
is_connected = true;
|
||||||
|
vs_event.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::linkShutdown() {
|
||||||
|
is_shutdown = true;
|
||||||
|
vs_event.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::linkReceived(const SocketAddr& addr, const void *buf, size_t n) {
|
||||||
|
{
|
||||||
|
VSLOCK();
|
||||||
|
recvbuf.emplace_back(addr, buf, n);
|
||||||
|
}
|
||||||
|
vs_event.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualSocket* VirtualSocket::allocate(bool udp) {
|
||||||
|
VSLOCK();
|
||||||
|
for (int idx = 0; idx < EMSOCKET_NSOCKETS; idx++) {
|
||||||
|
if (sockets[idx].fd_ == -1) {
|
||||||
|
sockets[idx].open(EMSOCKET_BASE_FD + idx, udp);
|
||||||
|
return &sockets[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualSocket* VirtualSocket::get(int fd) {
|
||||||
|
if (fd < EMSOCKET_BASE_FD || fd >= EMSOCKET_MAX_FD) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
int idx = fd - EMSOCKET_BASE_FD;
|
||||||
|
VirtualSocket* vs = &sockets[idx];
|
||||||
|
assert(vs && vs->fd_ == fd);
|
||||||
|
return vs;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualSocket::bind(const SocketAddr& addr) {
|
||||||
|
assert(bindAddr.getPort() == 0);
|
||||||
|
VSLOCK();
|
||||||
|
// TODO: Separate out TCP and UDP ports?
|
||||||
|
uint16_t port = addr.getPort();
|
||||||
|
if (port == 0) {
|
||||||
|
std::default_random_engine engine(rd());
|
||||||
|
std::uniform_int_distribution<int> randport(4096, 16384);
|
||||||
|
do {
|
||||||
|
port = randport(engine);
|
||||||
|
} while (vs_port_map.count(port));
|
||||||
|
} else if (vs_port_map.count(port)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bindAddr = addr;
|
||||||
|
bindAddr.setPort(port);
|
||||||
|
vs_port_map[port] = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualSocket::canBlock() const {
|
||||||
|
// Out of necessity, sockets in the main browser thread will
|
||||||
|
// always act like non-blocking sockets.
|
||||||
|
return is_blocking; // && !emscripten_is_main_browser_thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::waitForData() {
|
||||||
|
VirtualSocket::waitFor([&]() {
|
||||||
|
return !recvbuf.empty();
|
||||||
|
}, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::waitForConnect() {
|
||||||
|
VirtualSocket::waitFor([&]() {
|
||||||
|
return isConnected() || isShutdown();
|
||||||
|
}, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualSocket::runWithLock(const std::function<bool(void)>& predicate) {
|
||||||
|
std::unique_lock<std::mutex> lock(vs_mutex);
|
||||||
|
return predicate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::waitFor(const std::function<bool(void)>& predicate, int64_t timeout) {
|
||||||
|
std::unique_lock<std::mutex> lock(vs_mutex);
|
||||||
|
if (timeout < 0) {
|
||||||
|
vs_event.wait(lock, predicate);
|
||||||
|
} else {
|
||||||
|
vs_event.wait_for(lock, std::chrono::milliseconds(timeout), predicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualSocket::startLocalConnect(uint16_t port) {
|
||||||
|
std::cerr << "emsocket local TCP not yet supported" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualSocket::startProxyConnect(const std::string &proxyUrl, const SocketAddr &dest) {
|
||||||
|
assert(!is_udp);
|
||||||
|
assert(!link);
|
||||||
|
assert(!is_connected);
|
||||||
|
assert(!is_shutdown);
|
||||||
|
assert(proxyUrl.size() > 0);
|
||||||
|
if (!isBound()) {
|
||||||
|
bind(SocketAddr()); // bind to random port
|
||||||
|
}
|
||||||
|
remoteAddr = dest;
|
||||||
|
link = make_proxy_link(this, proxyUrl, dest, is_udp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stream read/write. Always non-blocking.
|
||||||
|
ssize_t VirtualSocket::read(void *buf, size_t n) {
|
||||||
|
assert(!is_udp);
|
||||||
|
VSLOCK();
|
||||||
|
char *cbuf = (char*)buf;
|
||||||
|
size_t pos = 0;
|
||||||
|
while (!recvbuf.empty() && pos < n) {
|
||||||
|
Packet pkt = std::move(recvbuf.front());
|
||||||
|
recvbuf.pop_front();
|
||||||
|
size_t take = std::min(n - pos, pkt.data.size());
|
||||||
|
memcpy(&cbuf[pos], &pkt.data[0], take);
|
||||||
|
pos += take;
|
||||||
|
if (take < pkt.data.size()) {
|
||||||
|
recvbuf.emplace_front(pkt.from, &pkt.data[take], pkt.data.size() - take);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::write(const void *buf, size_t n) {
|
||||||
|
assert(!is_udp);
|
||||||
|
assert(is_connected);
|
||||||
|
if (link) {
|
||||||
|
link->send(buf, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datagram read/write. Always non-blocking
|
||||||
|
ssize_t VirtualSocket::recvfrom(void *buf, size_t n, SocketAddr *from) {
|
||||||
|
assert(is_udp);
|
||||||
|
VSLOCK();
|
||||||
|
char *cbuf = (char*)buf;
|
||||||
|
if (!recvbuf.empty()) {
|
||||||
|
Packet pkt = std::move(recvbuf.front());
|
||||||
|
recvbuf.pop_front();
|
||||||
|
size_t take = std::min(n, pkt.data.size());
|
||||||
|
memcpy(&cbuf[0], &pkt.data[0], take);
|
||||||
|
*from = pkt.from;
|
||||||
|
return take;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualSocket::sendto(const void *buf, size_t n, const SocketAddr& to) {
|
||||||
|
assert(is_udp);
|
||||||
|
assert(isBound());
|
||||||
|
SocketAddr sourceAddr("127.0.0.1", bindAddr.getPort());
|
||||||
|
if (to.isLocalHost()) {
|
||||||
|
bool sent = false;
|
||||||
|
{
|
||||||
|
VSLOCK();
|
||||||
|
uint16_t port = to.getPort();
|
||||||
|
auto it = vs_port_map.find(port);
|
||||||
|
if (it != vs_port_map.end()) {
|
||||||
|
it->second->recvbuf.emplace_back(sourceAddr, buf, n);
|
||||||
|
sent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sent) {
|
||||||
|
vs_event.notify_all();
|
||||||
|
} else {
|
||||||
|
std::cerr << "sendto going nowhere" << std::endl;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Proxying to external UDP not implemented yet
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
118
src/emsocket/VirtualSocket.h
Normal file
118
src/emsocket/VirtualSocket.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include "SocketAddr.h"
|
||||||
|
#include "Link.h"
|
||||||
|
|
||||||
|
// This must match the same defines in emsocket.h
|
||||||
|
#define EMSOCKET_BASE_FD 512
|
||||||
|
#define EMSOCKET_MAX_FD 1024
|
||||||
|
#define EMSOCKET_NSOCKETS (EMSOCKET_MAX_FD - EMSOCKET_BASE_FD)
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
struct Packet {
|
||||||
|
SocketAddr from;
|
||||||
|
std::vector<char> data;
|
||||||
|
|
||||||
|
Packet() = delete;
|
||||||
|
Packet(const Packet &) = delete;
|
||||||
|
Packet& operator=(const Packet &) = delete;
|
||||||
|
Packet(Packet &&p) : from(p.from), data(std::move(p.data)) { }
|
||||||
|
Packet(const SocketAddr &from_, const void *buf, size_t len)
|
||||||
|
: from(from_), data((char*)buf, ((char*)buf) + len) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class VirtualSocket {
|
||||||
|
public:
|
||||||
|
static VirtualSocket* allocate(bool udp);
|
||||||
|
static VirtualSocket* get(int fd);
|
||||||
|
int fd() const { return fd_; }
|
||||||
|
bool isUDP() const { return is_udp; }
|
||||||
|
bool isBlocking() const { return is_blocking; }
|
||||||
|
void setBlocking(bool value) { is_blocking = value; }
|
||||||
|
bool isBound() const { return bindAddr.getPort() != 0; }
|
||||||
|
bool bind(const SocketAddr &addr);
|
||||||
|
const SocketAddr& getBindAddr() { return bindAddr; }
|
||||||
|
const SocketAddr& getRemoteAddr() { return remoteAddr; }
|
||||||
|
|
||||||
|
bool isConnected() const { return is_connected; }
|
||||||
|
bool isShutdown() const { return is_shutdown; }
|
||||||
|
|
||||||
|
// This should only be called holding the lock
|
||||||
|
// (e.g. inside the predicate for waitFor)
|
||||||
|
bool hasData() const { return !recvbuf.empty(); }
|
||||||
|
|
||||||
|
bool canBlock() const;
|
||||||
|
void waitForData();
|
||||||
|
void waitForConnect();
|
||||||
|
static bool runWithLock(const std::function<bool(void)>& predicate);
|
||||||
|
static void waitFor(const std::function<bool(void)>& predicate, int64_t timeout);
|
||||||
|
|
||||||
|
bool startLocalConnect(uint16_t port);
|
||||||
|
bool startProxyConnect(const std::string &proxyUrl, const SocketAddr &dest);
|
||||||
|
|
||||||
|
// Stream read/write. Always non-blocking.
|
||||||
|
ssize_t read(void *buf, size_t n);
|
||||||
|
void write(const void *buf, size_t n);
|
||||||
|
|
||||||
|
// Datagram read/write. Always non-blocking
|
||||||
|
ssize_t recvfrom(void *buf, size_t n, SocketAddr *from);
|
||||||
|
void sendto(const void *buf, size_t n, const SocketAddr& to);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
|
||||||
|
void linkConnected();
|
||||||
|
void linkShutdown();
|
||||||
|
void linkReceived(const SocketAddr &addr, const void *buf, size_t n);
|
||||||
|
|
||||||
|
private:
|
||||||
|
VirtualSocket()
|
||||||
|
: fd_(-1) {
|
||||||
|
}
|
||||||
|
VirtualSocket(const VirtualSocket &) = delete;
|
||||||
|
VirtualSocket& operator=(const VirtualSocket &) = delete;
|
||||||
|
void reset();
|
||||||
|
void open(int fd, bool udp);
|
||||||
|
|
||||||
|
int fd_;
|
||||||
|
bool is_udp;
|
||||||
|
bool is_blocking;
|
||||||
|
std::atomic<bool> is_connected;
|
||||||
|
std::atomic<bool> is_shutdown;
|
||||||
|
SocketAddr bindAddr;
|
||||||
|
SocketAddr remoteAddr;
|
||||||
|
std::list<Packet> recvbuf;
|
||||||
|
Link *link;
|
||||||
|
|
||||||
|
static VirtualSocket sockets[EMSOCKET_NSOCKETS];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
@ -22,6 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define EMSOCKET_INTERNAL
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@ -30,8 +32,6 @@ SOFTWARE.
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <chrono>
|
|
||||||
#include <random>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -44,202 +44,270 @@ SOFTWARE.
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#define EMSOCKET_INTERNAL
|
#include "VirtualSocket.h"
|
||||||
|
|
||||||
#include "emsocket.h"
|
#include "emsocket.h"
|
||||||
|
|
||||||
// Browsers don't allow websocket servers, so simulate servers on `localhost` with a buffer instead.
|
using namespace emsocket;
|
||||||
static bool is_localhost(const struct sockaddr *addr, socklen_t addrlen) {
|
|
||||||
assert(addr->sa_family == AF_INET);
|
namespace emsocket {
|
||||||
if (((sockaddr_in*)addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
|
extern std::string currentProxyUrl;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t get_port(const struct sockaddr *addr, socklen_t addrlen) {
|
#if EMSOCKET_DEBUG
|
||||||
assert(addr->sa_family == AF_INET);
|
|
||||||
return ntohs(((sockaddr_in*)addr)->sin_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sockfd -> receive packet buffer
|
std::mutex dbg_mutex;
|
||||||
struct pkt {
|
#define DBG(x) do { \
|
||||||
uint16_t source_port;
|
const std::lock_guard<std::mutex> lock(dbg_mutex); \
|
||||||
std::vector<char> data;
|
x \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
pkt() = delete;
|
#else
|
||||||
pkt(const pkt &) = delete;
|
|
||||||
pkt& operator=(const pkt &) = delete;
|
|
||||||
|
|
||||||
pkt(uint16_t port, const char *buf, size_t len)
|
#define DBG(x) do { } while (0)
|
||||||
: source_port(port), data(&buf[0], &buf[len]) { }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VirtualSocket {
|
#endif
|
||||||
std::mutex mutex;
|
|
||||||
std::condition_variable cv;
|
|
||||||
bool open;
|
|
||||||
uint16_t sport;
|
|
||||||
std::list<pkt> recvbuf;
|
|
||||||
|
|
||||||
VirtualSocket() {
|
|
||||||
reset(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(bool open_) {
|
|
||||||
open = open_;
|
|
||||||
sport = 0;
|
|
||||||
recvbuf.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Protects the maps and id generation
|
|
||||||
static std::mutex mutex;
|
|
||||||
static std::vector<VirtualSocket*> socket_map;
|
|
||||||
static std::unordered_map<uint16_t, VirtualSocket*> port_map;
|
|
||||||
static unsigned int seed = 0;
|
|
||||||
static std::default_random_engine generator;
|
|
||||||
static std::uniform_int_distribution<uint16_t> randport(4096, 32000);
|
|
||||||
|
|
||||||
// Must be called while holding mutex
|
|
||||||
static uint16_t random_port() {
|
|
||||||
if (seed == 0) {
|
|
||||||
seed = std::chrono::system_clock::now().time_since_epoch().count();
|
|
||||||
generator.seed(seed);
|
|
||||||
}
|
|
||||||
uint16_t port;
|
|
||||||
do {
|
|
||||||
port = randport(generator);
|
|
||||||
} while (port_map.count(port) != 0);
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void maybe_init_socket_map() {
|
|
||||||
if (socket_map.size() == 0) {
|
|
||||||
int count = EMSOCKET_MAX_FD - EMSOCKET_BASE_FD;
|
|
||||||
for (int idx = 0; idx < count; idx++) {
|
|
||||||
socket_map.push_back(new VirtualSocket());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be called holding the mutex
|
|
||||||
static VirtualSocket *getvs(int fd) {
|
|
||||||
assert(fd >= EMSOCKET_BASE_FD && fd < EMSOCKET_MAX_FD);
|
|
||||||
maybe_init_socket_map();
|
|
||||||
int idx = fd - EMSOCKET_BASE_FD;
|
|
||||||
auto vs = socket_map[idx];
|
|
||||||
assert(vs && vs->open);
|
|
||||||
return vs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************************************/
|
/**************************************************************************************************/
|
||||||
|
|
||||||
#define VLOCK() const std::lock_guard<std::mutex> lock(mutex)
|
|
||||||
|
|
||||||
int emsocket_socket(int domain, int type, int protocol) {
|
int emsocket_socket(int domain, int type, int protocol) {
|
||||||
VLOCK();
|
DBG(std::cerr << "emsocket_socket " << domain << "," << type << "," << protocol << std::endl;);
|
||||||
maybe_init_socket_map();
|
if (domain != AF_INET) {
|
||||||
int idx = 0;
|
DBG(std::cerr << "emsocket_socket bad domain: " << domain << std::endl;);
|
||||||
for (; idx < socket_map.size(); idx++) {
|
errno = EINVAL;
|
||||||
if (!socket_map[idx]->open) {
|
return -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (idx == socket_map.size()) {
|
if (type != SOCK_STREAM && type != SOCK_DGRAM) {
|
||||||
|
DBG(std::cerr << "emsocket_socket bad type: " << type << std::endl;);
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (protocol == 0) {
|
||||||
|
if (type == SOCK_STREAM) protocol = IPPROTO_TCP;
|
||||||
|
if (type == SOCK_DGRAM) protocol = IPPROTO_UDP;
|
||||||
|
}
|
||||||
|
if (type == SOCK_DGRAM && protocol != IPPROTO_UDP) {
|
||||||
|
DBG(std::cerr << "emsocket_socket bad dgram protocol: " << protocol << std::endl;);
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (type == SOCK_STREAM && protocol != IPPROTO_TCP) {
|
||||||
|
DBG(std::cerr << "emsocket_socket bad stream protocol: " << protocol << std::endl;);
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bool is_udp = (type == SOCK_DGRAM);
|
||||||
|
auto vs = VirtualSocket::allocate(is_udp);
|
||||||
|
if (!vs) {
|
||||||
errno = EMFILE;
|
errno = EMFILE;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
socket_map[idx]->reset(true);
|
DBG(std::cerr << "emsocket_socket returns fd=" << vs->fd() << ", udp=" << vs->isUDP() << std::endl;);
|
||||||
return EMSOCKET_BASE_FD + idx;
|
return vs->fd();
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_socketpair(int domain, int type, int protocol, int fds[2]);
|
int emsocket_socketpair(int domain, int type, int protocol, int fds[2]);
|
||||||
|
|
||||||
int emsocket_bind(int fd, const struct sockaddr *addr, socklen_t len) {
|
int emsocket_bind(int fd, const struct sockaddr *addr, socklen_t len) {
|
||||||
VLOCK();
|
auto vs = VirtualSocket::get(fd);
|
||||||
auto vs = getvs(fd);
|
if (!vs->bind(SocketAddr(addr, len))) {
|
||||||
assert(vs->sport == 0);
|
DBG(std::cerr << "emsocket_bind failed" << std::endl;);
|
||||||
uint16_t port = get_port(addr, len);
|
|
||||||
if (port == 0) {
|
|
||||||
port = random_port();
|
|
||||||
}
|
|
||||||
if (port_map.count(port)) {
|
|
||||||
errno = EADDRINUSE;
|
errno = EADDRINUSE;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
vs->sport = port;
|
DBG(std::cerr << "emsocket_bind success fd=" << fd << " bindAddr=" << vs->getBindAddr() << std::endl;);
|
||||||
port_map[port] = vs;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_getsockname(int fd, struct sockaddr *addr, socklen_t *len);
|
int emsocket_getsockname(int fd, struct sockaddr *addr, socklen_t *len) {
|
||||||
|
DBG(std::cerr << "emsocket_getsockname fd=" << fd << std::endl;);
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
vs->getBindAddr().copyTo(addr, len);
|
||||||
|
DBG(std::cerr << " --> " << vs->getBindAddr() << std::endl;);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_connect(int fd, const struct sockaddr *addr, socklen_t len);
|
int emsocket_connect(int fd, const struct sockaddr *addr, socklen_t len) {
|
||||||
|
SocketAddr dest(addr, len);
|
||||||
|
DBG(std::cerr << "emsocket_connect: fd=" << fd << ", " << dest << std::endl;);
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (vs->isUDP()) {
|
||||||
|
// connect() on a UDP socket actually has a particular meaning...
|
||||||
|
// but this is not implemented here.
|
||||||
|
DBG(std::cerr << "emsocket_connect: Unexpected UDP" << std::endl;);
|
||||||
|
errno = EPROTOTYPE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (vs->isConnected() || vs->isShutdown()) {
|
||||||
|
DBG(std::cerr << "emsocket_connect: Already connected or shutdown" << std::endl;);
|
||||||
|
errno = EISCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (dest.isLocalHost()) {
|
||||||
|
if (!vs->startLocalConnect(dest.getPort())) {
|
||||||
|
DBG(std::cerr << "emsocket_connect: StartLocalConnect failed" << std::endl;);
|
||||||
|
errno = ECONNREFUSED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (currentProxyUrl.size() > 0) {
|
||||||
|
if (!vs->startProxyConnect(currentProxyUrl, dest)) {
|
||||||
|
DBG(std::cerr << "emsocket_connect: StartProxyConnect failed" << std::endl;);
|
||||||
|
errno = ECONNREFUSED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DBG(std::cerr << "emsocket_connect: Unreachable address" << std::endl;);
|
||||||
|
errno = ENETUNREACH;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_getpeername(int fd, struct sockaddr *addr, socklen_t *len);
|
if (vs->isConnected() && !vs->isShutdown()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t emsocket_send(int fd, const void *buf, size_t n, int flags);
|
if (!vs->canBlock()) {
|
||||||
|
DBG(std::cerr << "emsocket_connect: Connection in progress" << std::endl;);
|
||||||
|
errno = EINPROGRESS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
vs->waitForConnect();
|
||||||
|
if (!vs->isConnected() || vs->isShutdown()) {
|
||||||
|
DBG(std::cerr << "emsocket_connect: Connection failed after wait" << std::endl;);
|
||||||
|
errno = ECONNREFUSED;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t emsocket_recv(int fd, void *buf, size_t n, int flags);
|
int emsocket_getpeername(int fd, struct sockaddr *addr, socklen_t *len) {
|
||||||
|
DBG(std::cerr << "emsocket_getpeername: fd=" << fd << std::endl;);
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (!vs->isConnected()) {
|
||||||
|
errno = ENOTCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
vs->getRemoteAddr().copyTo(addr, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t emsocket_send(int fd, const void *buf, size_t n, int flags) {
|
||||||
|
DBG(std::cerr << "emsocket_send: fd=" << fd << ", n = " << n << std::endl;);
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (!vs->isConnected() || vs->isShutdown()) {
|
||||||
|
DBG(std::cerr << " --> not connected" << std::endl;);
|
||||||
|
errno = ENOTCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
flags &= ~MSG_NOSIGNAL; // no signals anyway
|
||||||
|
if (flags != 0) {
|
||||||
|
DBG(std::cerr << "Unsupported flags in emsocket_send: " << flags << std::endl;);
|
||||||
|
errno = EOPNOTSUPP;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
vs->write(buf, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t emsocket_recv(int fd, void *buf, size_t n, int flags) {
|
||||||
|
DBG(std::cerr << "emsocket_recv: fd=" << fd << ", n=" << n << std::endl;);
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (flags != 0) {
|
||||||
|
DBG(std::cerr << "Unsupported flags in emsocket_recv: " << flags << std::endl;);
|
||||||
|
errno = EOPNOTSUPP;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// If there's data ready to go, give it back.
|
||||||
|
ssize_t bytes = vs->read(buf, n);
|
||||||
|
if (bytes > 0) {
|
||||||
|
DBG(std::cerr << " --> read " << bytes << std::endl;);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
if (!vs->isConnected() || vs->isShutdown()) {
|
||||||
|
DBG(std::cerr << " --> not connected or shutdown" << std::endl;);
|
||||||
|
errno = ENOTCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!vs->canBlock()) {
|
||||||
|
DBG(std::cerr << " --> would block" << std::endl;);
|
||||||
|
errno = EWOULDBLOCK;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
vs->waitForData();
|
||||||
|
bytes = vs->read(buf, n);
|
||||||
|
DBG(std::cerr << " --> read " << bytes << std::endl;);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t emsocket_sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len) {
|
ssize_t emsocket_sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len) {
|
||||||
if (!is_localhost(addr, addr_len)) {
|
DBG(std::cerr << "emsocket_sendto fd=" << fd << ", n=" << n << std::endl;);
|
||||||
// Sending to other than localhost not yet implemented
|
if (addr == NULL || addr_len < sizeof(sockaddr_in)) {
|
||||||
return 0;
|
DBG(std::cerr << " --> EDESTADDRREQ" << std::endl;);
|
||||||
}
|
errno = EDESTADDRREQ;
|
||||||
uint16_t source_port;
|
return -1;
|
||||||
uint16_t dest_port = get_port(addr, addr_len);
|
}
|
||||||
VirtualSocket* dest_vs = nullptr;
|
SocketAddr dest(addr, addr_len);
|
||||||
{
|
auto vs = VirtualSocket::get(fd);
|
||||||
VLOCK();
|
if (flags != 0) {
|
||||||
source_port = getvs(fd)->sport;
|
DBG(std::cerr << " --> EOPNOTSUPP" << std::endl;);
|
||||||
auto it = port_map.find(dest_port);
|
errno = EOPNOTSUPP;
|
||||||
if (it != port_map.end()) {
|
return -1;
|
||||||
dest_vs = it->second;
|
}
|
||||||
}
|
if (!vs->isUDP()) {
|
||||||
}
|
if (addr != NULL || addr_len != 0) {
|
||||||
assert(source_port && "sendto before bind()");
|
DBG(std::cerr << " --> EISCONN" << std::endl;);
|
||||||
if (!dest_vs) {
|
errno = EISCONN;
|
||||||
// Nothing is listening on localhost?
|
return -1;
|
||||||
return 0;
|
}
|
||||||
}
|
DBG(std::cerr << " --> forwarding to emsocket_send" << std::endl;);
|
||||||
// Lock destination vs
|
return emsocket_send(fd, buf, n, flags);
|
||||||
{
|
}
|
||||||
const std::lock_guard<std::mutex> lock(dest_vs->mutex);
|
if (!vs->isBound()) {
|
||||||
dest_vs->recvbuf.emplace_back(source_port, (const char*)buf, n);
|
DBG(std::cerr << " --> autobinding" << std::endl;);
|
||||||
}
|
// Autobind to random port
|
||||||
dest_vs->cv.notify_all();
|
if (!vs->bind(SocketAddr())) {
|
||||||
return n;
|
DBG(std::cerr << " --> EADDRINUSE" << std::endl;);
|
||||||
|
errno = EADDRINUSE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vs->sendto(buf, n, dest);
|
||||||
|
DBG(std::cerr << " --> sent" << std::endl;);
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t emsocket_recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len) {
|
ssize_t emsocket_recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len) {
|
||||||
VirtualSocket *vs;
|
DBG(std::cerr << "emsocket_recvfrom fd=" << fd << ", n = " << n << std::endl;);
|
||||||
{
|
auto vs = VirtualSocket::get(fd);
|
||||||
VLOCK();
|
if (flags != 0) {
|
||||||
vs = getvs(fd);
|
DBG(std::cerr << "emsocket: unsupported flags in recvfrom" << std::endl;);
|
||||||
}
|
errno = EOPNOTSUPP;
|
||||||
// For now, this should never be called in a blocking situation.
|
return -1;
|
||||||
assert(vs->recvbuf.size() > 0);
|
}
|
||||||
const std::lock_guard<std::mutex> lock(vs->mutex);
|
if (!vs->isUDP()) {
|
||||||
const pkt &p = vs->recvbuf.front();
|
DBG(std::cerr << " --> forwarding to emsocket_recv" << std::endl;);
|
||||||
ssize_t written = std::min(p.data.size(), n);
|
vs->getRemoteAddr().copyTo(addr, addr_len);
|
||||||
bool truncated = (written != (ssize_t)p.data.size());
|
return emsocket_recv(fd, buf, n, 0);
|
||||||
memcpy(buf, &p.data[0], written);
|
}
|
||||||
if (addr) {
|
// Common case: UDP
|
||||||
struct sockaddr_in ai = {0};
|
SocketAddr dest;
|
||||||
ai.sin_family = AF_INET;
|
ssize_t bytes = vs->recvfrom(buf, n, &dest); // nonblock
|
||||||
ai.sin_port = htons(p.source_port);
|
if (bytes > 0) {
|
||||||
ai.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
DBG(std::cerr << " --> got " << bytes << " from " << dest << std::endl;);
|
||||||
memcpy(addr, &ai, std::min((size_t)*addr_len, sizeof(ai)));
|
dest.copyTo(addr, addr_len);
|
||||||
*addr_len = sizeof(ai);
|
return bytes;
|
||||||
}
|
}
|
||||||
vs->recvbuf.pop_front();
|
if (!vs->canBlock()) {
|
||||||
if (truncated) errno = EMSGSIZE;
|
DBG(std::cerr << " --> EWOULDBLOCK" << std::endl;);
|
||||||
//std::cout << "sockfd=" << sockfd << " Received packet of size " << written << std::endl;
|
errno = EWOULDBLOCK;
|
||||||
return written;
|
return -1;
|
||||||
|
}
|
||||||
|
vs->waitForData();
|
||||||
|
bytes = vs->recvfrom(buf, n, &dest);
|
||||||
|
dest.copyTo(addr, addr_len);
|
||||||
|
DBG(std::cerr << " --> got " << bytes << " from " << dest << " after wait" << std::endl;);
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ssize_t emsocket_sendmsg(int fd, const struct msghdr *message, int flags);
|
ssize_t emsocket_sendmsg(int fd, const struct msghdr *message, int flags);
|
||||||
|
|
||||||
int emsocket_sendmmsg(int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags);
|
int emsocket_sendmmsg(int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags);
|
||||||
@ -248,15 +316,44 @@ ssize_t emsocket_recvmsg(int fd, struct msghdr *message, int flags);
|
|||||||
|
|
||||||
int emsocket_recvmmsg(int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags, struct timespec *tmo);
|
int emsocket_recvmmsg(int fd, struct mmsghdr *vmessages, unsigned int vlen, int flags, struct timespec *tmo);
|
||||||
|
|
||||||
int emsocket_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen);
|
int emsocket_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) {
|
||||||
|
std::cerr << "emsocket_getsockopt: level=" << level << ", optname=" << optname << std::endl;
|
||||||
int emsocket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) {
|
if (level == SOL_SOCKET) {
|
||||||
return -1;
|
if (optname == SO_ERROR) {
|
||||||
|
if (optval && optlen && *optlen == sizeof(int)) {
|
||||||
|
int *val = (int*)optval;
|
||||||
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (!vs->isConnected() || vs->isShutdown()) {
|
||||||
|
*val = ECONNREFUSED;
|
||||||
|
} else {
|
||||||
|
*val = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errno = ENOPROTOOPT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errno = ENOPROTOOPT;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_listen(int fd, int n);
|
int emsocket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||||
|
std::cerr << "emsocket_setsockopt: level=" << level << ", optname=" << optname << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_accept(int fd, struct sockaddr *addr, socklen_t *addr_len);
|
int emsocket_listen(int fd, int n) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
int emsocket_accept(int fd, struct sockaddr *addr, socklen_t *addr_len) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_accept4(int fd, struct sockaddr *addr, socklen_t *addr_len, int flags);
|
int emsocket_accept4(int fd, struct sockaddr *addr, socklen_t *addr_len, int flags);
|
||||||
|
|
||||||
@ -266,9 +363,14 @@ int emsocket_sockatmark(int fd);
|
|||||||
|
|
||||||
int emsocket_isfdtype(int fd, int fdtype);
|
int emsocket_isfdtype(int fd, int fdtype);
|
||||||
|
|
||||||
int emsocket_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
|
int emsocket_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
|
||||||
|
/* There's no DNS lookup function in javascript, so it would need to be provided */
|
||||||
|
return EAI_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
void emsocket_freeaddrinfo(struct addrinfo *res);
|
void emsocket_freeaddrinfo(struct addrinfo *res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const char* emsocket_gai_strerror(int errcode);
|
const char* emsocket_gai_strerror(int errcode);
|
||||||
|
|
||||||
@ -296,34 +398,139 @@ int emsocket_gethostbyname_r(const char *name, struct hostent *ret, char *buf, s
|
|||||||
|
|
||||||
int emsocket_gethostbyname2_r(const char *name, int af, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
|
int emsocket_gethostbyname2_r(const char *name, int af, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
|
||||||
|
|
||||||
// Cheap hack
|
#define MAX_SELECT_FDS 64
|
||||||
// Only supports select on a single fd
|
|
||||||
int emsocket_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) {
|
|
||||||
int sockfd = nfds - 1;
|
|
||||||
assert(FD_ISSET(sockfd, readfds));
|
|
||||||
VirtualSocket *vs;
|
|
||||||
{
|
|
||||||
VLOCK();
|
|
||||||
vs = getvs(sockfd);
|
|
||||||
}
|
|
||||||
std::unique_lock<std::mutex> lock(vs->mutex);
|
|
||||||
|
|
||||||
if (timeout == NULL) {
|
static void print_fd_set(int nfds, fd_set *x) {
|
||||||
vs->cv.wait(lock, [&]{ return vs->recvbuf.size() > 0; });
|
if (x == NULL) {
|
||||||
} else {
|
std::cerr << "NULL";
|
||||||
long ms = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000);
|
} else {
|
||||||
auto cvtimeout = std::chrono::milliseconds(ms);
|
std::cerr << "[ ";
|
||||||
vs->cv.wait_for(lock, cvtimeout, [&]{ return vs->recvbuf.size() > 0; });
|
for (int fd = 0; fd < nfds; fd++) {
|
||||||
}
|
if (FD_ISSET(fd, x)) {
|
||||||
if (vs->recvbuf.size() == 0) {
|
std::cerr << fd << " ";
|
||||||
return 0;
|
}
|
||||||
}
|
}
|
||||||
return 1;
|
std::cerr << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int emsocket_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) {
|
||||||
|
#if EMSOCKET_DEBUG
|
||||||
|
DBG(
|
||||||
|
std::cerr << "emsocket_select nfds=" << nfds;
|
||||||
|
std::cerr << ", readfds="; print_fd_set(nfds, readfds);
|
||||||
|
std::cerr << ", writefs="; print_fd_set(nfds, writefds);
|
||||||
|
std::cerr << ", exceptfds="; print_fd_set(nfds, exceptfds);
|
||||||
|
std::cerr << ", timeout=";
|
||||||
|
if (timeout) {
|
||||||
|
std::cerr << timeout->tv_sec << "s " << timeout->tv_usec << "us";
|
||||||
|
} else {
|
||||||
|
std::cerr << "NULL";
|
||||||
|
}
|
||||||
|
std::cerr << std::endl;
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
// Convert to a call to poll() instead
|
||||||
|
struct pollfd fds[MAX_SELECT_FDS] = { 0 };
|
||||||
|
nfds_t count = 0;
|
||||||
|
for (int fd = 0; fd < nfds; fd++) {
|
||||||
|
bool check_read = readfds && FD_ISSET(fd, readfds);
|
||||||
|
bool check_write = writefds && FD_ISSET(fd, writefds);
|
||||||
|
bool check_except = exceptfds && FD_ISSET(fd, exceptfds);
|
||||||
|
if (check_read || check_write || check_except) {
|
||||||
|
if (count == MAX_SELECT_FDS) {
|
||||||
|
DBG(std::cerr << "emsocket select() called with too many fds" << std::endl;);
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fds[count].fd = fd;
|
||||||
|
fds[count].events = (check_read ? POLLIN : 0) | (check_write ? POLLOUT : 0);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (readfds) FD_ZERO(readfds);
|
||||||
|
if (writefds) FD_ZERO(writefds);
|
||||||
|
if (exceptfds) FD_ZERO(exceptfds);
|
||||||
|
int poll_timeout = -1;
|
||||||
|
if (timeout) {
|
||||||
|
poll_timeout = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000);
|
||||||
|
}
|
||||||
|
int ret = emsocket_poll(fds, count, poll_timeout);
|
||||||
|
if (ret <= 0) {
|
||||||
|
//DBG(std::cerr << " --> returning " << ret << std::endl;);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitcount = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int fd = fds[i].fd;
|
||||||
|
short revents = fds[i].revents;
|
||||||
|
if (readfds && (revents & (POLLIN|POLLHUP))) {
|
||||||
|
bitcount++;
|
||||||
|
FD_SET(fd, readfds);
|
||||||
|
}
|
||||||
|
if (writefds && (revents & POLLOUT)) {
|
||||||
|
bitcount++;
|
||||||
|
FD_SET(fd, writefds);
|
||||||
|
}
|
||||||
|
if (exceptfds && (revents & (POLLERR|POLLHUP))) {
|
||||||
|
bitcount++;
|
||||||
|
FD_SET(fd, exceptfds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if EMSOCKET_DEBUG
|
||||||
|
DBG(
|
||||||
|
std::cerr << " --> returning " << bitcount;
|
||||||
|
std::cerr << ", readfds="; print_fd_set(nfds, readfds);
|
||||||
|
std::cerr << ", writefs="; print_fd_set(nfds, writefds);
|
||||||
|
std::cerr << ", exceptfds="; print_fd_set(nfds, exceptfds);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
return bitcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
|
int emsocket_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
|
||||||
|
|
||||||
int emsocket_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
#define MAX_POLL_FDS 64
|
||||||
|
|
||||||
|
int emsocket_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||||
|
int count = 0;
|
||||||
|
std::function<bool(void)> predicate = [&]() {
|
||||||
|
count = 0;
|
||||||
|
for (int i = 0; i < nfds; i++) {
|
||||||
|
if (fds[i].fd < 0) continue;
|
||||||
|
auto vs = VirtualSocket::get(fds[i].fd);
|
||||||
|
short revents = 0;
|
||||||
|
if (fds[i].events & POLLIN) {
|
||||||
|
if (vs->hasData()) {
|
||||||
|
revents |= POLLIN;
|
||||||
|
}
|
||||||
|
if (vs->isShutdown()) {
|
||||||
|
revents |= POLLIN | POLLHUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fds[i].events & POLLOUT) {
|
||||||
|
if (vs->isUDP() || vs->isConnected() || vs->isShutdown()) {
|
||||||
|
revents |= POLLOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fds[i].revents = revents;
|
||||||
|
if (revents) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Keep waiting until count > 0
|
||||||
|
return (count > 0);
|
||||||
|
};
|
||||||
|
//if (emscripten_is_main_browser_thread()) {
|
||||||
|
// //DBG(std::cerr << "emsocket_poll fast exit due to being on main thread" << std::endl;);
|
||||||
|
// VirtualSocket::runWithLock(predicate);
|
||||||
|
// return count;
|
||||||
|
//}
|
||||||
|
VirtualSocket::waitFor(predicate, timeout);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);
|
int emsocket_ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);
|
||||||
|
|
||||||
@ -337,24 +544,46 @@ int emsocket_epoll_wait(int, struct epoll_event *, int, int);
|
|||||||
|
|
||||||
int emsocket_epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
|
int emsocket_epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
|
||||||
|
|
||||||
|
ssize_t emsocket_read(int fd, void *buf, size_t count) {
|
||||||
|
if (fd < EMSOCKET_BASE_FD) return read(fd, buf, count);
|
||||||
|
return emsocket_recv(fd, buf, count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t emsocket_write(int fd, const void *buf, size_t count) {
|
||||||
|
if (fd < EMSOCKET_BASE_FD) return write(fd, buf, count);
|
||||||
|
return emsocket_send(fd, buf, count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
int emsocket_close(int fd) {
|
int emsocket_close(int fd) {
|
||||||
if (fd < EMSOCKET_BASE_FD) {
|
if (fd < EMSOCKET_BASE_FD) {
|
||||||
return ::close(fd);
|
return ::close(fd);
|
||||||
}
|
}
|
||||||
VLOCK();
|
auto vs = VirtualSocket::get(fd);
|
||||||
auto vs = getvs(fd);
|
vs->close();
|
||||||
if (vs->sport) {
|
|
||||||
port_map.erase(vs->sport);
|
|
||||||
vs->sport = 0;
|
|
||||||
}
|
|
||||||
vs->reset(false);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_fcntl(int fd, int cmd, ...) {
|
int emsocket_fcntl(int fd, int cmd, ...) {
|
||||||
abort();
|
auto vs = VirtualSocket::get(fd);
|
||||||
|
if (cmd == F_GETFL) {
|
||||||
|
return vs->isBlocking() ? O_NONBLOCK : 0;
|
||||||
|
} else if (cmd == F_SETFL) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, cmd);
|
||||||
|
int flags = va_arg(ap, int);
|
||||||
|
vs->setBlocking((flags & O_NONBLOCK) ? false : true);
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
if (flags) {
|
||||||
|
std::cerr << "emsocket_fcntl unrecognized flags=" << flags << std::endl;
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::cerr << "emsocket_fcntl unknown fcntl cmd=" << cmd << std::endl;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int emsocket_ioctl(int fd, unsigned long request, ...) {
|
int emsocket_ioctl(int fd, unsigned long request, ...) {
|
||||||
|
std::cerr << "emsocket_ioctl not implemented, request=" << request << std::endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ SOFTWARE.
|
|||||||
// but it redeclares ioctl(), so it needs to be here.
|
// but it redeclares ioctl(), so it needs to be here.
|
||||||
#include <stropts.h>
|
#include <stropts.h>
|
||||||
|
|
||||||
|
#include <emscripten/threading.h>
|
||||||
|
|
||||||
|
// This must match the same defines in VirtualSocket.h
|
||||||
#define EMSOCKET_BASE_FD 512
|
#define EMSOCKET_BASE_FD 512
|
||||||
#define EMSOCKET_MAX_FD 1024
|
#define EMSOCKET_MAX_FD 1024
|
||||||
|
|
||||||
@ -118,6 +121,8 @@ extern int emsocket_epoll_wait(int, struct epoll_event *, int, int);
|
|||||||
extern int emsocket_epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
|
extern int emsocket_epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
|
||||||
|
|
||||||
/* From unistd.h */
|
/* From unistd.h */
|
||||||
|
extern ssize_t emsocket_read(int fd, void *buf, size_t count);
|
||||||
|
extern ssize_t emsocket_write(int fd, const void *buf, size_t count);
|
||||||
extern int emsocket_close(int fd);
|
extern int emsocket_close(int fd);
|
||||||
|
|
||||||
/* From fcntl.h */
|
/* From fcntl.h */
|
||||||
@ -177,6 +182,8 @@ extern int emsocket_ioctl(int fd, unsigned long request, ...);
|
|||||||
#define epoll_ctl emsocket_epoll_ctl
|
#define epoll_ctl emsocket_epoll_ctl
|
||||||
#define epoll_wait emsocket_epoll_wait
|
#define epoll_wait emsocket_epoll_wait
|
||||||
#define epoll_pwait emsocket_epoll_pwait
|
#define epoll_pwait emsocket_epoll_pwait
|
||||||
|
#define read emsocket_read
|
||||||
|
#define write emsocket_write
|
||||||
#define close emsocket_close
|
#define close emsocket_close
|
||||||
|
|
||||||
// Special macros needed to handle __VA_ARGS__ forwarding
|
// Special macros needed to handle __VA_ARGS__ forwarding
|
||||||
|
98
src/emsocket/emsocketctl.cpp
Normal file
98
src/emsocket/emsocketctl.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define EMSOCKET_INTERNAL
|
||||||
|
|
||||||
|
#include "emsocketctl.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <emscripten.h>
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
std::string currentProxyUrl;
|
||||||
|
bool didInit;
|
||||||
|
pthread_t ioThread;
|
||||||
|
std::mutex ioMutex;
|
||||||
|
std::condition_variable ioCv;
|
||||||
|
std::vector<std::function<void()> > ioCallbacks;
|
||||||
|
uint64_t ioCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace emsocket;
|
||||||
|
|
||||||
|
static void *io_thread_main(void *);
|
||||||
|
|
||||||
|
void emsocket_init() {
|
||||||
|
if (didInit) return;
|
||||||
|
didInit = true;
|
||||||
|
|
||||||
|
// Launch dedicated i/o thread
|
||||||
|
int rc = pthread_create(&ioThread, NULL, io_thread_main, NULL);
|
||||||
|
if (rc != 0) {
|
||||||
|
std::cerr << "emsocket_init: Failed to launch I/O thread" << std::endl;
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void emsocket_set_proxy(const char *proxyUrl) {
|
||||||
|
currentProxyUrl = proxyUrl ? proxyUrl : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void io_thread_reenter(void) {
|
||||||
|
std::vector<std::function<void()> > callbacks;
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(ioMutex);
|
||||||
|
callbacks = std::move(ioCallbacks);
|
||||||
|
ioCallbacks.clear();
|
||||||
|
ioCounter += 1;
|
||||||
|
}
|
||||||
|
ioCv.notify_all();
|
||||||
|
for (const auto &callback : callbacks) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *io_thread_main(void *) {
|
||||||
|
emscripten_set_main_loop(io_thread_reenter, 100, EM_TRUE);
|
||||||
|
abort(); // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
// Returns the id of the callback.
|
||||||
|
// Use this id in emsocket_remove_io_callback()
|
||||||
|
void emsocket_run_on_io_thread(bool sync, std::function<void()> && callback) {
|
||||||
|
std::unique_lock<std::mutex> lock(ioMutex);
|
||||||
|
ioCallbacks.emplace_back(std::move(callback));
|
||||||
|
if (sync) {
|
||||||
|
// Wait for 2 counter clicks, that'll guarantee the callback has run.
|
||||||
|
uint64_t ioTarget = ioCounter + 2;
|
||||||
|
ioCv.wait(lock, [&](){ return ioCounter >= ioTarget; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace emsocket
|
45
src/emsocket/emsocketctl.h
Normal file
45
src/emsocket/emsocketctl.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 paradust7
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void emsocket_init();
|
||||||
|
void emsocket_set_proxy(const char *proxyUrl);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EMSOCKET_INTERNAL
|
||||||
|
#include <functional>
|
||||||
|
namespace emsocket {
|
||||||
|
|
||||||
|
// Will be called exactly once in the I/O thread.
|
||||||
|
void emsocket_run_on_io_thread(bool sync, std::function<void()> && callback);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user