likes
comments
collection
share

使用go做一个返回公网IP的web服务

作者站长头像
站长
· 阅读数 24

在我们日常运维中,特别是在云服务器环境下,其使用基本上都是弹性网卡,所以使用ip a只能获取其内网地址,若想要获取其公网ip,除了上云平台查看机器信息以外,只有通过其他服务来协助返回公网地址了。

我们应该如何获取公网地址

前些天,我们已经看了http相关基础报文,我们可以知晓,从http报文而言来看,我们是无法获取其来访者IP的,那么我们究竟应当如何获取呢?

那在报文中,哪里有记录来访者IP呢? 答案是TCP/IP4层协议中的网络层中有定义源IP地址和目标IP地址,其报文架构大致如下:

下图来源于: rfc791 网址: www.rfc-editor.org/rfc/rfc791

使用go做一个返回公网IP的web服务

我们能够直接在应用中获取其IP层的数据么?答案是不能的,因为网络包来了,若只有IP层的数据,这是给哪个进程的呢? 所以这个时候就需要引入我们的传输层的TCP报文协议了。

下图来源于: rfc793 网址: www.rfc-editor.org/rfc/rfc793.…

使用go做一个返回公网IP的web服务

可以看到,在IP报文的基础上,TCP报文又定义了源端口 和 目标端口,端口是为了解决不同主机和不通进程之间的通信所建立的规则,结合起来可以形成一个四元组,即: 源IP地址,源端口号 | 目标IP地址,目标端口号,这个我们也称之为是TCP套接字四元组

所以,回到问题,返回来访者IP地址,我们就将其四元组的信息返回回去就可以了。

编写返回来访者IP的web服务

根据上述信息,我们可以直接编写一个tcpdemo,返回源IP数据。

package main

import (
	"fmt"
	"net"
	"time"
)

func main() {

	l , _ := net.Listen("tcp","0.0.0.0:8082")

	conn , _ := l.Accept()

	fmt.Println("来访者IP:" , conn.RemoteAddr().String())

	time.Sleep(3 * time.Second)
}

我们将其程序执行后,可得直接如下

使用go做一个返回公网IP的web服务

我们可以来分析下,该TCP套接字四元组信息是什么,首先,服务器端口是8082,而服务器IP监听所有的网卡,根据客户端的信息,暂时定义为127.0.0.1 , 而客户端端口为54953,客户端IP为127.0.0.1,所以可以这样理解为

源IP地址源端口号目标IP地址目标端口号
127.0.0.154953127.0.0.18082

这里的54953端口是客户端临时使用的,使用完后会撤销回去,怎么? 你不认为这2个确实建立了连接? 那我们可以测试下,使用netstat命令可以查看TCP连接信息,我们暂时将代码中time.Sleep(3 * time.Second) 给修改为time.Sleep(30 * time.Second) ,以便于我们稍后观察。即程序如下:

使用go做一个返回公网IP的web服务

我们将转移阵地到linux环境下测试代码。

启动服务器后,我们使用curl 127.0.0.1:8082来访问其数据。

使用go做一个返回公网IP的web服务

会得到客户端的端口号,我们将在其没有没有close连接的时候,我们使用netstat -an | grep 39440可以查询TCP连接信息。

使用go做一个返回公网IP的web服务

其中-a参数是显示网卡所有的连接信息,-n是显示ip地址,而非主机名。

那我们要返回其来访者IP地址,并将其打造为web服务,那也简单,我们仅需嵌套一层http报文就行了。

我们将其程序改写如下即可。

package main

import (
	"fmt"
	"net"
	"strings"
)

func main() {
	l , err := net.Listen("tcp","0.0.0.0:8082");if err != nil {
		panic(err)
	}

	for {
		conn , err := l.Accept();if err != nil {
			continue
		}

		ip := strings.Split(conn.RemoteAddr().String(),":")[0]
		
		conn.Write([]byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s",len(ip),ip)))

	}
}

如上代码,在服务器启动后,使用curl IP:8082后可以得到IP地址,我们尝试一下。

使用go做一个返回公网IP的web服务

若是放在公网上,则显示的是公网的地址信息。

关于反向代理如何获取地址

反向代理是一个很有意思的事情,我们通过如上方法,若经过代理,我们所获取的remoteIP则是代理服务器的ip,而非客户端Ip,流程如下图。

使用go做一个返回公网IP的web服务

那么如何解决这个问题呢? 很显然,修改网络层报文,这个是很不现实的,也是不可能的,所以我们可以另辟蹊径,由于我们做的是web服务器,所以我们可以在代理服务器上设置将客户端的remoteIP添加到请求报文中去,一同发给真实web服务器,而服务器拿到后,则不从TCP套接字获取IP,而直接从http报文获取IP即可,这个只要代理服务器和真实web服务器将相关规则约定好即可,我们拿nginx举例:

使用go做一个返回公网IP的web服务

例如我们配置如上,那么在真实web服务器就应该使用请求头为X-Real-IP来获取客户端IP地址。

总结

返回来访者IP地址,在很多业务场景,尤其是目前云服务器中。在获取过程中,我们可以将其分为2种,第一种是web服务器直接面对客户端,第二种是经过一层代理服务器,不管怎么样,我们都需要从tcp请求中获取客户端的地址,经过某种方式,从而返回给客户端,怎么样,好玩吧,快来试试吧。