聊聊企业专网出外网的问题处理方案

249 阅读2分钟

在一些企业应用场景涉及到内网应用要调用公网服务请求我们看一下企业网络的网络结构,通常网络分为DMZ区和内网区,这里简化网络结果只用于说明问题处理和解决方案。

企业内网(1).png

在企业网络中DMZ区是可以开通网络权限访问公网的这里通常部署的是网关服务可以是nginx服务或其他反向代理服务,企业内网区的服务只能访问内网不允许访问公网,这样带来一个问题比如一些公网云服务像离线消息类的服务比如华为推送、小米推送、apns推送等如何处理呢?

一种做法是通过DMZ区的nginx再反向代理到公网的服务,但是这种方案也比较麻烦需要再内网服务host里面配置并且扩展一台服务都需要这样增加配置。

另外一种做法是使用代理技术解决内网出外网的方案

1、http隧道技术

在http发起代理请求时先发送CONNECT连接处理连接代理目标的服务然后再使用Http协议访请求具体的公网服务

2、socks5技术

socks5技术完通过socks5协议创建目标代理的连接后使用透传方式转发http1 http2,https等协议这种方式避免了https通讯协议的证书验证过程更适合基于http/https协议的出外网访问方案。socks5可以使用开源的socks5服务部署到dmz区,也可以基于netty sock5 codec自行实现。

ss5地址:ss5.sourceforge.net/software.ht…

下面我针对Netty socks5处理核心逻辑进行解释说明

1、我们在初始化服务时需要加载socks5协议编解码器处理

pipeline.addLast(new Socks5InitialRequestDecoder());  
pipeline.addLast(new Socks5InitialRequestHandler());  
pipeline.addLast(new Socks5CommandRequestDecoder());  
pipeline.addLast(new Socks5CommandRequestHandler());  
pipeline.addLast(Socks5ServerEncoder.DEFAULT);

2、Socks5CommandRequestHandler处理逻辑如下

 protected void channelRead0(ChannelHandlerContext ctx, final DefaultSocks5CommandRequest msg) throws Exception {
        final Channel inboundChannel = ctx.channel();
        if(msg.type().equals(Socks5CommandType.CONNECT)) {
            log.info("连接信息[host:{},dstAddrType:{},port:{}]",msg.dstAddr(),msg.dstAddrType(),msg.dstPort());
            bootstrap.group(inboundChannel.eventLoop())
                    .channel(EventLoopFactory.clientSocketChannelClass())
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000*30)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new RelayHandler(inboundChannel,inboundChannel.remoteAddress().toString()));
                        }
                    });
            bootstrap.connect(msg.dstAddr(),msg.dstPort()).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        proxyChannel=future.channel();
                        Socks5CommandResponse cmd = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4);
                        inboundChannel.writeAndFlush(cmd);
                        if(inboundChannel.isActive()){
                            ChannelPipeline pipeline=inboundChannel.pipeline();
                            pipeline.remove(Socks5InitialRequestDecoder.class);
                            pipeline.remove(Socks5InitialRequestHandler.class);
                            pipeline.remove(Socks5CommandRequestDecoder.class);
                            pipeline.remove(Socks5CommandRequestHandler.class);
                            pipeline.remove(Socks5ServerEncoder.class);
                            pipeline.addLast(new RelayHandler(proxyChannel,proxyChannel.remoteAddress().toString()));
                        }else{
                            log.warn("Socks5代理连接关闭[客户端关闭连接]");
                            proxyChannel.close();
                        }
                    } else {
                        log.warn("连接失败[host:{},dstAddrType:{},port:{}]",msg.dstAddr(),msg.dstAddrType(),msg.dstPort(),future.cause());
                        if(inboundChannel.isActive()){
                            //回复客户端代理连接失败
                            Socks5CommandResponse cmd = new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, Socks5AddressType.IPv4);
                            inboundChannel.writeAndFlush(cmd);
                            inboundChannel.eventLoop().schedule(new Runnable() {
                                @Override
                                public void run() {
                                    inboundChannel.close();
                                }
                            },200, TimeUnit.MILLISECONDS);
                        }

                    }
                }
            });
        }else{
            ctx.fireChannelRead(msg);
        }
    }

解析Connect请求用于目标主机创建连接,之后给客户端返回sock5连接成功后客户端就会脱去socks5协议使用http协议通过专有tcp通道进行http协议透明传输。