diff --git a/lib/_dtls_wrap.js b/lib/_dtls_wrap.js new file mode 100644 index 00000000000..5e52d887dd2 --- /dev/null +++ b/lib/_dtls_wrap.js @@ -0,0 +1,699 @@ +var assert = require('assert'); +var constants = require('constants'); +var crypto = require('crypto'); +var dgram = require('dgram'); +var tls = require('tls'); +var util = require('util'); + +var Timer = process.binding('timer_wrap').Timer; +var dtls_wrap = process.binding('tls_wrap'); + +var debug = util.debuglog('dtls'); + +function onhandshakestart() { + debug('onhandshakestart'); + + var self = this; + var ssl = self.ssl; + var now = Timer.now(); + + assert(now >= ssl.lastHandshakeTime); + + if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) { + ssl.handshakes = 0; + } + + var first = (ssl.lastHandshakeTime === 0); + ssl.lastHandshakeTime = now; + if (first) return; + + if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) { + // Defer the error event to the next tick. We're being called from OpenSSL's + // state machine and OpenSSL is not re-entrant. We cannot allow the user's + // callback to destroy the connection right now, it would crash and burn. + setImmediate(function() { + var err = new Error('TLS session renegotiation attack detected.'); + self._tlsError(err); + }); + } +} + + +function onhandshakedone() { + // for future use + debug('onhandshakedone'); + this._finishInit(); +} + + +function onclienthello(hello) { + var self = this, + onceSession = false, + onceSNI = false; + + function callback(err, session) { + if (onceSession) + return self.destroy(new Error('DTLS session callback was called 2 times')); + onceSession = true; + + if (err) + return self.destroy(err); + + // NOTE: That we have disabled OpenSSL's internal session storage in + // `node_crypto.cc` and hence its safe to rely on getting servername only + // from clienthello or this place. + var ret = self.ssl.loadSession(session); + + // Servername came from SSL session + // NOTE: DTLS Session ticket doesn't include servername information + // + // Another note, From RFC3546: + // + // If, on the other hand, the older + // session is resumed, then the server MUST ignore extensions appearing + // in the client hello, and send a server hello containing no + // extensions; in this case the extension functionality negotiated + // during the original session initiation is applied to the resumed + // session. + // + // Therefore we should account session loading when dealing with servername + if (ret && ret.servername) { + self._SNICallback(ret.servername, onSNIResult); + } else if (hello.servername && self._SNICallback) { + self._SNICallback(hello.servername, onSNIResult); + } else { + self.ssl.endParser(); + } + } + + function onSNIResult(err, context) { + if (onceSNI) + return self.destroy(new Error('DTLS SNI callback was called 2 times')); + onceSNI = true; + + if (err) + return self.destroy(err); + + if (context) + self.ssl.sni_context = context; + + self.ssl.endParser(); + } + + if (hello.sessionId.length <= 0 || + hello.tlsTicket || + this.server && + !this.server.emit('resumeSession', hello.sessionId, callback)) { + // Invoke SNI callback, since we've no session to resume + if (hello.servername && this._SNICallback) + this._SNICallback(hello.servername, onSNIResult); + else + this.ssl.endParser(); + } +} + +function onnewsession(key, session) { + if (this.server) + this.server.emit('newSession', key, session); +} + + +/** + * Provides a wrap of socket stream to do encrypted communication. + */ + +function DTLSSocket(options, listener) { + + dgram.Socket.call(this, { type: 'udp4' }, listener); + + this._tlsOptions = options; + this._secureEstablished = false; + this._controlReleased = false; + this._SNICallback = null; + this.ssl = null; + this.servername = null; + this.npnProtocol = null; + this.authorized = false; + this.authorizationError = null; + + this.on('error', this._tlsError); + + if (!this._handle) + this.once('bind', this._init.bind(this)); + else + this._init(); +} +util.inherits(DTLSSocket, dgram.Socket); +exports.DTLSSocket = DTLSSocket; + +DTLSSocket.prototype._init = function () { + + assert(this._handle); + + // lib/net.js expect this value to be non-zero if write hasn't been flushed + // immediately + // TODO(indutny): rewise this solution, it might be 1 before handshake and + // represent real writeQueueSize during regular writes. + this._handle.writeQueueSize = 1; + + var self = this; + var options = this._tlsOptions; + + // Wrap socket's handle + var credentials = options.credentials || crypto.createCredentials(); + this.ssl = dtls_wrap.wrap(this._handle, credentials.context, options.isServer); + this.server = options.server || null; + + // For clients, we will always have either a given ca list or be using + // default one + var requestCert = !!options.requestCert || !options.isServer, + rejectUnauthorized = !!options.rejectUnauthorized; + + if (requestCert || rejectUnauthorized) + this.ssl.setVerifyMode(requestCert, rejectUnauthorized); + + if (options.isServer) { + this.ssl.onhandshakestart = onhandshakestart.bind(this); + this.ssl.onhandshakedone = onhandshakedone.bind(this); + this.ssl.onclienthello = onclienthello.bind(this); + this.ssl.onnewsession = onnewsession.bind(this); + this.ssl.lastHandshakeTime = 0; + this.ssl.handshakes = 0; + + if (this.server && + (this.server.listeners('resumeSession').length > 0 || + this.server.listeners('newSession').length > 0)) { + this.ssl.enableSessionCallbacks(); + } + } else { + this.ssl.onhandshakestart = function() {}; + this.ssl.onhandshakedone = this._finishInit.bind(this); + } + + this.ssl.onerror = function(err) { + // Destroy socket if error happened before handshake's finish + if (!this._secureEstablished) { + self._tlsError(err); + self.destroy(); + } else if (options.isServer && + rejectUnauthorized && + /peer did not return a certificate/.test(err.message)) { + // Ignore server's authorization errors + self.destroy(); + } else { + // Throw error + self._tlsError(err); + } + }; + + // If custom SNICallback was given, or if + // there're SNI contexts to perform match against - + // set `.onsniselect` callback. + if (process.features.tls_sni && + options.isServer && + options.server && + (options.SNICallback !== SNICallback || + options.server._contexts.length)) { + assert(typeof options.SNICallback === 'function'); + this._SNICallback = options.SNICallback; + this.ssl.enableHelloParser(); + } + + if (process.features.tls_npn && options.NPNProtocols) + this.ssl.setNPNProtocols(options.NPNProtocols); +}; + +DTLSSocket.prototype._tlsError = function(err) { + this.emit('_tlsError', err); + if (this._controlReleased) + this.emit('error', err); +}; + +DTLSSocket.prototype._releaseControl = function() { + if (this._controlReleased) + return; + this._controlReleased = true; + this.removeListener('error', this._tlsError); +}; + +DTLSSocket.prototype._finishInit = function() { + if (process.features.tls_npn) { + this.npnProtocol = this.ssl.getNegotiatedProtocol(); + } + + if (process.features.tls_sni && this._tlsOptions.isServer) { + this.servername = this.ssl.getServername(); + } + + debug('secure established'); + this._secureEstablished = true; + this.emit('secure'); +}; + +DTLSSocket.prototype._start = function() { + this.ssl.start(); +}; + +DTLSSocket.prototype.setServername = function(name) { + this.ssl.setServername(name); +}; + +DTLSSocket.prototype.setSession = function(session) { + if (util.isString(session)) + session = new Buffer(session, 'binary'); + this.ssl.setSession(session); +}; + +DTLSSocket.prototype.getPeerCertificate = function() { + if (this.ssl) { + var c = this.ssl.getPeerCertificate(); + + if (c) { + if (c.issuer) c.issuer = tls.parseCertString(c.issuer); + if (c.subject) c.subject = tls.parseCertString(c.subject); + return c; + } + } + + return null; +}; + +DTLSSocket.prototype.getSession = function() { + if (this.ssl) { + return this.ssl.getSession(); + } + + return null; +}; + +DTLSSocket.prototype.isSessionReused = function() { + if (this.ssl) { + return this.ssl.isSessionReused(); + } + + return null; +}; + +DTLSSocket.prototype.getCipher = function(err) { + if (this.ssl) { + return this.ssl.getCurrentCipher(); + } else { + return null; + } +}; + +// TODO: support anonymous (nocert) and PSK + + +// AUTHENTICATION MODES +// +// There are several levels of authentication that TLS/SSL supports. +// Read more about this in "man SSL_set_verify". +// +// 1. The server sends a certificate to the client but does not request a +// cert from the client. This is common for most HTTPS servers. The browser +// can verify the identity of the server, but the server does not know who +// the client is. Authenticating the client is usually done over HTTP using +// login boxes and cookies and stuff. +// +// 2. The server sends a cert to the client and requests that the client +// also send it a cert. The client knows who the server is and the server is +// requesting the client also identify themselves. There are several +// outcomes: +// +// A) verifyError returns null meaning the client's certificate is signed +// by one of the server's CAs. The server know's the client idenity now +// and the client is authorized. +// +// B) For some reason the client's certificate is not acceptable - +// verifyError returns a string indicating the problem. The server can +// either (i) reject the client or (ii) allow the client to connect as an +// unauthorized connection. +// +// The mode is controlled by two boolean variables. +// +// requestCert +// If true the server requests a certificate from client connections. For +// the common HTTPS case, users will want this to be false, which is what +// it defaults to. +// +// rejectUnauthorized +// If true clients whose certificates are invalid for any reason will not +// be allowed to make connections. If false, they will simply be marked as +// unauthorized but secure communication will continue. By default this is +// true. +// +// +// +// Options: +// - requestCert. Send verify request. Default to false. +// - rejectUnauthorized. Boolean, default to true. +// - key. string. +// - cert: string. +// - ca: string or array of strings. +// - sessionTimeout: integer. +// +// emit 'secureConnection' +// function (dtlsSocket) { } +// +// "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL", +// "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE", +// "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE", +// "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED", +// "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD", +// "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD", +// "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM", +// "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN", +// "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", +// "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA", +// "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED", +// "CERT_REJECTED" +// +function Server(/* [options], listener */) { + var options, listener; + if (util.isObject(arguments[0])) { + options = arguments[0]; + listener = arguments[1]; + } else if (util.isFunction(arguments[0])) { + options = {}; + listener = arguments[0]; + } + + if (!(this instanceof Server)) return new Server(options, listener); + + this._contexts = []; + + var self = this; + + // Handle option defaults: + this.setOptions(options); + + if (!self.pfx && (!self.cert || !self.key)) { + throw new Error('Missing PFX or certificate + private key.'); + } + + var sharedCreds = crypto.createCredentials({ + pfx: self.pfx, + key: self.key, + passphrase: self.passphrase, + cert: self.cert, + ca: self.ca, + ciphers: self.ciphers || tls.DEFAULT_CIPHERS, + secureProtocol: self.secureProtocol, + secureOptions: self.secureOptions, + crl: self.crl, + sessionIdContext: self.sessionIdContext + }); + this._sharedCreds = sharedCreds; + + var timeout = options.handshakeTimeout || (120 * 1000); + + if (!util.isNumber(timeout)) { + throw new TypeError('handshakeTimeout must be a number'); + } + + if (self.sessionTimeout) { + sharedCreds.context.setSessionTimeout(self.sessionTimeout); + } + + if (self.ticketKeys) { + sharedCreds.context.setTicketKeys(self.ticketKeys); + } + + debug('before : dgram.Server.call'); + + // Now attach the UDP socket to the DTLS socket + this.socket = new DTLSSocket({ + credentials: sharedCreds, + isServer: true, + server: self, + type: 'udp4', + requestCert: self.requestCert, + rejectUnauthorized: self.rejectUnauthorized, + NPNProtocols: self.NPNProtocols, + SNICallback: options.SNICallback || SNICallback + }); + + function listener() { + this.socket._tlsError(new Error('TLS handshake timeout')); + } + + // TODO: Look at this closer + // DGRAM - Not like an accept, DGRAM must wait the first packet... + //if (timeout > 0) { + // socket.setTimeout(timeout, listener); + //} + this.socket.once('secure', function () { + //self.socket.setTimeout(0, listener); + + if (self.requestCert) { + var verifyError = socket.ssl.verifyError(); + if (verifyError) { + socket.authorizationError = verifyError.message; + + if (self.rejectUnauthorized) + socket.destroy(); + } else { + socket.authorized = true; + } + } + + if (!self.socket.destroyed) { + self.socket._releaseControl(); + self.emit('secureConnection', self.socket); + } + }); + + this.socket.on('_tlsError', function (err) { + if (!socket._controlReleased) + self.emit('clientError', err, socket); + }); + + if (listener) { + this.on('secureConnection', listener); + } + + // constructor call + dgram.Server.call(this, function () { + debug('after : dgram.Server.call'); + }); +} + + +util.inherits(Server, dgram.Server); +exports.Server = Server; + +exports.Server = function() { + debug('exports.Server'); + return new Server(arguments[0], arguments[1]); +}; + +exports.createServer = function(options, listener) { + debug('exports.createServer'); + return new Server(options, listener); +}; + + +Server.prototype._getServerData = function() { + return { + ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex') + }; +}; + + +Server.prototype._setServerData = function(data) { + this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex')); +}; + + +Server.prototype.setOptions = function(options) { + if (util.isBoolean(options.requestCert)) { + this.requestCert = options.requestCert; + } else { + this.requestCert = false; + } + + if (util.isBoolean(options.rejectUnauthorized)) { + this.rejectUnauthorized = options.rejectUnauthorized; + } else { + this.rejectUnauthorized = false; + } + + if (options.pfx) this.pfx = options.pfx; + if (options.key) this.key = options.key; + if (options.passphrase) this.passphrase = options.passphrase; + if (options.cert) this.cert = options.cert; + if (options.ca) this.ca = options.ca; + if (options.secureProtocol) this.secureProtocol = options.secureProtocol; + if (options.crl) this.crl = options.crl; + if (options.ciphers) this.ciphers = options.ciphers; + if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout; + var secureOptions = options.secureOptions || 0; + if (options.honorCipherOrder) { + secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE; + } + if (secureOptions) this.secureOptions = secureOptions; + if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this); + if (options.sessionIdContext) { + this.sessionIdContext = options.sessionIdContext; + } else if (this.requestCert) { + this.sessionIdContext = crypto.createHash('md5') + .update(process.argv.join(' ')) + .digest('hex'); + } +}; + +// SNI Contexts High-Level API +Server.prototype.addContext = function(servername, credentials) { + if (!servername) { + throw 'Servername is required parameter for Server.addContext'; + } + + var re = new RegExp('^' + + servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1') + .replace(/\*/g, '.*') + + '$'); + this._contexts.push([re, crypto.createCredentials(credentials).context]); +}; + +function SNICallback(servername, callback) { + var ctx; + + this.server._contexts.some(function(elem) { + if (!util.isNull(servername.match(elem[0]))) { + ctx = elem[1]; + return true; + } + }); + + callback(null, ctx); +} + + +// Target API: +// +// var s = tls.connect({port: 8000, host: "google.com"}, function() { +// if (!s.authorized) { +// s.destroy(); +// return; +// } +// +// // s.socket; +// +// s.end("hello world\n"); +// }); +// +// +function normalizeConnectArgs(listArgs) { + + throw 'Not yet implemented'; + + var args = undefined; //net._normalizeConnectArgs(listArgs); + var options = args[0]; + var cb = args[1]; + + if (util.isObject(listArgs[1])) { + options = util._extend(options, listArgs[1]); + } else if (util.isObject(listArgs[2])) { + options = util._extend(options, listArgs[2]); + } + + return (cb) ? [options, cb] : [options]; +} + +exports.connect = function(/* [port, host], options, cb */) { + var args = normalizeConnectArgs(arguments); + var options = args[0]; + var cb = args[1]; + + var defaults = { + rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED + }; + options = util._extend(defaults, options || {}); + + var hostname = options.servername || options.host || 'localhost', + NPN = {}; + tls.convertNPNProtocols(options.NPNProtocols, NPN); + + var socket = new DTLSSocket(options.socket, { + credentials: crypto.createCredentials(options), + isServer: false, + requestCert: true, + rejectUnauthorized: options.rejectUnauthorized, + NPNProtocols: NPN.NPNProtocols + }); + + function onHandle() { + + socket._releaseControl(); + + if (options.session) + socket.setSession(options.session); + + if (options.servername) + socket.setServername(options.servername); + + socket._start(); + socket.on('secure', function() { + var verifyError = socket.ssl.verifyError(); + + // Verify that server's identity matches it's certificate's names + if (!verifyError) { + var validCert = tls.checkServerIdentity(hostname, + socket.getPeerCertificate()); + if (!validCert) { + verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' + + 'altnames'); + } + } + + if (verifyError) { + socket.authorizationError = verifyError.message; + + if (options.rejectUnauthorized) { + socket.emit('error', verifyError); + socket.destroy(); + return; + } else { + socket.emit('secureConnect'); + } + } else { + socket.authorized = true; + socket.emit('secureConnect'); + } + + // Uncork incoming data + socket.removeListener('end', onHangUp); + }); + + function onHangUp() { + // NOTE: This logic is shared with _http_client.js + if (!socket._hadError) { + socket._hadError = true; + var error = new Error('socket hang up'); + error.code = 'ECONNRESET'; + socket.destroy(); + socket.emit('error', error); + } + } + socket.once('end', onHangUp); + } + if (socket._handle) + onHandle(); + else + socket.once('bind', onHandle); + + if (cb) + socket.once('secureConnect', cb); + + if (!options.socket) { + var connect_opt = (options.path && !options.port) ? {path: options.path} : { + port: options.port, + host: options.host, + localAddress: options.localAddress + }; + socket.connect(connect_opt); + } + + return socket; +}; diff --git a/lib/_tls_common.js b/lib/_tls_common.js new file mode 100644 index 00000000000..5f95145e624 --- /dev/null +++ b/lib/_tls_common.js @@ -0,0 +1,205 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +var net = require('net'); +var url = require('url'); +var util = require('util'); + +exports.DEFAULT_ECDH_CURVE = 'prime256v1'; + +// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations +// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more +// renegotations are seen. The settings are applied to all remote client +// connections. +exports.CLIENT_RENEG_LIMIT = 3; +exports.CLIENT_RENEG_WINDOW = 600; + +exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024; + +exports.getCiphers = function() { + var names = process.binding('crypto').getSSLCiphers(); + // Drop all-caps names in favor of their lowercase aliases, + var ctx = {}; + names.forEach(function(name) { + if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase(); + ctx[name] = true; + }); + return Object.getOwnPropertyNames(ctx).sort(); +}; + +// Convert protocols array into valid OpenSSL protocols list +// ("\x06spdy/2\x08http/1.1\x08http/1.0") +exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) { + // If NPNProtocols is Array - translate it into buffer + if (util.isArray(NPNProtocols)) { + var buff = new Buffer(NPNProtocols.reduce(function(p, c) { + return p + 1 + Buffer.byteLength(c); + }, 0)); + + NPNProtocols.reduce(function(offset, c) { + var clen = Buffer.byteLength(c); + buff[offset] = clen; + buff.write(c, offset + 1); + + return offset + 1 + clen; + }, 0); + + NPNProtocols = buff; + } + + // If it's already a Buffer - store it + if (util.isBuffer(NPNProtocols)) { + out.NPNProtocols = NPNProtocols; + } +}; + +exports.checkServerIdentity = function checkServerIdentity(host, cert) { + // Create regexp to much hostnames + function regexpify(host, wildcards) { + // Add trailing dot (make hostnames uniform) + if (!/\.$/.test(host)) host += '.'; + + // The same applies to hostname with more than one wildcard, + // if hostname has wildcard when wildcards are not allowed, + // or if there are less than two dots after wildcard (i.e. *.com or *d.com) + // + // also + // + // "The client SHOULD NOT attempt to match a presented identifier in + // which the wildcard character comprises a label other than the + // left-most label (e.g., do not match bar.*.example.net)." + // RFC6125 + if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) || + /\*/.test(host) && !/\*.*\..+\..+/.test(host)) { + return /$./; + } + + // Replace wildcard chars with regexp's wildcard and + // escape all characters that have special meaning in regexps + // (i.e. '.', '[', '{', '*', and others) + var re = host.replace( + /\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g, + function(all, sub) { + if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub); + return '\\' + all; + }); + + return new RegExp('^' + re + '$', 'i'); + } + + var dnsNames = [], + uriNames = [], + ips = [], + matchCN = true, + valid = false; + + // There're several names to perform check against: + // CN and altnames in certificate extension + // (DNS names, IP addresses, and URIs) + // + // Walk through altnames and generate lists of those names + if (cert.subjectaltname) { + cert.subjectaltname.split(/, /g).forEach(function(altname) { + if (/^DNS:/.test(altname)) { + dnsNames.push(altname.slice(4)); + } else if (/^IP Address:/.test(altname)) { + ips.push(altname.slice(11)); + } else if (/^URI:/.test(altname)) { + var uri = url.parse(altname.slice(4)); + if (uri) uriNames.push(uri.hostname); + } + }); + } + + // If hostname is an IP address, it should be present in the list of IP + // addresses. + if (net.isIP(host)) { + valid = ips.some(function(ip) { + return ip === host; + }); + } else { + // Transform hostname to canonical form + if (!/\.$/.test(host)) host += '.'; + + // Otherwise check all DNS/URI records from certificate + // (with allowed wildcards) + dnsNames = dnsNames.map(function(name) { + return regexpify(name, true); + }); + + // Wildcards ain't allowed in URI names + uriNames = uriNames.map(function(name) { + return regexpify(name, false); + }); + + dnsNames = dnsNames.concat(uriNames); + + if (dnsNames.length > 0) matchCN = false; + + // Match against Common Name (CN) only if no supported identifiers are + // present. + // + // "As noted, a client MUST NOT seek a match for a reference identifier + // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, + // URI-ID, or any application-specific identifier types supported by the + // client." + // RFC6125 + if (matchCN) { + var commonNames = cert.subject.CN; + if (util.isArray(commonNames)) { + for (var i = 0, k = commonNames.length; i < k; ++i) { + dnsNames.push(regexpify(commonNames[i], true)); + } + } else { + dnsNames.push(regexpify(commonNames, true)); + } + } + + valid = dnsNames.some(function(re) { + return re.test(host); + }); + } + + return valid; +}; + +// Example: +// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org +exports.parseCertString = function parseCertString(s) { + var out = {}; + var parts = s.split('\n'); + for (var i = 0, len = parts.length; i < len; i++) { + var sepIndex = parts[i].indexOf('='); + if (sepIndex > 0) { + var key = parts[i].slice(0, sepIndex); + var value = parts[i].slice(sepIndex + 1); + if (key in out) { + if (!util.isArray(out[key])) { + out[key] = [out[key]]; + } + out[key].push(value); + } else { + out[key] = value; + } + } + } + return out; +}; diff --git a/lib/dgram.js b/lib/dgram.js index f31fd7158cf..94b38d7535e 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -23,6 +23,8 @@ var assert = require('assert'); var util = require('util'); var events = require('events'); +var debug = util.debuglog('dgram'); + var UDP = process.binding('udp_wrap').UDP; var BIND_STATE_UNBOUND = 0; @@ -52,8 +54,8 @@ function lookup6(address, callback) { return lookup(address || '::0', 6, callback); } - function newHandle(type) { + if (type == 'udp4') { var handle = new UDP; handle.lookup = lookup4; @@ -74,6 +76,9 @@ function newHandle(type) { throw new Error('Bad socket type specified. Valid types are: udp4, udp6'); } +exports.createServer = function () { + return new Server(arguments[0], arguments[1]); +}; exports._createSocketHandle = function(address, port, addressType, fd) { // Opening an existing fd is not supported for UDP handles. @@ -92,17 +97,45 @@ exports._createSocketHandle = function(address, port, addressType, fd) { return handle; }; +// Returns an array [options] or [options, cb] +// It is the same as the argument of Socket.prototype(). +function normalizeSocketArgs(args) { + var options = {}; -function Socket(type, listener) { + if (util.isString(args[0])) { + options.type = args[0]; + } + else { + options.type = options.type || 'udp4'; + } + + var cb = args[args.length - 1]; + return util.isFunction(cb) ? [options, cb] : [options]; +} +exports._normalizeSocketArgs = normalizeSocketArgs; + +function Socket() { + + // Old API: + // Socket(type, callback) + // New API: + // Socket(option, callback) + var args = normalizeSocketArgs(arguments); + + var options = args[0]; + var listener = args[1]; + events.EventEmitter.call(this); - var handle = newHandle(type); + debug('Socket : ', options.type); + + var handle = newHandle(options.type); handle.owner = this; this._handle = handle; this._receiving = false; this._bindState = BIND_STATE_UNBOUND; - this.type = type; + this.type = options.type; this.fd = null; // compatibility hack if (util.isFunction(listener)) @@ -117,7 +150,7 @@ exports.createSocket = function(type, listener) { }; -function startListening(socket) { +function startListening(socket, context) { socket._handle.onmessage = onMessage; // Todo: handle errors socket._handle.recvStart(); @@ -125,7 +158,7 @@ function startListening(socket) { socket._bindState = BIND_STATE_BOUND; socket.fd = -42; // compatibility hack - socket.emit('listening'); + (context || socket).emit('listening'); } function replaceHandle(self, newHandle) { @@ -457,3 +490,211 @@ Socket.prototype.unref = function() { if (this._handle) this._handle.unref(); }; + +function Server(/* [ options, ] listener */) { + + if (!(this instanceof Server)) + return new Server(arguments[0], arguments[1]); + + events.EventEmitter.call(this); + + var self = this; + + var options; + + debug('dgram:Server'); + + if (util.isFunction(arguments[0])) { + options = {}; + self.on('listening', arguments[0]); + } else { + options = arguments[0] || {}; + + if (util.isFunction(arguments[1])) { + self.on('listening', arguments[1]); + } + } + + this._connections = 0; + + Object.defineProperty(this, 'connections', { + get: util.deprecate(function() { + + if (self._usingSlaves) { + return null; + } + return self._connections; + }, 'connections property is deprecated. Use getConnections() method'), + set: util.deprecate(function(val) { + return (self._connections = val); + }, 'connections property is deprecated. Use getConnections() method'), + configurable: true, enumerable: true + }); + + var type = options.type || 'udp4'; + + if (!this.socket) { + this.socket = {}; + Socket.call(this.socket, { type: type }); + } + + this._usingSlaves = false; // TODO - CHECK THIS + this._slaves = []; // TODO - CHECK THIS + + this.allowHalfOpen = options.allowHalfOpen || false; // TODO - CHECK THIS +} +util.inherits(Server, events.EventEmitter); +exports.Server = Server; + + +Server.prototype._bind2 = function(address, port, addressType, fd) { + debug('_bind2', address, port, addressType); + var self = this; + + // If there is not yet a handle, we need to create one and bind. + // In the case of a server sent via IPC, we don't need to do this. + if (!self._handle) { + debug('_bind2: create a handle'); + var rval = createServerHandle(address, port, addressType, fd); + if (util.isNumber(rval)) { + var error = errnoException(rval, 'bind'); + process.nextTick(function() { + self.emit('error', error); + }); + return; + } + self._handle = rval; + } else { + debug('_bind2: have a handle already'); + } + + self._handle.onconnection = onconnection; + self._handle.owner = self; + + // Use a backlog of 512 entries. We pass 511 to the listen() call because + // the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); + // which will thus give us a backlog of 512 entries. + var err = self._handle.listen(backlog || 511); + + if (err) { + var ex = errnoException(err, 'bind2'); + self._handle.close(); + self._handle = null; + process.nextTick(function() { + self.emit('error', ex); + }); + return; + } + + // generate connection key, this should be unique to the connection + this._connectionKey = addressType + ':' + address + ':' + port; + + process.nextTick(function() { + self.emit('listening'); + }); +}; + + +function bind(self, address, port, addressType, fd) { + if (!cluster) cluster = require('cluster'); + + if (cluster.isMaster) { + self._bind2(address, port, addressType, fd); + return; + } + + cluster._getServer(self, address, port, addressType, fd, cb); + + function cb(err, handle) { + // EADDRINUSE may not be reported until we call listen(). To complicate + // matters, a failed bind() followed by listen() will implicitly bind to + // a random port. Ergo, check that the socket is bound to the expected + // port before calling listen(). + // + // FIXME(bnoordhuis) Doesn't work for pipe handles, they don't have a + // getsockname() method. Non-issue for now, the cluster module doesn't + // really support pipes anyway. + if (err === 0 && port > 0 && handle.getsockname) { + var out = {}; + err = handle.getsockname(out); + if (err === 0 && port !== out.port) + err = uv.UV_EADDRINUSE; + } + + if (err) + return self.emit('error', errnoException(err, 'bind')); + + self._handle = handle; + self._bind2(address, port, addressType, fd); + } +} + + +Server.prototype.bind = function() { + var self = this; + + //self._healthCheck(); + + if (this.socket._bindState != BIND_STATE_UNBOUND) + throw new Error('Socket is already bound'); + + this.socket._bindState = BIND_STATE_BINDING; + + if (util.isFunction(arguments[arguments.length - 1])) + self.once('listening', arguments[arguments.length - 1]); + + var UDP = process.binding('udp_wrap').UDP; + if (arguments[0] instanceof UDP) { + replaceHandle(self, arguments[0]); + startListening(self, ''); + return; + } + + var port = arguments[0]; + var address = arguments[1]; + if (util.isFunction(address)) address = ''; // a.k.a. "any address" + + // resolve address first + self.socket._handle.lookup(address, function(err, ip) { + if (err) { + self.socket._bindState = BIND_STATE_UNBOUND; + self.emit('error', err); + return; + } + + if (!cluster) + cluster = require('cluster'); + + if (cluster.isWorker) { + cluster._getServer(self, ip, port, self.type, -1, function(err, handle) { + if (err) { + self.emit('error', errnoException(err, 'bind')); + self._bindState = BIND_STATE_UNBOUND; + return; + } + + if (!self._handle) + // handle has been closed in the mean time. + return handle.close(); + + replaceHandle(self, handle); + startListening(self); + }); + + } else { + if (!self.socket._handle) + return; // handle has been closed in the mean time + + var err = self.socket._handle.bind(ip, port || 0, /*flags=*/ 0); + if (err) { + self.emit('error', errnoException(err, 'bind')); + self.socket._bindState = BIND_STATE_UNBOUND; + // Todo: close? + return; + } + + startListening(self.socket, self); + } + }); +} + diff --git a/lib/dtls.js b/lib/dtls.js new file mode 100644 index 00000000000..73b8d70fa7b --- /dev/null +++ b/lib/dtls.js @@ -0,0 +1,41 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. +var util = require('util'); +var tls_common = require('_tls_common'); + +for (var attr in tls_common) { + exports[attr] = tls_common[attr]; +} + +exports.DEFAULT_CIPHERS = + 'RC4:HIGH:!MD5:!aNULL:!EDH'; // DTLS 1.0 is based on TLS 1.0 +// 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // DTLS 1.2 is based on TLS 1.2 - NOT YET READY (NEED OpenSSL 1.1.0|) + +// Public API +exports.DTLSSocket = require('_dtls_wrap').DTLSSocket; +exports.Server = require('_dtls_wrap').Server; +exports.createServer = require('_dtls_wrap').createServer; +exports.connect = require('_dtls_wrap').connect; + +// Legacy API +exports.__defineGetter__('createSecurePair', util.deprecate(function() { + return require('_tls_legacy').createSecurePair; +}, 'createSecurePair() is deprecated, use TLSSocket instead')); \ No newline at end of file diff --git a/lib/tls.js b/lib/tls.js index 3004c32d2b4..f4dfe36b79f 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -18,196 +18,19 @@ // 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. - -var net = require('net'); -var url = require('url'); var util = require('util'); +var tls_common = require('_tls_common'); +debugger; + +for (var attr in tls_common) { + exports[attr] = tls_common[attr]; +} + exports.DEFAULT_CIPHERS = 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // TLS 1.2 'RC4:HIGH:!MD5:!aNULL:!EDH'; // TLS 1.0 -exports.DEFAULT_ECDH_CURVE = 'prime256v1'; - -// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations -// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more -// renegotations are seen. The settings are applied to all remote client -// connections. -exports.CLIENT_RENEG_LIMIT = 3; -exports.CLIENT_RENEG_WINDOW = 600; - -exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024; - -exports.getCiphers = function() { - var names = process.binding('crypto').getSSLCiphers(); - // Drop all-caps names in favor of their lowercase aliases, - var ctx = {}; - names.forEach(function(name) { - if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase(); - ctx[name] = true; - }); - return Object.getOwnPropertyNames(ctx).sort(); -}; - -// Convert protocols array into valid OpenSSL protocols list -// ("\x06spdy/2\x08http/1.1\x08http/1.0") -exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) { - // If NPNProtocols is Array - translate it into buffer - if (util.isArray(NPNProtocols)) { - var buff = new Buffer(NPNProtocols.reduce(function(p, c) { - return p + 1 + Buffer.byteLength(c); - }, 0)); - - NPNProtocols.reduce(function(offset, c) { - var clen = Buffer.byteLength(c); - buff[offset] = clen; - buff.write(c, offset + 1); - - return offset + 1 + clen; - }, 0); - - NPNProtocols = buff; - } - - // If it's already a Buffer - store it - if (util.isBuffer(NPNProtocols)) { - out.NPNProtocols = NPNProtocols; - } -}; - -exports.checkServerIdentity = function checkServerIdentity(host, cert) { - // Create regexp to much hostnames - function regexpify(host, wildcards) { - // Add trailing dot (make hostnames uniform) - if (!/\.$/.test(host)) host += '.'; - - // The same applies to hostname with more than one wildcard, - // if hostname has wildcard when wildcards are not allowed, - // or if there are less than two dots after wildcard (i.e. *.com or *d.com) - // - // also - // - // "The client SHOULD NOT attempt to match a presented identifier in - // which the wildcard character comprises a label other than the - // left-most label (e.g., do not match bar.*.example.net)." - // RFC6125 - if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) || - /\*/.test(host) && !/\*.*\..+\..+/.test(host)) { - return /$./; - } - - // Replace wildcard chars with regexp's wildcard and - // escape all characters that have special meaning in regexps - // (i.e. '.', '[', '{', '*', and others) - var re = host.replace( - /\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g, - function(all, sub) { - if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub); - return '\\' + all; - }); - - return new RegExp('^' + re + '$', 'i'); - } - - var dnsNames = [], - uriNames = [], - ips = [], - matchCN = true, - valid = false; - - // There're several names to perform check against: - // CN and altnames in certificate extension - // (DNS names, IP addresses, and URIs) - // - // Walk through altnames and generate lists of those names - if (cert.subjectaltname) { - cert.subjectaltname.split(/, /g).forEach(function(altname) { - if (/^DNS:/.test(altname)) { - dnsNames.push(altname.slice(4)); - } else if (/^IP Address:/.test(altname)) { - ips.push(altname.slice(11)); - } else if (/^URI:/.test(altname)) { - var uri = url.parse(altname.slice(4)); - if (uri) uriNames.push(uri.hostname); - } - }); - } - - // If hostname is an IP address, it should be present in the list of IP - // addresses. - if (net.isIP(host)) { - valid = ips.some(function(ip) { - return ip === host; - }); - } else { - // Transform hostname to canonical form - if (!/\.$/.test(host)) host += '.'; - - // Otherwise check all DNS/URI records from certificate - // (with allowed wildcards) - dnsNames = dnsNames.map(function(name) { - return regexpify(name, true); - }); - - // Wildcards ain't allowed in URI names - uriNames = uriNames.map(function(name) { - return regexpify(name, false); - }); - - dnsNames = dnsNames.concat(uriNames); - - if (dnsNames.length > 0) matchCN = false; - - // Match against Common Name (CN) only if no supported identifiers are - // present. - // - // "As noted, a client MUST NOT seek a match for a reference identifier - // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, - // URI-ID, or any application-specific identifier types supported by the - // client." - // RFC6125 - if (matchCN) { - var commonNames = cert.subject.CN; - if (util.isArray(commonNames)) { - for (var i = 0, k = commonNames.length; i < k; ++i) { - dnsNames.push(regexpify(commonNames[i], true)); - } - } else { - dnsNames.push(regexpify(commonNames, true)); - } - } - - valid = dnsNames.some(function(re) { - return re.test(host); - }); - } - - return valid; -}; - -// Example: -// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org -exports.parseCertString = function parseCertString(s) { - var out = {}; - var parts = s.split('\n'); - for (var i = 0, len = parts.length; i < len; i++) { - var sepIndex = parts[i].indexOf('='); - if (sepIndex > 0) { - var key = parts[i].slice(0, sepIndex); - var value = parts[i].slice(sepIndex + 1); - if (key in out) { - if (!util.isArray(out[key])) { - out[key] = [out[key]]; - } - out[key].push(value); - } else { - out[key] = value; - } - } - } - return out; -}; - // Public API exports.TLSSocket = require('_tls_wrap').TLSSocket; exports.Server = require('_tls_wrap').Server; @@ -217,4 +40,4 @@ exports.connect = require('_tls_wrap').connect; // Legacy API exports.__defineGetter__('createSecurePair', util.deprecate(function() { return require('_tls_legacy').createSecurePair; -}, 'createSecurePair() is deprecated, use TLSSocket instead')); +}, 'createSecurePair() is deprecated, use TLSSocket instead')); \ No newline at end of file diff --git a/node.gyp b/node.gyp index 0b5ea215b45..a8b262df902 100644 --- a/node.gyp +++ b/node.gyp @@ -30,6 +30,8 @@ 'lib/dgram.js', 'lib/dns.js', 'lib/domain.js', + #'lib/dtls.js', + 'lib/_dtls_wrap.js', 'lib/events.js', 'lib/freelist.js', 'lib/fs.js', @@ -60,6 +62,7 @@ 'lib/sys.js', 'lib/timers.js', 'lib/tls.js', + 'lib/_tls_common.js', 'lib/_tls_legacy.js', 'lib/_tls_wrap.js', 'lib/tty.js', diff --git a/src/callback_wrap.h b/src/callback_wrap.h new file mode 100644 index 00000000000..fab818f3707 --- /dev/null +++ b/src/callback_wrap.h @@ -0,0 +1,94 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +#ifndef SRC_CALLBACK_WRAP_H_ +#define SRC_CALLBACK_WRAP_H_ + +#include "v8.h" +#include "node.h" +#include "handle_wrap.h" +#include "req_wrap.h" +#include "string_bytes.h" + + +namespace node { + +typedef class ReqWrap ShutdownWrap; + +class SendWrap; +class WriteWrap; + + +// Overridable callbacks' types +class WrapCallbacks { + + public: + typedef int(WrapCallbacks::*DoShutdownCallback)(ShutdownWrap* req_wrap, uv_shutdown_cb cb); + + explicit WrapCallbacks(HandleWrap* wrap) : wrap_(wrap) { + oldCallback_ = NULL; + } + + explicit WrapCallbacks(WrapCallbacks* old) : wrap_(old->wrap()) { + oldCallback_ = old; + } + + virtual ~WrapCallbacks() { + } + + virtual v8::Handle Self() = 0; + + // Common + virtual void DoAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) = 0; + virtual int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { + if (oldCallback_) { + return oldCallback_->DoShutdown(req_wrap, cb); + } + + return 0; + }; + + // UDP + virtual void DoRecv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) {}; + virtual int DoSend(SendWrap* s, uv_udp_t* handle, uv_buf_t* buf, size_t count, const struct sockaddr* addr, uv_udp_send_cb cb) { return -1; }; + + // Stream + virtual void DoRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf, uv_handle_type pending) { /* throw .... */ }; + virtual int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count, uv_stream_t* send_handle, uv_write_cb cb) { return -1; }; + virtual void AfterWrite(WriteWrap* w) {}; + + + protected: + inline HandleWrap* wrap() const { + return wrap_; + } + + HandleWrap* const wrap_; + + private: + WrapCallbacks* oldCallback_; +}; + + +} // namespace node + + +#endif // SRC_STREAM_WRAP_H_ diff --git a/src/env.h b/src/env.h index 5cf414b5dc1..5c5036d183a 100644 --- a/src/env.h +++ b/src/env.h @@ -153,7 +153,7 @@ namespace node { V(tick_callback_function, v8::Function) \ V(tls_wrap_constructor_function, v8::Function) \ V(tty_constructor_template, v8::FunctionTemplate) \ - V(udp_constructor_function, v8::Function) \ + V(udp_constructor_template, v8::FunctionTemplate) \ class Environment; diff --git a/src/node.cc b/src/node.cc index 7f8528c479b..a0206cadc44 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2164,6 +2164,9 @@ static Handle GetFeatures() { obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "tls"), Boolean::New(get_builtin_module("crypto") != NULL)); + obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "dtls"), + Boolean::New(get_builtin_module("crypto") != NULL)); + return scope.Close(obj); } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index d1467527e4b..6deeacefdc3 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -313,6 +313,10 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { method = TLSv1_2_server_method(); } else if (strcmp(*sslmethod, "TLSv1_2_client_method") == 0) { method = TLSv1_2_client_method(); + } else if (strcmp(*sslmethod, "DTLSv1_server_method") == 0) { + method = DTLSv1_server_method(); + } else if (strcmp(*sslmethod, "DTLSv1_client_method") == 0) { + method = DTLSv1_client_method(); } else { return ThrowError("Unknown method"); } diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index 3b361f7a059..30cb6e33548 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -25,6 +25,7 @@ #include "handle_wrap.h" #include "node_buffer.h" #include "node_counters.h" +#include "callback_wrap.h" #include "pipe_wrap.h" #include "req_wrap.h" #include "tcp_wrap.h" @@ -466,7 +467,7 @@ void StreamWrap::Shutdown(const FunctionCallbackInfo& args) { Local req_wrap_obj = args[0].As(); ShutdownWrap* req_wrap = new ShutdownWrap(env, req_wrap_obj); - int err = wrap->callbacks()->DoShutdown(req_wrap, AfterShutdown); + int err = wrap->callbacks_->DoShutdown(req_wrap, AfterShutdown); req_wrap->Dispatched(); if (err) delete req_wrap; @@ -601,4 +602,8 @@ int StreamWrapCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { return uv_shutdown(&req_wrap->req_, wrap()->stream(), cb); } +Handle StreamWrapCallbacks::Self() { + return wrap()->object(); +} + } // namespace node diff --git a/src/stream_wrap.h b/src/stream_wrap.h index ed8c53ebe87..c2f1f47a5bd 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -24,6 +24,7 @@ #include "env.h" #include "handle_wrap.h" +#include "callback_wrap.h" #include "req_wrap.h" #include "string_bytes.h" #include "v8.h" @@ -62,12 +63,12 @@ class WriteWrap: public ReqWrap { }; // Overridable callbacks' types -class StreamWrapCallbacks { +class StreamWrapCallbacks : public WrapCallbacks { public: - explicit StreamWrapCallbacks(StreamWrap* wrap) : wrap_(wrap) { + explicit StreamWrapCallbacks(StreamWrap* wrap) : WrapCallbacks(reinterpret_cast(wrap)) { } - explicit StreamWrapCallbacks(StreamWrapCallbacks* old) : wrap_(old->wrap()) { + explicit StreamWrapCallbacks(StreamWrapCallbacks* old) : WrapCallbacks(old) { } virtual ~StreamWrapCallbacks() { @@ -88,20 +89,19 @@ class StreamWrapCallbacks { uv_handle_type pending); virtual int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); + v8::Handle Self(); + protected: inline StreamWrap* wrap() const { - return wrap_; + return (StreamWrap*) wrap_; } - - private: - StreamWrap* const wrap_; }; class StreamWrap : public HandleWrap { public: - void OverrideCallbacks(StreamWrapCallbacks* callbacks) { + void OverrideCallbacks(WrapCallbacks* callbacks) { StreamWrapCallbacks* old = callbacks_; - callbacks_ = callbacks; + callbacks_ = reinterpret_cast(callbacks); if (old != &default_callbacks_) delete old; } diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 03ccee4b679..0b1848a598f 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -61,9 +61,10 @@ static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL TLSCallbacks::TLSCallbacks(Environment* env, Kind kind, Handle sc, - StreamWrapCallbacks* old) + WrapCallbacks* old, + bool isstream) : SSLWrap(env, Unwrap(sc), kind), - StreamWrapCallbacks(old), + WrapCallbacks(old), AsyncWrap(env, env->tls_wrap_constructor_function()->NewInstance()), sc_(Unwrap(sc)), sc_handle_(env->isolate(), sc), @@ -75,11 +76,13 @@ TLSCallbacks::TLSCallbacks(Environment* env, started_(false), established_(false), shutdown_(false), - eof_(false) { + isstream_(isstream) +{ node::Wrap(object(), this); // Initialize queue for clearIn writes QUEUE_INIT(&write_item_queue_); + QUEUE_INIT(&send_item_queue_); // We've our own session callbacks SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap::GetSessionCallback); @@ -182,10 +185,35 @@ void TLSCallbacks::Wrap(const FunctionCallbackInfo& args) { SSLWrap::kClient; TLSCallbacks* callbacks = NULL; + + if (env->tcp_constructor_template().IsEmpty() == false && + env->tcp_constructor_template()->HasInstance(stream)) { + TCPWrap* const wrap = Unwrap(stream); + callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks(), true); + wrap->OverrideCallbacks(callbacks); + } else if (env->udp_constructor_template().IsEmpty() == false && + env->udp_constructor_template()->HasInstance(stream)) { + UDPWrap* const wrap = Unwrap(stream); + callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks(), false); + wrap->OverrideCallbacks(callbacks); + } else if (env->tty_constructor_template().IsEmpty() == false && + env->tty_constructor_template()->HasInstance(stream)) { + TTYWrap* const wrap = Unwrap(stream); + callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks(), true); + wrap->OverrideCallbacks(callbacks); + } else if (env->pipe_constructor_template().IsEmpty() == false && + env->pipe_constructor_template()->HasInstance(stream)) { + PipeWrap* const wrap = Unwrap(stream); + callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks(), true); + wrap->OverrideCallbacks(callbacks); + } + + /* WITH_GENERIC_STREAM(env, stream, { callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks()); wrap->OverrideCallbacks(callbacks); }); + */ if (callbacks == NULL) { return args.GetReturnValue().SetNull(); @@ -241,7 +269,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { } -void TLSCallbacks::EncOut() { +void TLSCallbacks::EncOut(const ExtraInfo * extraInfo) { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return; @@ -264,31 +292,61 @@ void TLSCallbacks::EncOut() { return; } - char* data[kSimultaneousBufferCount]; - size_t size[ARRAY_SIZE(data)]; - size_t count = ARRAY_SIZE(data); - write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count); - assert(write_size_ != 0 && count != 0); - - write_req_.data = this; - uv_buf_t buf[ARRAY_SIZE(data)]; - for (size_t i = 0; i < count; i++) - buf[i] = uv_buf_init(data[i], size[i]); - int r = uv_write(&write_req_, wrap()->stream(), buf, count, EncOutCb); - - // Ignore errors, this should be already handled in js - if (!r) { - if (wrap()->is_tcp()) { - NODE_COUNT_NET_BYTES_SENT(write_size_); - } else if (wrap()->is_named_pipe()) { - NODE_COUNT_PIPE_BYTES_SENT(write_size_); - } + char* data = NodeBIO::FromBIO(enc_out_)->Peek(&write_size_); + assert(write_size_ != 0); + + uv_buf_t buf = uv_buf_init(data, write_size_); + + + //UDPWrap * udpWrap = reinterpret_cast(wrap()); + + // Check for DTLS + // TODO: Check if it exists a better way rather + // than putting a flag, direct to the SSL context + //if (sc_->ctx_->method == ) ??? + if (isstream_) { + + write_req_.data = this; + + StreamWrap * streamWrap = reinterpret_cast(wrap()); + int r = uv_write(&write_req_, streamWrap->stream(), &buf, 1, EncOutStreamCb); + + // Ignore errors, this should be already handled in js + if (!r) { + if (streamWrap->is_tcp()) { + NODE_COUNT_NET_BYTES_SENT(write_size_); + } else if (streamWrap->is_named_pipe()) { + NODE_COUNT_PIPE_BYTES_SENT(write_size_); + } + } } + else { + UDPWrap * udpWrap = reinterpret_cast(wrap()); + send_req_.data = this; + + int r = uv_udp_send(&send_req_, + udpWrap->UVHandle(), + &buf, + 1, + reinterpret_cast(extraInfo->addr), + EncOutHandleCb); + + if (!r) { + //NODE_COUNT_UDP_BYTES_SENT(write_size_); + } + } +} + + +void TLSCallbacks::EncOutStreamCb(uv_write_t* req, int status) { + EncOutCb(static_cast(req->data), status); } +void TLSCallbacks::EncOutHandleCb(uv_udp_send_t* req, int status) { + EncOutCb(static_cast(req->data), status); +} -void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { - TLSCallbacks* callbacks = static_cast(req->data); +void TLSCallbacks::EncOutCb(TLSCallbacks* callbacks, int status) { Environment* env = callbacks->env(); // Handle error @@ -309,7 +367,9 @@ void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { } // Commit - NodeBIO::FromBIO(callbacks->enc_out_)->Read(NULL, callbacks->write_size_); + if (callbacks->enc_out_) { + NodeBIO::FromBIO(callbacks->enc_out_)->Read(NULL, callbacks->write_size_); + } // Try writing more data callbacks->write_size_ = 0; @@ -352,7 +412,7 @@ Local TLSCallbacks::GetSSLError(int status, int* err) { } -void TLSCallbacks::ClearOut() { +void TLSCallbacks::ClearOut(const ExtraInfo * extraInfo) { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return; @@ -366,22 +426,35 @@ void TLSCallbacks::ClearOut() { int read; do { read = SSL_read(ssl_, out, sizeof(out)); + + // + // Depending of a stream or a dgram (invoke onread or onmessage) + // if (read > 0) { - Local argv[] = { - Integer::New(read, node_isolate), - Buffer::New(env(), out, read) - }; - wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv); + + if (extraInfo) { + Local wrap_obj = wrap()->object(); + Local argv[] = { + Integer::New(read, node_isolate), + wrap_obj, + Undefined(env()->isolate()), + Undefined(env()->isolate()) + }; + + argv[2] = Buffer::New(env(), out, read); + argv[3] = AddressToJS(env(), extraInfo->addr); + wrap()->MakeCallback(env()->onmessage_string(), ARRAY_SIZE(argv), argv); + } + else { + Local argv[] = { + Integer::New(read, node_isolate), + Buffer::New(env(), out, read) + }; + wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv); + } } } while (read > 0); - int flags = SSL_get_shutdown(ssl_); - if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { - eof_ = true; - Local arg = Integer::New(UV_EOF, node_isolate); - wrap()->MakeCallback(env()->onread_string(), 1, &arg); - } - if (read == -1) { int err; Handle arg = GetSSLError(read, &err); @@ -392,8 +465,13 @@ void TLSCallbacks::ClearOut() { } } +Handle TLSCallbacks::Self() { + wrap()->GetHandle()->type; + return Handle(); +} + -bool TLSCallbacks::ClearIn() { +bool TLSCallbacks::ClearIn(const ExtraInfo * extraInfo) { // Ignore cycling data if ClientHello wasn't yet parsed if (!hello_parser_.IsEnded()) return false; @@ -452,7 +530,7 @@ int TLSCallbacks::DoWrite(WriteWrap* w, // However if there any data that should be written to socket, // callback should not be invoked immediately if (BIO_pending(enc_out_) == 0) - return uv_write(&w->req_, wrap()->stream(), bufs, count, cb); + return uv_write(&w->req_, reinterpret_cast(wrap())->stream(), bufs, count, cb); } QUEUE_INSERT_TAIL(&write_item_queue_, &wi->member_); @@ -513,7 +591,6 @@ void TLSCallbacks::DoAlloc(uv_handle_t* handle, buf->len = suggested_size; } - void TLSCallbacks::DoRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf, @@ -521,14 +598,6 @@ void TLSCallbacks::DoRead(uv_stream_t* handle, if (nread < 0) { // Error should be emitted only after all data was read ClearOut(); - - // Ignore EOF if received close_notify - if (nread == UV_EOF) { - if (eof_) - return; - eof_ = true; - } - HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); Local arg = Integer::New(nread, node_isolate); @@ -556,12 +625,140 @@ void TLSCallbacks::DoRead(uv_stream_t* handle, } +void TLSCallbacks::DoRecv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) +{ + if (nread < 0) { + // Error should be emitted only after all data was read + ClearOut(); + HandleScope handle_scope(env()->isolate()); + Context::Scope context_scope(env()->context()); + + Local wrap_obj = wrap()->object(); + Local argv[] = { + Integer::New(nread, node_isolate), + wrap_obj, + Undefined(env()->isolate()), + Undefined(env()->isolate()) + }; + + + if (nread < 0) { + if (buf->base != NULL) + free(buf->base); + wrap()->MakeCallback(env()->onmessage_string(), ARRAY_SIZE(argv), argv); + return; + } + + char* base = static_cast(realloc(buf->base, nread)); + argv[2] = Buffer::Use(env(), base, nread); + argv[3] = AddressToJS(env(), addr); + wrap()->MakeCallback(env()->onmessage_string(), ARRAY_SIZE(argv), argv); + return; + } + + // Only client connections can receive data + assert(ssl_ != NULL); + + // Commit read data + NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_); + enc_in->Commit(nread); + + // Parse ClientHello first + if (!hello_parser_.IsEnded()) { + size_t avail = 0; + uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); + assert(avail == 0 || data != NULL); + return hello_parser_.Parse(data, avail); + } + + ExtraInfo extraInfo = { addr }; + + // Cycle OpenSSL's state + Cycle(&extraInfo); +} + +int TLSCallbacks::DoSend(SendWrap* s, uv_udp_t* handle, uv_buf_t* bufs, size_t count, const struct sockaddr* addr, uv_udp_send_cb cb) +{ + ExtraInfo extraInfo = { addr }; + + // Queue callback to execute it on next tick + SendItem* si = new SendItem(s, cb); + bool empty = true; + UDPWrap * udpWrap = NULL; + + // Empty writes should not go through encryption process + size_t i; + for (i = 0; i < count; i++) + if (bufs[i].len > 0) { + empty = false; + break; + } + if (empty) { + ClearOut(&extraInfo); + // However if there any data that should be written to socket, + // callback should not be invoked immediately + if (BIO_pending(enc_out_) == 0) + udpWrap = reinterpret_cast(wrap()); + return uv_udp_send(NULL, + udpWrap->UVHandle(), + bufs, + count, + addr, + cb); + } + + QUEUE_INSERT_TAIL(&write_item_queue_, &si->member_); + + // Write queued data + if (empty) { + EncOut(&extraInfo); + return 0; + } + + // Process enqueued data first + if (!ClearIn(&extraInfo)) { + // If there're still data to process - enqueue current one + for (i = 0; i < count; i++) + clear_in_->Write(bufs[i].base, bufs[i].len); + return 0; + } + + int written = 0; + for (i = 0; i < count; i++) { + written = SSL_write(ssl_, bufs[i].base, bufs[i].len); + assert(written == -1 || written == static_cast(bufs[i].len)); + if (written == -1) + break; + } + + if (i != count) { + int err; + HandleScope handle_scope(env()->isolate()); + Context::Scope context_scope(env()->context()); + Handle arg = GetSSLError(written, &err); + if (!arg.IsEmpty()) { + MakeCallback(env()->onerror_string(), 1, &arg); + return -1; + } + + // No errors, queue rest + for (; i < count; i++) + clear_in_->Write(bufs[i].base, bufs[i].len); + } + + // Try writing data immediately + EncOut(&extraInfo); + + return 0; +}; + int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { if (SSL_shutdown(ssl_) == 0) SSL_shutdown(ssl_); shutdown_ = true; - EncOut(); - return StreamWrapCallbacks::DoShutdown(req_wrap, cb); + EncOut(); + + return WrapCallbacks::DoShutdown(req_wrap, cb); } diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 2b10e09bca3..5b5a010d34a 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -28,7 +28,9 @@ #include "async-wrap.h" #include "env.h" #include "queue.h" +#include "callback_wrap.h" #include "stream_wrap.h" +#include "udp_wrap.h" #include "v8.h" #include @@ -37,13 +39,14 @@ namespace node { // Forward-declarations class NodeBIO; +class SendWrap; class WriteWrap; namespace crypto { class SecureContext; } class TLSCallbacks : public crypto::SSLWrap, - public StreamWrapCallbacks, + public WrapCallbacks, public AsyncWrap { public: static void Initialize(v8::Handle target, @@ -59,18 +62,28 @@ class TLSCallbacks : public crypto::SSLWrap, void DoAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); + void DoRecv(uv_udp_t* handle, + ssize_t nread, + const uv_buf_t* buf, + const struct sockaddr* addr, + unsigned int flags); + int DoSend(SendWrap* s, + uv_udp_t* handle, + uv_buf_t* bufs, + size_t count, + const struct sockaddr* addr, + uv_udp_send_cb cb); void DoRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf, uv_handle_type pending); int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); +v8::Handle Self(); + protected: static const int kClearOutChunkSize = 1024; - // Maximum number of buffers passed to uv_write() - static const int kSimultaneousBufferCount = 10; - // Write callback queue's item class WriteItem { public: @@ -86,24 +99,48 @@ class TLSCallbacks : public crypto::SSLWrap, QUEUE member_; }; + class SendItem { + public: + SendItem(SendWrap* s, uv_udp_send_cb cb) : s_(s), cb_(cb) { + } + ~SendItem() { + s_ = NULL; + cb_ = NULL; + } + + SendWrap* s_; + uv_udp_send_cb cb_; + QUEUE member_; + }; + + union ExtraInfo { + struct { + const struct sockaddr* addr; + }; + }; + TLSCallbacks(Environment* env, Kind kind, v8::Handle sc, - StreamWrapCallbacks* old); + WrapCallbacks* old, bool isstream); ~TLSCallbacks(); static void SSLInfoCallback(const SSL* ssl_, int where, int ret); void InitSSL(); - void EncOut(); - static void EncOutCb(uv_write_t* req, int status); - bool ClearIn(); - void ClearOut(); + void EncOut(const ExtraInfo * extraInfo = NULL); + + static void EncOutStreamCb(uv_write_t* req, int status); + static void EncOutHandleCb(uv_udp_send_t* req, int status); + static void EncOutCb(TLSCallbacks* callbacks, int status); + + bool ClearIn(const ExtraInfo * extraInfo = NULL); + void ClearOut(const ExtraInfo * extraInfo = NULL); void InvokeQueued(int status); - inline void Cycle() { - ClearIn(); - ClearOut(); - EncOut(); + inline void Cycle(const ExtraInfo * extraInfo = NULL) { + ClearIn(extraInfo); + ClearOut(extraInfo); + EncOut(extraInfo); } v8::Local GetSSLError(int status, int* err); @@ -129,17 +166,16 @@ class TLSCallbacks : public crypto::SSLWrap, BIO* enc_out_; NodeBIO* clear_in_; uv_write_t write_req_; + uv_udp_send_t send_req_; size_t write_size_; size_t write_queue_size_; + QUEUE send_item_queue_; QUEUE write_item_queue_; WriteItem* pending_write_item_; bool started_; bool established_; bool shutdown_; - - // If true - delivered EOF to the js-land, either after `close_notify`, or - // after the `UV_EOF` on socket. - bool eof_; + bool isstream_; #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB v8::Persistent sni_context_; diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index a563a7eccbf..b842126160e 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -73,13 +73,20 @@ inline bool SendWrap::have_callback() const { UDPWrap::UDPWrap(Environment* env, Handle object) - : HandleWrap(env, object, reinterpret_cast(&handle_)) { + : HandleWrap(env, object, reinterpret_cast(&handle_)), + default_callbacks_(this), + callbacks_(&default_callbacks_) { + int r = uv_udp_init(env->event_loop(), &handle_); assert(r == 0); // can't fail anyway } UDPWrap::~UDPWrap() { + if (callbacks_ != &default_callbacks_) { + delete callbacks_; + callbacks_ = NULL; + } } @@ -102,12 +109,14 @@ void UDPWrap::Initialize(Handle target, attributes); NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind); - NODE_SET_PROTOTYPE_METHOD(t, "send", Send); NODE_SET_PROTOTYPE_METHOD(t, "bind6", Bind6); - NODE_SET_PROTOTYPE_METHOD(t, "send6", Send6); - NODE_SET_PROTOTYPE_METHOD(t, "close", Close); - NODE_SET_PROTOTYPE_METHOD(t, "recvStart", RecvStart); - NODE_SET_PROTOTYPE_METHOD(t, "recvStop", RecvStop); + NODE_SET_PROTOTYPE_METHOD(t, "close", HandleWrap::Close); + + NODE_SET_PROTOTYPE_METHOD(t, "send", UDPWrap::Send); + NODE_SET_PROTOTYPE_METHOD(t, "send6", UDPWrap::Send6); + NODE_SET_PROTOTYPE_METHOD(t, "recvStart", UDPWrap::RecvStart); + NODE_SET_PROTOTYPE_METHOD(t, "recvStop", UDPWrap::RecvStop); + NODE_SET_PROTOTYPE_METHOD(t, "getsockname", GetSockName); NODE_SET_PROTOTYPE_METHOD(t, "addMembership", AddMembership); NODE_SET_PROTOTYPE_METHOD(t, "dropMembership", DropMembership); @@ -123,7 +132,7 @@ void UDPWrap::Initialize(Handle target, AsyncWrap::AddMethods(t); target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "UDP"), t->GetFunction()); - env->set_udp_constructor_function(t->GetFunction()); + env->set_udp_constructor_template(t); } @@ -242,7 +251,7 @@ void UDPWrap::DropMembership(const FunctionCallbackInfo& args) { } -void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { +void UDPWrap::ProceedSend(const FunctionCallbackInfo& args, int family) { HandleScope handle_scope(args.GetIsolate()); Environment* env = Environment::GetCurrent(args.GetIsolate()); @@ -268,7 +277,7 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { assert(offset < Buffer::Length(buffer_obj)); assert(length <= Buffer::Length(buffer_obj) - offset); - SendWrap* req_wrap = new SendWrap(env, req_wrap_obj, have_callback); + SendWrap* send_wrap = new SendWrap(env, req_wrap_obj, have_callback); uv_buf_t buf = uv_buf_init(Buffer::Data(buffer_obj) + offset, length); @@ -288,29 +297,24 @@ void UDPWrap::DoSend(const FunctionCallbackInfo& args, int family) { } if (err == 0) { - err = uv_udp_send(&req_wrap->req_, - &wrap->handle_, - &buf, - 1, - reinterpret_cast(&addr), - OnSend); + err = wrap->callbacks()->DoSend(send_wrap, &wrap->handle_, &buf, 1, reinterpret_cast(&addr), UDPWrap::OnSend); } - req_wrap->Dispatched(); + send_wrap->Dispatched(); if (err) - delete req_wrap; + delete send_wrap; args.GetReturnValue().Set(err); } void UDPWrap::Send(const FunctionCallbackInfo& args) { - DoSend(args, AF_INET); + ProceedSend(args, AF_INET); } void UDPWrap::Send6(const FunctionCallbackInfo& args) { - DoSend(args, AF_INET6); + ProceedSend(args, AF_INET6); } @@ -375,14 +379,10 @@ void UDPWrap::OnSend(uv_udp_send_t* req, int status) { void UDPWrap::OnAlloc(uv_handle_t* handle, size_t suggested_size, - uv_buf_t* buf) { - buf->base = static_cast(malloc(suggested_size)); - buf->len = suggested_size; - - if (buf->base == NULL && suggested_size > 0) { - FatalError("node::UDPWrap::OnAlloc(uv_handle_t*, size_t, uv_buf_t*)", - "Out Of Memory"); - } + uv_buf_t* buf) +{ + UDPWrap* wrap = static_cast(handle->data); + wrap->callbacks()->DoAlloc(handle, suggested_size, buf); } @@ -391,51 +391,103 @@ void UDPWrap::OnRecv(uv_udp_t* handle, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) { - if (nread == 0) { - if (buf->base != NULL) - free(buf->base); - return; - } UDPWrap* wrap = static_cast(handle->data); - Environment* env = wrap->env(); - - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - Local wrap_obj = wrap->object(); - Local argv[] = { - Integer::New(nread, node_isolate), - wrap_obj, - Undefined(env->isolate()), - Undefined(env->isolate()) - }; - - if (nread < 0) { - if (buf->base != NULL) - free(buf->base); - wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); - return; + + // We should not be getting this callback if someone as already called + // uv_close() on the handle. + assert(wrap->persistent().IsEmpty() == false); + + if (nread > 0) { + //NODE_COUNT_UDP_BYTES_RECV(nread); } - char* base = static_cast(realloc(buf->base, nread)); - argv[2] = Buffer::Use(env, base, nread); - argv[3] = AddressToJS(env, addr); - wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); + wrap->callbacks()->DoRecv(handle, nread, buf, addr, flags); } Local UDPWrap::Instantiate(Environment* env) { - // If this assert fires then Initialize hasn't been called yet. - assert(env->udp_constructor_function().IsEmpty() == false); - return env->udp_constructor_function()->NewInstance(); -} + HandleScope handle_scope(env->isolate()); + + // If this assert fires then Initialize hasn't been called yet. + assert(env->tcp_constructor_template().IsEmpty() == false); + Local constructor = env->tcp_constructor_template()->GetFunction(); + assert(constructor.IsEmpty() == false); + Local instance = constructor->NewInstance(); + assert(instance.IsEmpty() == false); + return handle_scope.Close(instance); +} uv_udp_t* UDPWrap::UVHandle() { return &handle_; } +void UDPWrapCallbacks::DoRecv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) +{ + if (nread == 0) { + if (buf->base != NULL) + free(buf->base); + return; + } + + UDPWrap* wrap = static_cast(handle->data); + Environment* env = wrap->env(); + + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local wrap_obj = wrap->object(); + Local argv[] = { + Integer::New(nread, node_isolate), + wrap_obj, + Undefined(env->isolate()), + Undefined(env->isolate()) + }; + + if (nread < 0) { + if (buf->base != NULL) + free(buf->base); + wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); + return; + } + + char* base = static_cast(realloc(buf->base, nread)); + argv[2] = Buffer::Use(env, base, nread); + argv[3] = AddressToJS(env, addr); + wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); +} + +int UDPWrapCallbacks::DoSend(SendWrap* s, uv_udp_t* handle, uv_buf_t* buf, size_t count, const struct sockaddr* addr, uv_udp_send_cb cb) +{ + return uv_udp_send(&s->req_, + handle, + buf, + count, + addr, + cb); +} + +void UDPWrapCallbacks::DoAlloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + buf->base = static_cast(malloc(suggested_size)); + buf->len = suggested_size; + + if (buf->base == NULL && suggested_size > 0) { + FatalError( + "node::UDPWrapCallbacks::DoAlloc(uv_handle_t*, size_t, uv_buf_t*)", + "Out Of Memory"); + } +} + +int UDPWrapCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { + return 0; //uv_shutdown(&req_wrap->req_, wrap()->stream(), cb); +} + +Handle UDPWrapCallbacks::Self() { + return wrap()->object(); +} } // namespace node diff --git a/src/udp_wrap.h b/src/udp_wrap.h index ab0d6fa8e69..05664d1da5a 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -25,11 +25,42 @@ #include "env.h" #include "handle_wrap.h" #include "req_wrap.h" +#include "callback_wrap.h" #include "uv.h" #include "v8.h" namespace node { +// Forward declaration +class UDPWrap; + +class UDPWrapCallbacks : public WrapCallbacks { + public: + explicit UDPWrapCallbacks(UDPWrap* wrap) : WrapCallbacks(reinterpret_cast(wrap)) { + } + + explicit UDPWrapCallbacks(UDPWrapCallbacks* old) : WrapCallbacks(old) { + } + + virtual ~UDPWrapCallbacks() { + } + + virtual void DoAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); + + virtual void DoRecv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags); + virtual int DoSend(SendWrap* s, uv_udp_t* handle, uv_buf_t* buf, size_t count, const struct sockaddr* addr, uv_udp_send_cb cb); + + virtual int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); + + v8::Handle Self(); + + protected: + inline UDPWrap* wrap() const { + return (UDPWrap *) wrap_; + } + +}; + class UDPWrap: public HandleWrap { public: static void Initialize(v8::Handle target, @@ -53,6 +84,17 @@ class UDPWrap: public HandleWrap { static void SetBroadcast(const v8::FunctionCallbackInfo& args); static void SetTTL(const v8::FunctionCallbackInfo& args); + void OverrideCallbacks(WrapCallbacks* callbacks) { + UDPWrapCallbacks* old = callbacks_; + callbacks_ = reinterpret_cast(callbacks); + if (old != &default_callbacks_) + delete old; + } + + inline UDPWrapCallbacks* callbacks() const { + return callbacks_; + } + static v8::Local Instantiate(Environment* env); uv_udp_t* UVHandle(); @@ -62,7 +104,7 @@ class UDPWrap: public HandleWrap { static void DoBind(const v8::FunctionCallbackInfo& args, int family); - static void DoSend(const v8::FunctionCallbackInfo& args, + static void ProceedSend(const v8::FunctionCallbackInfo& args, int family); static void SetMembership(const v8::FunctionCallbackInfo& args, uv_membership membership); @@ -77,7 +119,12 @@ class UDPWrap: public HandleWrap { const struct sockaddr* addr, unsigned int flags); + UDPWrapCallbacks default_callbacks_; + UDPWrapCallbacks* callbacks_; // Overridable callbacks + uv_udp_t handle_; + + friend class UDPWrapCallbacks; }; } // namespace node diff --git a/test/dtls-test.js b/test/dtls-test.js new file mode 100644 index 00000000000..de946b96973 --- /dev/null +++ b/test/dtls-test.js @@ -0,0 +1,37 @@ +var dtls = require('dtls'); +var fs = require('fs'); +var util = require('util'); + +// ..\openssl.exe s_client -connect localhost:8000 -dtls1 +// set NODE_DEBUG=cluster,dgram,net,http,fs,tls,dtls,module,timers node +var options = { + key: fs.readFileSync('c:\\temp\\dtls-key.pem'), + cert: fs.readFileSync('c:\\temp\\dtls-cert-self-signed.pem'), + secureProtocol: "DTLSv1_server_method" + // This is necessary only if using the client certificate authentication. + //requestCert: true, + + // This is necessary only if the client uses the self-signed certificate. + // ca: [ fs.readFileSync('client-cert.pem') ] +}; + +var server = dtls.createServer(options, function(cleartextStream) { + console.log('server connected', cleartextStream.authorized ? 'authorized' : 'unauthorized'); +}); + +// TODO: +// Change the handle.owner to the server ? +// +server.socket.on('message', function (msg, rinfo) { + console.log("server got: " + msg + " from " + rinfo.address + ":" + rinfo.port); + var answer = new Buffer("pong - " + msg); + server.socket.send(answer, 0, answer.length, rinfo.port, rinfo.address, function(err, bytes) { + console.log("answer sent"); + }); +}); + +console.log(util.inspect(server, false, null)); + +server.bind(8000, function() { + console.log('server bound : port 8000'); +}); \ No newline at end of file diff --git a/test/tls-test.js b/test/tls-test.js new file mode 100644 index 00000000000..28c57549072 --- /dev/null +++ b/test/tls-test.js @@ -0,0 +1,26 @@ +var tls = require('tls'); +var fs = require('fs'); + +debugger; + +var options = { + key: fs.readFileSync('c:\\temp\\dtls-key.pem'), + cert: fs.readFileSync('c:\\temp\\dtls-cert-self-signed.pem'), + + // This is necessary only if using the client certificate authentication. + //requestCert: true, + + // This is necessary only if the client uses the self-signed certificate. + //ca: [ fs.readFileSync('client-cert.pem') ] +}; + +var server = tls.createServer(options, function(cleartextStream) { + console.log('server connected', + cleartextStream.authorized ? 'authorized' : 'unauthorized'); + cleartextStream.write("welcome!\n"); + cleartextStream.setEncoding('utf8'); + cleartextStream.pipe(cleartextStream); +}); +server.listen(8000, function() { + console.log('server bound'); +});