Netty ProxyHandler writeAndFlush 未向服务器写入响应
Netty ProxyHandler writeAndFlush is not writing response to server
我正在尝试在 Netty 中实现 NTLMProxyHandler,它可以执行 NTLM 消息交换并使用 Web 代理对客户端进行身份验证。
NTLMProxyHandler 扩展了 Netty 的 ProxyHandler class。因此,初始 HTTP 请求由代理处理程序触发,并到达我创建的模拟代理服务器。代理服务器读取此请求并响应 407 代理身份验证要求响应。
NTLMProxyHandler 在客户端读取此响应并准备新的 NTLM Type1Message 并将响应再次写回服务器。我面临的问题是,尽管调用了通道未来的成功处理程序,但此请求从未发送到我的代理服务器。
我在日志记录中启用了 Netty 包,但无法弄清楚为什么只有第二次从 ntlm 代理处理程序写入的响应丢失。
我尝试过使用 Netty ProxyHandler 的 sendToProxyServer(msg) 以及使用从 channelRead() 传递的 channelHandlerCtx。在这两种情况下,writeAndFlush 都已完成,但响应从未到达服务器并且服务器超时。
有没有人使用 channelHandlerCtx 向服务器写回响应并执行与此类似的消息交换?
- 为什么来自 ntlm 代理处理程序的初始请求 -> 服务器成功,但没有从此 ntlm 代理处理程序写入的连续响应。
- 我在调试时也看到,即使我在写NTLMMessage1 时关闭了代理服务器,writeAndFlush future 仍然是成功的。为什么 writeAndFlush 在这种情况下会成功?
任何指点都会很有帮助。谢谢!
NTLMProxyHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.proxy.ProxyConnectException;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtlmContext;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class NTLMProxyHandler extends AbstractProxyHandler {
private String userName;
private String password;
private final static String DOMAIN = "CORP";
public static final String NTLM_Prefix = "NTLM";
private static final Logger logger = LoggerFactory.getLogger(NTLMProxyHandler.class);
private static int NTLMV2_FLAGS_TYPE3 = 0xa2888205;
private HttpResponseStatus status;
private HttpResponse response;
private NtlmPasswordAuthentication ntlmPasswordAuthentication;
private NtlmContext ntlmContext;
private final HttpClientCodec codec = new HttpClientCodec();
public NTLMProxyHandler(SocketAddress proxyAddress) {
super(proxyAddress);
}
public NTLMProxyHandler(SocketAddress proxyAddress, String domain, String username, String password) {
super(proxyAddress);
setConnectTimeoutMillis(50000);
this.userName = username;
this.password = password;
ntlmPasswordAuthentication = new NtlmPasswordAuthentication(DOMAIN, username, password);
ntlmContext = new NtlmContext(ntlmPasswordAuthentication, true);
}
@Override
public String protocol() {
return "http";
}
@Override
public String authScheme() {
return "ntlm";
}
protected void addCodec(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline p = ctx.pipeline();
String name = ctx.name();
p.addBefore(name, (String)null, this.codec);
}
protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
this.codec.removeOutboundHandler();
}
protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
this.codec.removeInboundHandler();
}
@Override
protected Object newInitialMessage(ChannelHandlerContext channelHandlerContext) throws Exception {
InetSocketAddress raddr = this.destinationAddress();
String rhost;
if(raddr.isUnresolved()) {
rhost = raddr.getHostString();
} else {
rhost = raddr.getAddress().getHostAddress();
}
String host = rhost + ':' + raddr.getPort();
DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
req.headers().set("connection", "keep-alive");
// This initial request successfully reaches the server !
return req;
}
@Override
protected boolean handleResponse(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
if (o instanceof HttpResponse) {
response = (HttpResponse) o;
}
boolean finished = o instanceof LastHttpContent;
if(finished) {
status = response.status();
logger.info("Status: " + status);
if (!response.headers().isEmpty()) {
for (String name: response.headers().names()) {
for (String value: response.headers().getAll(name)) {
logger.debug("Header: " + name + " = " + value);
}
}
}
if(status.code() == 407) {
negotiate(channelHandlerContext, response);
}
else if(status.code() == 200){
logger.info("Client: NTLM exchange complete. Authenticated !");
}
else {
throw new ProxyConnectException(this.exceptionMessage("status: " + this.status));
}
}
return finished;
}
private void negotiate(ChannelHandlerContext channelHandlerContext, HttpResponse msg) throws Exception{
String ntlmHeader = msg.headers().get(HttpHeaderNames.PROXY_AUTHENTICATE);
if(ntlmHeader.equalsIgnoreCase("NTLM")){
logger.info("Client: Creating NTLM Type1Message");
//Send Type1Message
byte[] rawType1Message = ntlmContext.initSecContext(new byte[]{}, 0, 0);
Type1Message type1Message = new Type1Message(rawType1Message);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
String proxyAuthHeader = Base64.encode(type1Message.toByteArray());
logger.info("Setting proxyAuthHeader = " + proxyAuthHeader);
response.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthHeader);
ByteBuf byteBuf = Unpooled.buffer(rawType1Message.length);
byteBuf.writeBytes(response.content());
//This is where the response is lost and never reaches the proxy server
sendToProxyServer(byteBuf);
// channelHandlerContext.writeAndFlush(response.content));
} else if (ntlmHeader.contains(NTLM_Prefix)) {
logger.info("Client: Creating NTLM Type3Message");
//Send Type3 Message
}
}
}
终于找到问题了。 NTLM 代理处理程序在响应代理消息时发送的是 FullHTTPResponse 而不是 FullHTTPRequest。看起来 Netty 的管道正在丢弃作为响应写入的数据,并且日志中没有指出这一点。
DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
req.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, "type3message");
sendToProxyServer(req);
我正在尝试在 Netty 中实现 NTLMProxyHandler,它可以执行 NTLM 消息交换并使用 Web 代理对客户端进行身份验证。
NTLMProxyHandler 扩展了 Netty 的 ProxyHandler class。因此,初始 HTTP 请求由代理处理程序触发,并到达我创建的模拟代理服务器。代理服务器读取此请求并响应 407 代理身份验证要求响应。
NTLMProxyHandler 在客户端读取此响应并准备新的 NTLM Type1Message 并将响应再次写回服务器。我面临的问题是,尽管调用了通道未来的成功处理程序,但此请求从未发送到我的代理服务器。
我在日志记录中启用了 Netty 包,但无法弄清楚为什么只有第二次从 ntlm 代理处理程序写入的响应丢失。
我尝试过使用 Netty ProxyHandler 的 sendToProxyServer(msg) 以及使用从 channelRead() 传递的 channelHandlerCtx。在这两种情况下,writeAndFlush 都已完成,但响应从未到达服务器并且服务器超时。
有没有人使用 channelHandlerCtx 向服务器写回响应并执行与此类似的消息交换?
- 为什么来自 ntlm 代理处理程序的初始请求 -> 服务器成功,但没有从此 ntlm 代理处理程序写入的连续响应。
- 我在调试时也看到,即使我在写NTLMMessage1 时关闭了代理服务器,writeAndFlush future 仍然是成功的。为什么 writeAndFlush 在这种情况下会成功?
任何指点都会很有帮助。谢谢!
NTLMProxyHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.proxy.ProxyConnectException;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtlmContext;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class NTLMProxyHandler extends AbstractProxyHandler {
private String userName;
private String password;
private final static String DOMAIN = "CORP";
public static final String NTLM_Prefix = "NTLM";
private static final Logger logger = LoggerFactory.getLogger(NTLMProxyHandler.class);
private static int NTLMV2_FLAGS_TYPE3 = 0xa2888205;
private HttpResponseStatus status;
private HttpResponse response;
private NtlmPasswordAuthentication ntlmPasswordAuthentication;
private NtlmContext ntlmContext;
private final HttpClientCodec codec = new HttpClientCodec();
public NTLMProxyHandler(SocketAddress proxyAddress) {
super(proxyAddress);
}
public NTLMProxyHandler(SocketAddress proxyAddress, String domain, String username, String password) {
super(proxyAddress);
setConnectTimeoutMillis(50000);
this.userName = username;
this.password = password;
ntlmPasswordAuthentication = new NtlmPasswordAuthentication(DOMAIN, username, password);
ntlmContext = new NtlmContext(ntlmPasswordAuthentication, true);
}
@Override
public String protocol() {
return "http";
}
@Override
public String authScheme() {
return "ntlm";
}
protected void addCodec(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline p = ctx.pipeline();
String name = ctx.name();
p.addBefore(name, (String)null, this.codec);
}
protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
this.codec.removeOutboundHandler();
}
protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
this.codec.removeInboundHandler();
}
@Override
protected Object newInitialMessage(ChannelHandlerContext channelHandlerContext) throws Exception {
InetSocketAddress raddr = this.destinationAddress();
String rhost;
if(raddr.isUnresolved()) {
rhost = raddr.getHostString();
} else {
rhost = raddr.getAddress().getHostAddress();
}
String host = rhost + ':' + raddr.getPort();
DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
req.headers().set("connection", "keep-alive");
// This initial request successfully reaches the server !
return req;
}
@Override
protected boolean handleResponse(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
if (o instanceof HttpResponse) {
response = (HttpResponse) o;
}
boolean finished = o instanceof LastHttpContent;
if(finished) {
status = response.status();
logger.info("Status: " + status);
if (!response.headers().isEmpty()) {
for (String name: response.headers().names()) {
for (String value: response.headers().getAll(name)) {
logger.debug("Header: " + name + " = " + value);
}
}
}
if(status.code() == 407) {
negotiate(channelHandlerContext, response);
}
else if(status.code() == 200){
logger.info("Client: NTLM exchange complete. Authenticated !");
}
else {
throw new ProxyConnectException(this.exceptionMessage("status: " + this.status));
}
}
return finished;
}
private void negotiate(ChannelHandlerContext channelHandlerContext, HttpResponse msg) throws Exception{
String ntlmHeader = msg.headers().get(HttpHeaderNames.PROXY_AUTHENTICATE);
if(ntlmHeader.equalsIgnoreCase("NTLM")){
logger.info("Client: Creating NTLM Type1Message");
//Send Type1Message
byte[] rawType1Message = ntlmContext.initSecContext(new byte[]{}, 0, 0);
Type1Message type1Message = new Type1Message(rawType1Message);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
String proxyAuthHeader = Base64.encode(type1Message.toByteArray());
logger.info("Setting proxyAuthHeader = " + proxyAuthHeader);
response.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthHeader);
ByteBuf byteBuf = Unpooled.buffer(rawType1Message.length);
byteBuf.writeBytes(response.content());
//This is where the response is lost and never reaches the proxy server
sendToProxyServer(byteBuf);
// channelHandlerContext.writeAndFlush(response.content));
} else if (ntlmHeader.contains(NTLM_Prefix)) {
logger.info("Client: Creating NTLM Type3Message");
//Send Type3 Message
}
}
}
终于找到问题了。 NTLM 代理处理程序在响应代理消息时发送的是 FullHTTPResponse 而不是 FullHTTPRequest。看起来 Netty 的管道正在丢弃作为响应写入的数据,并且日志中没有指出这一点。
DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
req.headers().set(HttpHeaderNames.HOST, host);
req.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, "type3message");
sendToProxyServer(req);