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 向服务器写回响应并执行与此类似的消息交换?

  1. 为什么来自 ntlm 代理处理程序的初始请求 -> 服务器成功,但没有从此 ntlm 代理处理程序写入的连续响应。
  2. 我在调试时也看到,即使我在写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);