我正在尝试实现MITM代理。
我正在处理一个CONNECT请求,该请求随后用于与内部HTTPS服务器建立连接。
根据请求,HTTPS服务器响应:
connection: close
foo
我希望客户端收到响应并代理以关闭连接套接字。
相反,client
收到响应,并且代理服务器记录错误:
server socket error Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (net.js:407:14)
at Socket.ondata (_stream_readable.js:713:22)
at Socket.emit (events.js:200:13)
at addChunk (_stream_readable.js:294:12)
at readableAddChunk (_stream_readable.js:275:11)
at Socket.Readable.push (_stream_readable.js:210:10)
at TCP.onStreamRead (internal/stream_base_commons.js:166:17) {
code: 'EPIPE'
}
request socket error Error: read ECONNRESET
at TCP.onStreamRead (internal/stream_base_commons.js:183:27) {
errno: 'ECONNRESET',
code: 'ECONNRESET',
syscall: 'read'
}
这是主题脚本:
const net = require('net');
const http = require('http');
const https = require('https');
const sslCertificate = {
ca: '-----BEGIN CERTIFICATE REQUEST-----\n' +
'MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B\n' +
'AQEFAAOCAQ8AMIIBCgKCAQEAuftLzDyJ8dRk71pZ3637tCIZCVLJieLqIlAf7wT5\n' +
'+qesTgu6vWzndZ4ze2V2lkac0xqFlW1djKT9IPUTCPx5dmWdT8mYFNUqB87hRWx9\n' +
'6Ge21bs+KDppujHYrrgNjT8L3+RlHenoG7Qi5WuSzfOqP5nqCyoKFFNHJ0Ds52Uk\n' +
'uvmTLzY/+kx3tFFGi4QXyva3T38uF99D4C2Tqxy7aRHEBJATQYxJgVPResiv31zv\n' +
'qd6H1jYIZGw5s4QJFh5C7VXsoHs1dLIfDoNcV/fO95VQ+wXPxrl8mcVQzNV7RKmX\n' +
'VHKudzx49IvOpRyM3OmN3RV5snOYKGmgwXQUF7JL2VSrSQIDAQABoAAwDQYJKoZI\n' +
'hvcNAQELBQADggEBAIaUryumwXIxMJErT/7B46l2k27+xefaTPCddjERhqk8WH/N\n' +
'95/yhvdzq1i0BSLv74Kh7L68kJiN8vtF6sAORofw42LMo+KzRDE1m1Zl7CVWw2DF\n' +
'wT7SJov22t6dVx6HOcsZZSo5lSN+CMN3xkgt6jyEPbCKfCJzl44Y3eOpqzry6/GM\n' +
'U+hR7nQx3IJmpAHNd7wolRzkf1X0gTifR5iC5S72GSRM9AnLfL2L0zQC6LmcNmZp\n' +
'3deNxIC+w5kTALREiMq3P9McBMCgwRinOJLbhmV9ifPRpLa9e+mFVdHzbR7+09kp\n' +
'6eNS19RndbHn6N1RbgFSNjDz28fMXISSWZFB/X4=\n-----END CERTIFICATE ' +
'REQUEST-----',
cert: '-----BEGIN CERTIFICATE-----\n' +
'MIICpDCCAYwCCQCK9kDE6/eFXDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls\n' +
'b2NhbGhvc3QwHhcNMTkwNzE5MTczMzI2WhcNMjAwNzE4MTczMzI2WjAUMRIwEAYD\n' +
'VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5\n' +
'+0vMPInx1GTvWlnfrfu0IhkJUsmJ4uoiUB/vBPn6p6xOC7q9bOd1njN7ZXaWRpzT\n' +
'GoWVbV2MpP0g9RMI/Hl2ZZ1PyZgU1SoHzuFFbH3oZ7bVuz4oOmm6MdiuuA2NPwvf\n' +
'5GUd6egbtCLla5LN86o/meoLKgoUU0cnQOznZSS6+ZMvNj/6THe0UUaLhBfK9rdP\n' +
'fy4X30PgLZOrHLtpEcQEkBNBjEmBU9F6yK/fXO+p3ofWNghkbDmzhAkWHkLtVeyg\n' +
'ezV0sh8Og1xX9873lVD7Bc/GuXyZxVDM1XtEqZdUcq53PHj0i86lHIzc6Y3dFXmy\n' +
'c5goaaDBdBQXskvZVKtJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGxXxytrNtm+\n' +
'q4NpWtKhy3DL5LOMH+K8lqgJ29SmmDEcqWgevpUnqLYFvb3AOxU/vYId5rFmHb5A\n' +
'WnXyKJ/YYSpNi47EcV+AJCwqDqBgAM4J3Tiiu6BguZ4sU20ZVFl1oQvTlQw8InLI\n' +
'D1ciwwtgWS2z9pRKmQ2ar2TY+2yhnl0L1WCl50XH6PngzzEHSxHiPDnOYPyXQjPs\n' +
'vkoJDmdnAVfWs2DfKfM0l27nIL2IBZr6Gks+nLwaK7FedQVD8ORYg9x/mwXO1oDr\n' +
'sLyCQUlXhhBNBmn+TTLFPbrXetOU6le7iW3JJVMUv84vh8cV8aLtXDuQ0qlKMd8B\n' +
'Mrgha3mM8EM=\n-----END CERTIFICATE-----',
key: '-----BEGIN RSA PRIVATE KEY-----\n' +
'MIIEpAIBAAKCAQEAuftLzDyJ8dRk71pZ3637tCIZCVLJieLqIlAf7wT5+qesTgu6\n' +
'vWzndZ4ze2V2lkac0xqFlW1djKT9IPUTCPx5dmWdT8mYFNUqB87hRWx96Ge21bs+\n' +
'KDppujHYrrgNjT8L3+RlHenoG7Qi5WuSzfOqP5nqCyoKFFNHJ0Ds52UkuvmTLzY/\n' +
'+kx3tFFGi4QXyva3T38uF99D4C2Tqxy7aRHEBJATQYxJgVPResiv31zvqd6H1jYI\n' +
'ZGw5s4QJFh5C7VXsoHs1dLIfDoNcV/fO95VQ+wXPxrl8mcVQzNV7RKmXVHKudzx4\n' +
'9IvOpRyM3OmN3RV5snOYKGmgwXQUF7JL2VSrSQIDAQABAoIBAGnWDuFwBhQ/iR0I\n' +
'rqJy0Q1GZjb/DL/SCOlz7WhIzbUNnClh1WgcxG8TkzqCmASWtIIR0rkhXp49+eq6\n' +
'bJWtj7WHyAjysQAR+nQtD9dBETmjY9GnV4zvCOGzohpzlQqvOSO1RrHKPZMeZMln\n' +
'+UgIhPbisOSfjNLaPWCiOu7HiSp5CgT70mSrylNQWhIa/okt8zjDbpV4QGPYP8J/\n' +
'fi4k3u5C8oHwCt3DYp4Qc6ybKiMuBELVcoI0Ug0CtVriB11uNCYOqMbanj4VfRzq\n' +
'KPTDRtkiF+EYi0PBstW+X9p7rFVB1PaBSF3PxudWMTmNZ1MooqOfkIves/T7YoxA\n' +
'Uh9XUIECgYEA4Y8RU+/lf5GMDKstdwm+OH0NBOT/mrsFAlWnGtQivWddyPxvPVJH\n' +
'LqIYtpqTH2luh7ksTcmTacqRjFx/ebobFAVgvg1zhCzHIdmgedGuxzvhEic1KRgT\n' +
'EJgm4kW9uPFZugd05873uWf0cYjbQZXQhhn1E3bTorTuJJZoJu0R7ZECgYEA0xTb\n' +
'bnFyOgD+c0A+kkirHiYU5RDAvtCS0jyKbZAPTP3fX016JeC2pxQcN4iLvgumm+Iv\n' +
'ugtdrHYDzZTIzMl0pT8HSDqjaW8nNmEMvYaE8FYGlFHqEJQlweGMYeXdCxZSA+1D\n' +
'HAzG8tW0rniMZp6KevZt5GCmBX3q0mH9ZKU/ZjkCgYEA14JTgwhOFXHiBuSyvu6v\n' +
'MdfBTbDiy1rvMUjXLZoMSz1s7TDLtCJd4p97z1SnRzb8JW92dign0cd7A0oJfiuj\n' +
'3aA5y7ycZ2hFJwGBA4OlY7TBmg+eClJ3PL6zQDR0TjVDjqu7NhSYuiwp8SRaoTJc\n' +
'FxTMBTnegbIvawPOJYsTOxECgYEAlVDzyLTHsPBzDuQrXx+4rKMTtNadAl5Y/g+F\n' +
'fOujZztPgAM2nQTRMG+xZjdZYx6qxSrDyD+yDAWPuyW8xeDceuiTJi0U28idXIJa\n' +
'mNdHwxuXm+Q2R3QFIZmDzNzl+KnZap20E2uWcMFsBt+PsigEneck5aDY0Jm6OwjG\n' +
'TyP2LUECgYALK+5AoQYbeUwVd3MhJONl0EdtKzjDq2wI127oXCjqVIe9BoqNedDu\n' +
'zOvo5QjNApRbPZcaJB7e/3XbMFv/jSpeL9jC/AynGQBdpk3meL9KtC7Nm4wwj8XX\n' +
'Ad5ZZkUZLAukbH1BqBuEgFjv3SDJ2g/aqUdqVfwq6qNSNdWzZTQG4w==\n-----END RSA ' +
'PRIVATE KEY-----'
};
const handleConnect = (port, request, requestSocket, head) => {
const {
httpVersion
} = request;
const serverSocket = net.connect({
port
}, () => {
requestSocket.write(
'HTTP/' + httpVersion + ' 200 Connection established\r\n' +
'\r\n'
);
serverSocket.write(head);
serverSocket.pipe(requestSocket);
requestSocket.pipe(serverSocket);
});
serverSocket.on('error', (error) => {
console.log('server socket error', error);
});
requestSocket.on('error', (error) => {
console.log('request socket error', error);
});
};
const requestHandler = (incomingMessage, outgoingMessage) => {
outgoingMessage.writeHead(200, {
connection: 'close'
});
outgoingMessage.end(Buffer.from('foo'));
};
const main = () => {
const httpServer = http.createServer(requestHandler);
const internalHttpsServer = https
.createServer(sslCertificate, requestHandler)
.listen()
.unref();
httpServer.on('connect', (request, requestSocket, head) => {
handleConnect(
internalHttpsServer.address().port,
request,
requestSocket,
head
);
});
httpServer.listen(8080);
};
main();
此脚本可以使用任何HTTPS URL进行测试,例如
curl --proxy http://127.0.0.1:8080 'https://127.0.0.1/' -k
或者,您可以:
git clone https://github.com/gajus/http-proxy-connection-close.git
cd ./http-proxy-connection-close
node ./server.js
curl --proxy http://127.0.0.1:8080 'https://127.0.0.1/' -k
当代理响应中包含ECONNRESET
标头时,如何处理connection: close
错误?
答案 0 :(得分:0)
有两个不相关的问题。
可以通过在“结束”事件中显式销毁客户端套接字来防止ECONNRESET
。
serverSocket.once('end', () => {
clientSocket.destroy();
});
但是,在对该实现进行压力测试后,我开始看到ECONNREFUSED
错误。
结果是有时IPC连接会失败:
errno: ECONNREFUSED
code: ECONNREFUSED
syscall: connect
address: /tmp/raygun-cjyaj51hz000035h3erhlf7a3.sock
name: Error
message: connect ECONNREFUSED /tmp/raygun-cjyaj51hz000035h3erhlf7a3.sock
stack:
"""
Error: connect ECONNREFUSED /tmp/raygun-cjyaj51hz000035h3erhlf7a3.sock
at PipeConnectWrap.afterConnect [as oncomplete] (net.js:1054:14)
"""
主要是在CPU使用率接近100%时发生。
在这些情况下,客户端套接字只会挂起,等待与内部HTTPS服务器的连接。
解决方案是以下两种之一:
这是前者的样子:
serverSocket.on('error', (error) => {
log.error({
error: serializeError(error)
}, 'server socket error');
clientSocket.write([
'HTTP/1.1 503 Service Unavailable',
'connection: close'
].join('\n') + '\n\n');
clientSocket.end();
});
https://gist.github.com/gajus/72270a9f3aea3b09d61b997f7e5537f3