如何在两层服务器的第二层Nginx上获取用户IP

 

一.之前在做nginx的服务器配置的时候遇到了一个问题,在之前服务器有用到一个限制客户端最大并发连接的功能,而且这个功能的实现是依靠在服务器中做

$remote-addr



这样的配置来达到的。但在增加了前端一层(负载、CDN、防火墙、安全服务)服务器之后,拿到的客户端IP就都变成了前端服务器的IP,而并非真是的用户IP地址。

 

二.这样的问题下,我又重新看了几次nginx官网的介绍,也发现其中另外一个特别重要的变量

$proxy_add_x_forwarded_for



这个变量是客户端访问请求中的X-forwarded-for 字段的值,如果请求中不包含这个字段,则自动用这个变量会等价于remote-addr这个变量。这允许我们获取HTTP请求中通常情况下前端服务器保存的客户真实IP地址的字段,通常就是我们说的X_FORWARDED_FOR字段,然后通过这样的方法,我们就可以实现各种各样的功能了。

 

 

三.下面我来实际给大家做一个简单演示。诸多不足,欢迎指正。

 

首先我们先搭建好Nginx的环境,这里我们使用1.7 系列的最新版本 1.7.9为例,(关于版本的问题参见FAQ 1)

 

下载、WGET所需地址 http://nginx.org/download/nginx-1.7.9.tar.gz


1.      下载Nginx

 

[lugt@localhostmysql]$ wget http://nginx.org/download/nginx-1.7.9.tar.gz

2.      解压

[lugt@localhostmysql]$ tar zxvf nginx-1.7.9.tar.gz

3.      直接编译(需要考虑是否需要openssl等插件的支持)

[lugt@localhost mysql]$cd nginx-1.7.9
[lugt@localhost nginx-1.7.9]$ ./configure 
[lugt@localhost nginx-1.7.9]$ make
[lugt@localhost nginx-1.7.9]$ su
[lugt@localhostnginx-1.7.9]$ make install

4.      然后接下来修改nginx.conf配置文件

 

[lugt@localhost nginx-1.7.9]$ su 
[lugt@localhost nginx-1.7.9]$cd /usr/local/nginx
[lugt@localhostnginx]$ vi conf/nginx.conf

 

然后在nginx.conf 中找到这里,加入来设置负载均衡,模仿CDN

upstream dnsnginx1 {
        server[*.*.*.*/yourhostname]:8080 weight=10000; #填IP、域名
}
server {
        listen       80;
        server_name 
#access_log  logs/host.access.log  main
        location /{
           proxy_pass          http://dnsnginx1;
            proxy_set_header    Host             $host;
            proxy_set_header    X-Real-IP        $remote_addr;
            proxy_set_header    X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header    HTTP_X_FORWARDED_FOR $remote_addr;
            proxy_redirect      default;
}
}



 

在设置一个虚拟服务器在8080端口,

 

 


limit_conn_zone $proxy_add_x_forwarded_for zone=addr:10m;  # 并发设置 空间10M
server {
        listen       8080;
        server_name  [*.*.*.*/yourhostname]:8080 weight=10000; #填IP、域名
        limit_conn addr 1; #限制客户端最大并发连接数为 1
            location / {
            root   html;
            index  index.html index.htm;
        }
}



 

 

保存。接着测试配置文件语法

[lugt@localhostnginx]$  ./sbin/nginx –t

启动服务器

[lugt@localhostnginx]$  ./sbin/nginx

 

四.使用ab 工具查看效果。

[lugt@localhost nginx]$ ab –c 10 –n 100 –v 4 http://127.0.0.1/ | grep HTTP/1.1

这行的意思:通过AB测试工具访问地址,并发连接数为30,总测试300次,显示HTTP返回头信息

 

通过ab 工具可以测出无论同时发送多少连接,最后成功返回200的只有之前限制nginx的最大并发连接数,所以可以证明对于IP的限制功能已经可以使用了。参考数据见FAQ2

 

 

FAQ 1 版本问题

如果当前正在使用的Nginx版本没有达到1.7.1版本,很可能nginx还不支持这个功能,

这时候就需要通过一段代码夹在limit_conn_handler函数中来从request中取得x_forwarded_for 的值。

以1.6.1版本为例,代码增加如下。 src/http/modules/ngx_http_limit_conn.c  第184行

 

hash =ngx_crc32_short(key.data, key.len);                                                                              
If(“” == &ctx->key){                                                             
<span style="white-space:pre">	</span>If(NULL!= r->main->headers_in->x_forwarded_for->elts){                       
           key.data= *(char*)r->main->headers_in->x_forwarded_for->elts;             
         key.len = 4;                                                                                                                               
hash =ngx_crc32_short(key.data, key.len);                                                                     
<span style="white-space:pre">	</span>}                                                                                                                                                 }



                                                                                                                                                    

 

 

FAQ 2 参考数据

这里是一份参考数据, 获取

[lugt@localhost~]$ ab -c 10 -n 100 -v 4 http://127.0.0.1/ | grep HTTP/1.1

HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1 200 OK
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1 200 OK
<… repeated 往下均为重复8次HTTP/503 与1次HTTP/200 交替出现>

English Version

 

How to retrievethe true ip of the client user if there are two layers of servers

 

Days before, wehave been faced such a difficulty which is we can’t use the variable $remote_addr for gathering the clients’ip address. This problem surfaces when we used a proxy server between the trueserver and client, which is actually a cdn. And that makes our functions oflimiting the maximum connections a client can make to a server at a time. Thissituation can also found if the load balance or any anti-spam service are inuse. So that’s why we can’t use remote_addr variable further.

 

After I did someresearch on the documentation and the code , I found out that this problem canbe solved by replacing the  

$remote_addr



 variable with the  

$proxy_add_x_forwarded_for

  variable. As this variable allows to retrievethe data from the column X_forwarded_for from the request, we can use thisvariable functioning in many ways.

 

And now I shall makean easy example to practically use this method.

 

First of all,build up a Nginx server.

 

Here, I will usethe 1.7.9 version (latest to the written time) for instance, therefore, thereexist some differences between older versions than 1.7.1 (see FAQ 1)

1.      Download A Nginx Copy: 
[lugt@localhostmysql]$ wget http://nginx.org/download/nginx-1.7.9.tar.gz
2.      Decompress the file
[lugt@localhostmysql]$ tar zxvf nginx-1.7.9.tar.gz
 
3.      Compile The Code
[lugt@localhostmysql]$ cd nginx-1.7.9
[lugt@localhostnginx-1.7.9]$ ./configure 
[lugt@localhostnginx-1.7.9]$ make
[lugt@localhostnginx-1.7.9]$ su
[lugt@localhostnginx-1.7.9]$ make install 
 
4.      And edit the config file nginx.conf
 
[lugt@localhost nginx-1.7.9]$ su 
[lugt@localhostnginx-1.7.9]$ cd /usr/local/nginx
[lugt@localhostnginx]$ vi conf/nginx.conf
 
There add suchdirectives to the server1 for emulate for an CDN server

 

upstream dnsnginx1 {
        server[*.*.*.*/yourhostname]:8080 weight=1000; #fill in your ip/hostname
}
server {
        listen       80;
        server_name  [hostname]   #fill your ip/ hostname here
#access_log  logs/host.access.log  main
        location /{
           proxy_pass          http://dnsnginx1;
            proxy_set_header    Host             $host;
            proxy_set_header    X-Real-IP        $remote_addr;
            proxy_set_header    X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header    HTTP_X_FORWARDED_FOR $remote_addr;
            proxy_redirect      default;
}
}




 

After the end ofone server directive, and in the http directive, add so to function the sever2

limit_conn_zone $proxy_add_x_forwarded_for zone=addr:10m;  # sample setting
server {
        listen       8080;
        server_name  [*.*.*.*/hostname]:8080 weight=10000; #fill in ip/hostname here
        limit_conn addr 1; # Enablethe limitation of connection per ip at a time to 1.
 
            location / {
            root   html;
            index  index.html index.htm;
        }
}




 

And then you cansave , test the config file and run nginx
Test your configfile:
    [lugt@localhostnginx]$  ./sbin/nginx –t
Start the nginx server
    [lugt@localhostnginx]$  ./sbin/nginx
 
Now, the serverhas been set and you can run a test at instance.
/* This CommandMeans to run a tool to connect to server as 10conn/once and 10 conns in total*/
[lugt@localhost~]$ ab -c 10 -n 100 -v 4 http://127.0.0.1/ | grep HTTP/1.1

FAQ 1

There is actuallysome little malfunctions when using elder versions than 1.7.1 (Probably the newversion has it for a new feature).So to use this directive in earlier versions,some code need to be added.

 

As a Example inthe version 1.6.1

In filesrc/http/modules/ngx_http_limit_conn.c Line around 184

 

hash =ngx_crc32_short(key.data, key.len);                                                                             
If("" == &ctx->key){                                                             
    If(NULL!= r->main->headers_in->x_forwarded_for->elts){                       
           key.data= *(char*)r->main->headers_in->x_forwarded_for->elts;             
         key.len = 4;                                                                                                                               
hash =ngx_crc32_short(key.data, key.len);                                                                     
    }
}<span style="font-family: Arial, Helvetica, sans-serif;">           </span>



                                                                                                                                                    

 

FAQ 2 TestingResults
[lugt@localhost~]$ ab -c 10 -n 100 -v 4 http://127.0.0.1/ | grep HTTP/1.1
 
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1 200 OK
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1503 Service Temporarily Unavailable
HTTP/1.1 200 OK
<… repeated as 8times of HTTP/503 and 1 time of HTTP/200 and so on>