quic
Version:
A QUIC server/client implementation in Node.js.
246 lines • 9.71 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
// **Github:** https://github.com/fidm/quic
//
// **License:** MIT
const util_1 = require("util");
const events_1 = require("events");
const constant_1 = require("./internal/constant");
const common_1 = require("./internal/common");
const error_1 = require("./internal/error");
const packet_1 = require("./internal/packet");
const symbol_1 = require("./internal/symbol");
const protocol_1 = require("./internal/protocol");
const socket_1 = require("./socket");
const session_1 = require("./session");
const debug = util_1.debuglog('quic');
class ServerSession extends session_1.Session {
constructor(id, socket, server) {
super(id, protocol_1.SessionType.SERVER);
this[symbol_1.kSocket] = socket;
this[symbol_1.kServer] = server;
this[symbol_1.kState].localPort = server.localPort;
this[symbol_1.kState].localAddress = server.localAddress;
this[symbol_1.kState].localFamily = server.localFamily;
this[symbol_1.kState].localAddr = new protocol_1.SocketAddress(server.address());
this[symbol_1.kState].maxPacketSize =
server.localFamily === protocol_1.FamilyType.IPv6 ? constant_1.MaxPacketSizeIPv6 : constant_1.MaxPacketSizeIPv4;
}
get server() {
return this[symbol_1.kServer];
}
}
exports.ServerSession = ServerSession;
class ServerState {
constructor() {
this.destroyed = false;
}
}
exports.ServerState = ServerState;
//
// *************** Server ***************
//
class Server extends events_1.EventEmitter {
constructor() {
super();
this[symbol_1.kSocket] = null;
this.localFamily = '';
this.localAddress = '';
this.localPort = 0;
this.listening = false;
this[symbol_1.kConns] = new Map();
this[symbol_1.kState] = new ServerState();
this[symbol_1.kIntervalCheck] = setInterval(() => {
const time = Date.now();
this._intervalCheck(time);
}, 1024);
}
address() {
return { port: this.localPort, family: this.localFamily, address: this.localAddress };
}
async listen(port, address = 'localhost') {
if (this[symbol_1.kSocket] != null) {
throw new Error('Server listening');
}
const addr = await common_1.lookup(address);
debug(`server listen: ${address}, ${port}`, addr);
const socket = this[symbol_1.kSocket] = socket_1.createSocket(addr.family);
socket[symbol_1.kState].exclusive = false; // socket is shared between all sessions
socket
.on('error', (err) => this.emit('error', err))
.on('close', () => serverOnClose(this))
.on('message', (msg, rinfo) => serverOnMessage(this, socket, msg, rinfo));
const res = new Promise((resolve, reject) => {
socket.once('listening', () => {
socket.removeListener('error', reject);
const localAddr = socket.address();
this.localFamily = localAddr.family;
this.localAddress = localAddr.address;
this.localPort = localAddr.port;
this.listening = true;
process.nextTick(() => this.emit('listening'));
resolve();
});
socket.once('error', reject);
});
// Can't support cluster
socket.bind({ port, address: addr.address, exclusive: true });
return res;
}
_intervalCheck(time) {
for (const session of this[symbol_1.kConns].values()) {
// server session idle timeout
if (time - session[symbol_1.kState].lastNetworkActivityTime > session[symbol_1.kState].idleTimeout) {
// When a server decides to terminate an idle connection,
// it should not notify the client to avoid waking up the radio on mobile devices.
if (!session.destroyed) {
session.emit('timeout');
session.destroy(error_1.QuicError.fromError(error_1.QuicError.QUIC_NETWORK_IDLE_TIMEOUT));
}
this[symbol_1.kConns].delete(session.id);
return;
}
// other session check
session._intervalCheck(time);
}
return;
}
shutdown(_timeout) {
return Promise.reject('TODO');
}
async close(err) {
if (this[symbol_1.kState].destroyed) {
return;
}
this[symbol_1.kState].destroyed = true;
for (const session of this[symbol_1.kConns].values()) {
await session.close(err);
}
const timer = this[symbol_1.kIntervalCheck];
if (timer != null) {
clearInterval(timer);
}
const socket = this[symbol_1.kSocket];
if (socket != null && !socket[symbol_1.kState].destroyed) {
socket.close();
socket[symbol_1.kState].destroyed = true;
}
process.nextTick(() => this.emit('close'));
}
getConnections() {
return Promise.resolve(this[symbol_1.kConns].size); // TODO
}
ref() {
const socket = this[symbol_1.kSocket];
if (socket == null) {
throw new Error('Server not listen');
}
socket.ref();
}
unref() {
const socket = this[symbol_1.kSocket];
if (socket == null) {
throw new Error('Server not listen');
}
socket.unref();
}
}
exports.Server = Server;
function serverOnClose(server) {
for (const session of server[symbol_1.kConns].values()) {
session.destroy(new Error('the underlying socket closed'));
}
// server[kConns].clear()
if (!server[symbol_1.kState].destroyed) {
const timer = server[symbol_1.kIntervalCheck];
if (timer != null) {
clearInterval(timer);
}
server[symbol_1.kState].destroyed = true;
server.emit('close');
}
}
function serverOnMessage(server, socket, msg, rinfo) {
if (msg.length === 0 || server[symbol_1.kState].destroyed) {
return;
}
// The packet size should not exceed protocol.MaxReceivePacketSize bytes
// If it does, we only read a truncated packet, which will then end up undecryptable
if (msg.length > constant_1.MaxReceivePacketSize) {
debug(`server message - receive too large data: $d bytes`, msg.length);
// msg = msg.slice(0, MaxReceivePacketSize)
}
const senderAddr = new protocol_1.SocketAddress(rinfo);
const rcvTime = Date.now();
const bufv = new common_1.BufferVisitor(msg);
let packet = null;
try {
packet = packet_1.parsePacket(bufv, protocol_1.SessionType.CLIENT);
}
catch (err) {
debug(`server message - parsing packet error: %o`, err);
// drop this packet if we can't parse the Public Header
return;
}
if (packet.isNegotiation()) {
debug(`server message - Received a unexpect Negotiation packet.`);
return;
}
const connectionID = packet.connectionID.valueOf();
let session = server[symbol_1.kConns].get(connectionID);
const newSession = session == null;
if (session == null) {
if (packet.isReset()) {
return;
}
session = new ServerSession(packet.connectionID, socket, server);
server[symbol_1.kConns].set(connectionID, session);
debug(`server message - new session: %s`, connectionID);
}
else if (session.destroyed) {
// Late packet for closed session
return;
}
if (packet.isReset()) {
// check if the remote address and the connection ID match
// otherwise this might be an attacker trying to inject a PUBLIC_RESET to kill the connection
const remoteAddr = session[symbol_1.kState].remoteAddr;
if (remoteAddr !== null && !remoteAddr.equals(senderAddr)) {
debug(`session %s - received a spoofed Public Reset: %j`, session.id, senderAddr);
return;
}
debug(`session %s - received a Public Reset: %j`, session.id, packet);
session.destroy(error_1.QuicError.fromError(error_1.QuicError.QUIC_PUBLIC_RESET));
return;
}
// update the remote address, even if unpacking failed for any other reason than a decryption error
session[symbol_1.kState].remotePort = senderAddr.port;
session[symbol_1.kState].remoteAddress = senderAddr.address;
session[symbol_1.kState].remoteFamily = senderAddr.family;
session[symbol_1.kState].remoteAddr = senderAddr;
if (newSession) {
server.emit('session', session);
}
const version = packet.version;
if (!session[symbol_1.kState].versionNegotiated) {
if (!protocol_1.isSupportedVersion(version)) {
const negotiationPacket = packet_1.NegotiationPacket.fromConnectionID(session[symbol_1.kID]);
debug(`session %s - send Public Negotiation: %j`, session.id, negotiationPacket);
session._sendPacket(negotiationPacket, (err) => {
if (err != null && session != null) {
session.close(err);
}
});
return;
}
session[symbol_1.kVersion] = version;
session[symbol_1.kState].versionNegotiated = true;
}
else if (version !== '' && session[symbol_1.kVersion] !== version) {
debug(`session %s - invalid version in RegularPacket: %s`, session.id, version);
return;
}
session[symbol_1.kState].bytesRead += msg.length;
session._handleRegularPacket(packet, rcvTime, bufv);
}
//# sourceMappingURL=server.js.map