介绍
XFF 处理 X-Forwarded-For 头的中间件,可以在7层过滤出内网ip以外的相对来说真实的客户端ip。
基础背景知识
cidr的网络地址表示
192.168.0.0/16
网络地址中的 /16
表示网络掩码,说明192.168.0.0
前16位为网络位,后16位网络内的主机位,网络内可以容纳后16位共计 (2^16-2) ⚠️需要 去除 192.168.0.0
网络地址 与 192.168.255.255
广播地址
X-Forwarded-For说明
X-Forwarded-For 在 rfc 7329 Forwarded HTTP Extension
中有过说明
A/B/C/D/E 网络分类
默认局域网保留网络地址
局域网地址的分配说明可以参考rfc1918
快速图解
代码分析
创建XFF对象
根据选项参数,创建一个XFF
对象指针,选项参数是否开启debug模式(会增加一些打印),设置信任的 L7 EdgeProxy
的网段,支持多个网段设置,并提供了默认创建方法 Default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Options is a configuration container to setup the XFF middleware.
type Options struct {
// AllowedSubnets is a list of Subnets from which we will accept the
// X-Forwarded-For header.
// If this list is empty we will accept every Subnets (default).
AllowedSubnets []string
// Debugging flag adds additional output to debug server side XFF issues.
Debug bool
}
// New creates a new XFF handler with the provided options.
func New(options Options) (*XFF, error) {
allowedMasks, err := toMasks(options.AllowedSubnets)
if err != nil {
return nil, err
}
xff := &XFF{
allowAll: len(options.AllowedSubnets) == 0,
allowedMasks: allowedMasks,
}
if options.Debug {
xff.Log = log.New(os.Stdout, "[xff] ", log.LstdFlags)
}
return xff, nil
}
// XFF http handler
type XFF struct {
// Debug logger
Log *log.Logger
// Set to true if all IPs or Subnets are allowed.
allowAll bool
// List of IP subnets that are allowed.
allowedMasks []net.IPNet
}
// New creates a new XFF handler with the provided options.
func New(options Options) (*XFF, error) {
allowedMasks, err := toMasks(options.AllowedSubnets)
if err != nil {
return nil, err
}
xff := &XFF{
allowAll: len(options.AllowedSubnets) == 0,
allowedMasks: allowedMasks,
}
if options.Debug {
xff.Log = log.New(os.Stdout, "[xff] ", log.LstdFlags)
}
return xff, nil
}
// Default creates a new XFF handler with default options.
func Default() (*XFF, error) {
return New(Options{})
}
将客户端真实IP设置进RemoteAddress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// HandlerFunc provides Martini compatible handler
func (xff *XFF) HandlerFunc(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
}
// Handler updates RemoteAdd from X-Fowarded-For Headers.
func (xff *XFF) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
h.ServeHTTP(w, r)
})
}
// Negroni compatible interface
func (xff *XFF) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
r.RemoteAddr = GetRemoteAddrIfAllowed(r, xff.allowed)
next(w, r)
}
解析X-Forwarded-For
1
2
3
4
5
6
7
8
9
10
// Parse parses the value of the X-Forwarded-For Header and returns the IP address.
func Parse(ipList string) string {
for _, ip := range strings.Split(ipList, ",") {
ip = strings.TrimSpace(ip)
if IP := net.ParseIP(ip); IP != nil && IsPublicIP(IP) {
return ip
}
}
return ""
}
对X-Forwarded-For
进行处理,按照,
进行切割,遍历次IP数组,当ip不为空,且IP为合法的公网IP,则将当前IP返回,并将此ip赋值``request.RemoteAddress`
在看下核心的IsPublicIp
的实现,IsGlobalUnicast
用于判断IP是否是一个单播地址,在配合privateMasks
是否为局域网保留地址,其实这里不用自己写这个判断,现在是有IsPrivate
的相同功能的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// list of private subnets
var privateMasks, _ = toMasks([]string{
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
})
// IsPublicIP returns true if the given IP can be routed on the Internet.
func IsPublicIP(ip net.IP) bool {
if !ip.IsGlobalUnicast() {
return false
}
return !ipInMasks(ip, privateMasks)
}
// IsGlobalUnicast reports whether ip is a global unicast
// address.
//
// The identification of global unicast addresses uses address type
// identification as defined in RFC 1122, RFC 4632 and RFC 4291 with
// the exception of IPv4 directed broadcast addresses.
// It returns true even if ip is in IPv4 private address space or
// local IPv6 unicast address space.
func (ip IP) IsGlobalUnicast() bool {
return (len(ip) == IPv4len || len(ip) == IPv6len) &&
!ip.Equal(IPv4bcast) &&
!ip.IsUnspecified() &&
!ip.IsLoopback() &&
!ip.IsMulticast() &&
!ip.IsLinkLocalUnicast()
}
官方提供判断ipv4/ipv6
是否为局域网函数,xff
的issue中提到了一个更加全面的判断是否为局域网保留ip的判断https://github.com/letsencrypt/boulder/blob/30a516737c9daa4c88c8c47070c25a5e7033cdcf/bdns/dns.go#L31-L145,还有一篇关于``X-Forwarded-For``如何解析的好文章https://github.com/ajvondrak/remote_ip/issues/29
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// IsPrivate reports whether ip is a private address, according to
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
func (ip IP) IsPrivate() bool {
if ip4 := ip.To4(); ip4 != nil {
// Following RFC 1918, Section 3. Private Address Space which says:
// The Internet Assigned Numbers Authority (IANA) has reserved the
// following three blocks of the IP address space for private internets:
// 10.0.0.0 - 10.255.255.255 (10/8 prefix)
// 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
// 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
return ip4[0] == 10 ||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
(ip4[0] == 192 && ip4[1] == 168)
}
// Following RFC 4193, Section 8. IANA Considerations which says:
// The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast".
return len(ip) == IPv6len && ip[0]&0xfe == 0xfc
}
总结
- 在获取客户端ip前判断当前的
RemoteAddress
是否为我方的代理IP - 不能直接拿X-Forwarded-For 的第一个IP地址作为客户端ip,需要判断IP是否为单播地址且不为局域网保留地址
如果你看不到评论,那么就真的看不到评论w(゜Д゜)w