Главная
Главная Руководства FreeBSD › Геолокация в nginx



Автор:

Статья опубликована: 2015-04-18 20:48:51
Последние правки: 2015-07-16 15:56:01

Добавляем в nginx геолокацию (geoip).

Одна из самых привлекательных по моему мнению функций в nginx - это возможность определения страны, города по ip-адресу посетителя. Эта функция называется геолокация (geoip).

  • Базы с данными
    В интернете есть два достойных упоминания сайта с базами для геолокации. Первый - это, конечно, www.maxmind.com где есть два варианта баз - платные и бесплатные. Второй - www.ipgeobase.ru. Я предпочитаю использовать данные с обеих сайтов, данные для России и Украины брать с ipgeobase (по идее, у них по этим странам данные должны быть точнее), а для остальных стран - с maxmind. Пишем скрипт для скачивания архивов с данными:
    #!/bin/sh
    wget http://ipgeobase.ru/files/db/Main/geo_files.zip
    wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City-CSV.zip
    
    Теперь необходимо переконвертировать данные в формат, который можно скормить nginx. Создаем директорию convert и распаковываем туда файлы из архивов. Для конвертации я написал на perl пару скриптов.

    1_ipgeobase.pl - создаем файл для nginx из файлов с ipgeobase. Тащим данные только для России и Украины.
    #!/usr/bin/perl -w
    # 1_ipgeobase.pl
    #-------------------------------------------------------------------------------
    # convert ipgeobase.ru txt files for use with nginx geo
    #-------------------------------------------------------------------------------
    # (c) Skull 2015
    #-------------------------------------------------------------------------------
    use strict;
    use warnings;
    use utf8;
    use open qw/:std :utf8/;
    use Net::CIDR;
    $|=1;
    
    my %ids;
    my(@tr) = ('А,а,a','Б,б,b','В,в,v','Г,г,g','Д,д,d','Е,е,ye','Ё,ё,jo','Ж,ж,zg','З,з,zh','И,и,i','Й,й,i','К,к,k','Л,л,l','М,м,m','Н,н,n','О,о,o','П,п,p','Р,р,r','С,с,s','Т,т,t','У,у,u','Ф,ф,f','Х,х,h','Ц,ц,ts','Ч,ч,ch','Ш,ш,sh','Щ,щ,shh','Ь,ь,','Ы,ы,y','Ъ,ъ,','Э,э,je','Ю,ю,ju','Я,я,ja');
    
    
    open (local *IN,'<:encoding(cp1251)','cities.txt') or die "Can't open cities.txt";
    while (my $s = ) {
            my ($id,$city,$region,$district) = split(/\t/,$s);
            $ids{$id} = $city if $city;
    }
    close IN;
    
    
    open (local *OUT0,'>','geo_country_ipgeobase.conf');
    open (local *OUT1,'>','geo_city_ipgeobase.conf');
    
    
    open (local *IN,'<:encoding(cp1251)','cidr_optim.txt') or die "Can't open cidr_optim.txt";
    while (my $s = ) {
            chomp $s;
            my ($start,$stop,$inetnum,$country,$id) = split(/\t/,$s);
    
            $inetnum =~ s/ //g;
            foreach my $cidr(Net::CIDR::range2cidr($inetnum)) {
                    if(defined $country and ($country eq 'RU' or $country eq 'UA')){
                            print $cidr,"\n";
                            print OUT0 "$cidr $country;\n";
                            if(exists $ids{$id}){
                                    my $city = ucfirst(translate($ids{$id}));
                                    if($city eq 'Moskva'){ $city = 'Moscow'; }
                                    $city =~ s/(-)(\w)(.+?)/$1\u$2$3/g;
                                    $city =~ s/( )(\w)(.+?)/$1\u$2$3/g;
                                    $city =~ s/\"//g;
                                    $city =~ s/\'//g;
                                    if($city =~ / /){ $city = '"'.$city.'"'; }
                                    print OUT1 "$cidr $city;\n";
                            }
                    }
            }
    }
    close IN;
    close OUT0;
    close OUT1;
    
    print "Done for ipgeobase.ru\n";
    
    exit;
    
    sub translate{
            my($i) = @_;
            foreach my $x(@tr){
                    my($x0,$x1,$x2) = split ',',$x;
                    $i =~ s/$x0/$x2/g;
                    $i =~ s/$x1/$x2/g;
            }
            return $i;
    }
    
    Net::CIDR - этот пакет тащим с сайта search.cpan.org, создаем в convert директорию Net и туда помещаем CIDR.pm.
    После отработки скрипта получим два файла: geo_city_ipgeobase.conf и geo_country_ipgeobase.conf.

    2_maxmind.pl - создаем файл для nginx из файлов с maxmind. Тащим данные для всех стран кроме России и Украины.
    #!/usr/bin/perl -w
    # 2_maxmind.pl
    #-------------------------------------------------------------------------------
    # convert MaxMind GeoLite2-City CSV for use with nginx geo
    #-------------------------------------------------------------------------------
    # (c) Skull 2015
    #-------------------------------------------------------------------------------
    use strict;
    use warnings;
    use utf8;
    use open qw/:std :utf8/;
    $|=1;
    
    my(%city,%country,%net);
    
    
    print "Read geo_country_ipgeobase.conf...";
    open(local *IN,'<','geo_country_ipgeobase.conf') or die "Can't open geo_country_ipgeobase.conf";
    my $y = ;
    while (my $in = ){
            my($i0,$i1) = split ' ',$in,2;
            $net{$i0} = 1;
    }
    close IN;
    print " done\n";
    
    # parse MaxMind files
    
    print "Read GeoLite2-City-Blocks-IPv4.csv...";
    my($count_mm,$count,$count_s) = (0,0,0);
    open(local *IN,'<','GeoLite2-City-Blocks-IPv4.csv') or die "Can't open GeoLite2-City-Blocks-IPv4.csv";
    $y = ;
    while (my $in = ){ $count_mm++; }
    close IN;
    print " done\n";
    
    
    open(local *IN,'<','GeoLite2-City-Locations-en.csv') or die "Can't open GeoLite2-City-Locations-en.csv";
    my $x = ;
    while (my $in = ){
            chomp $in;
            my(@x) = split ',',$in;
            if($x[10]){ $city{$x[0]} = $x[10]; }
            if($x[4]){ $country{$x[0]} = $x[4]; }
    }
    close IN;
    
    open IN,'<','GeoLite2-City-Blocks-IPv4.csv' or die "Can't open GeoLite2-City-Blocks-IPv4.csv";
    open OUT2,'>','geo_country_maxmind.conf' or die "Can't open geo_country_maxmind.conf";
    open OUT3,'>','geo_city_maxmind.conf' or die "geo_city_maxmind.conf";
    
    $x = ;
    while (my $in = ){
            chomp $in;
            my(@x) = split ',',$in;
    
            if(!$net{$x[0]}){
    
                    if(exists $country{$x[1]} and $country{$x[1]} ne 'RU' and $country{$x[1]} ne 'UA'){
                            if($country{$x[1]} =~ / /){ $country{$x[1]} = '"'.$country{$x[1]}.'"'; }
                            print OUT2 $x[0],"\t",$country{$x[1]},";\n";
    
                            if(exists $city{$x[1]}){
                                    $city{$x[1]} =~ s/\"//g;
                                    $city{$x[1]} =~ s/\'//g;
                                    if($city{$x[1]} =~ / /){ $city{$x[1]} = '"'.$city{$x[1]}.'"'; }
                                    print OUT3 $x[0],"\t",$city{$x[1]},";\n";
                            }
    
                            $count++;
                            $count_s++;
                            if($count_s >= 100000){
                                    print "$count / $count_mm\n";
                                    $count_s=0;
                            }
                    }
            }
    }
    close IN;
    close OUT2;
    close OUT3;
    
    print "$count / $count_mm\n";
    print "Done for maxmind.com\n";
    
    exit;
    
    После отработки скрипта получим два файла: geo_city_maxmind.conf и geo_country_maxmind.conf.

    Создаем директорию
    # mkdir /usr/local/etc/nginx/geoip
    
    Переносим туда четыре файла: geo_city_ipgeobase.conf, geo_city_maxmind.conf, geo_country_ipgeobase.conf, geo_country_maxmind.conf

    На этом создание файлов с geo-данными для nginx закончено. Эту операцию необходимо проделывать каждый раз когда обновляются файлы с данными на сайтах ipgeobase и maxmind.

    Создаем файл /usr/local/etc/nginx/geoip/bad_countries.conf. В этом файле хранятся данные по странам, которым запрещен доступ к сайтам web-сервера. Формат файла:
    US      0;
    CH      1;
    IN      1;
    
    1 - посетителю из этой страны запрещен доступ.


  • Конфигурация nginx
    /usr/local/etc/nginx/nginx.conf
    http {
    
        geo $ip2city {
            default UNKNOWN;
            192.168.1.0/24  My City;
            127.0.0.1/32    My City;
            include geoip/geo_city_ipgeobase.conf;
            include geoip/geo_city_maxmind.conf;
        }
        geo $ip2country {
            default ZZ;
            192.168.1.0/24  RU;
            127.0.0.1/32    RU;
            127.0.0.0/24    US;
            include geoip/geo_country_ipgeobase.conf;
            include geoip/geo_country_maxmind.conf;
        }
        map $ip2country $bad_country {
            default 0;
            include geoip/bad_countries.conf;
        }
    
    }
    
    • /usr/local/etc/nginx/sites/mysite.conf - сайт, который обслуживает nginx.
      Вариант 1, если геоданные нужны в пределах server. Nginx должен быть собран с поддержкой 3rd party headers_more module (опция HEADERS_MORE в конфиге FreeBSD порта) иначе more_set_input_headers работать не будет.
      server {
          more_set_input_headers 'X-Geoip-City-Name: $ip2city';
          more_set_input_headers 'X-Geoip-Country-Code: $ip2country';
      
          if ($bad_country){
              return 444;
          }
      }
      
      Вариант 2, если геоданные нужны только скриптам. Передаем через fastcgi без всяких дополнительных модулей. Параметры задаются в location /cgi-bin/.
      server {
          location ~ /cgi-bin/ {
              fastcgi_param HTTP_X_GEOIP_CITY_NAME $ip2city;
              fastcgi_param HTTP_X_GEOIP_COUNTRY_CODE $ip2country;
          }
          if ($bad_country){
              return 444;
          }
      }
      
      if ($bad_country){...} - здесь задается реакция сайта на список "плохих" стран. Для посетителей из запрещенных стран возвращается ответ HTTP 444.

    • /usr/local/etc/nginx/sites/proxysite.conf - прокси для сайта на другом веб-сервере. В файле /usr/local/etc/nginx/proxy_vars.conf должны быть строчки:
      proxy_set_header        X-Geoip-City-Name               $ip2city;
      proxy_set_header        X-Geoip-Country-Code            $ip2country;
      

  • Применить изменения.
    Если не добавлялось новых сайтов, то можно просто перечитать конфиг
    # /usr/local/etc/rc.d/nginx reload
    
    Иначе необходима полная перезагрузка
    # /usr/local/etc/rc.d/nginx restart