在一些企业应用场景涉及到内网应用要调用公网服务请求我们看一下企业网络的网络结构,通常网络分为DMZ区和内网区,这里简化网络结果只用于说明问题处理和解决方案。
在企业网络中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协议透明传输。