Linux
干货集散地

CentOS 7上安装和使用GeoIP模块 用iptables限制境外IP访问

  1. 放弃CentOS默认的firewall改为使用iptables:
sudo systemctl status firewalld.service
sudo service iptables status
sudo systemctl stop firewalld.service
sudo systemctl disable firewalld.service
sudo systemctl restart iptables.service
sudo systemctl enable iptables.service
sudo service iptables status

此时可以重启一下系统,然后查看是否正常

  1. 安装xt_geoip模块:
  • 下载xt_geoip源代码:
wget https://inai.de/files/xtables-addons/xtables-addons-2.9.tar.xz
tar xvf xtables-addons-2.9.tar.xz
  • 安装系统依赖模块
yum install gcc gcc-c++ build-essential autoconf automake
yum -y install iptables-devel
yum install build-essential
  • 编译和安装模块:
cd xtables-addons-2.9
./configure && make
sudo make install
  • 如果遇到错误“make: *** /lib/modules/3.10.0-1127.18.2.el7.x86_64/build: No such file or…”,解决办法如下:
sudo uname -r
sudo yum -y update
sudo rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
sudo rpm -Uvh https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
sudo yum -y --enablerepo=elrepo-kernel install kernel-ml
sudo awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
sudo grub2-set-default 0
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo reboot now
  • 如果遇到错误“~/xtables-addons-2.9/extensions/xt_TARPIT.c:412:10: error: too few arguments to function ‘NF_HOOK’”,解决办法:
    搜索文件xt_TARPIT.c里的所有 KERNEL_VERSION(4, 1, 0),修改为 KERNEL_VERSION(3, 10, 0)
  • 加载xt_geoip模块:
sudo modprobe xt_geoip
  1. 下载GeoIP库:
    去官网注册下载GeoLite2-Country-CSV文件:
    注册地址:https://www.maxmind.com/en/geolite2/signup?lang=en

4 将官网下载的CSV格式IP库转换为iptables可识别的格式

  • 创建格式转换脚本xt_geoip_build,代码内容如下:
#!/usr/bin/perl
#
#       Converter for MaxMind (GeoLite2) CSV database to binary, for xt_geoip
#       Copyright Jan Engelhardt, 2008-2011
#       Copyright Philip Prindeville, 2018
#
use Getopt::Long;
use Net::CIDR::Lite;
use Socket qw(AF_INET AF_INET6 inet_pton);
use warnings;
use Text::CSV_XS; # or trade for Text::CSV
use strict;

my $csv = Text::CSV_XS->new({
        allow_whitespace => 1,
        binary => 1,
        eol => $/,
}); # or Text::CSV
my $source_dir = ".";
my $quiet = 0;
my $target_dir = ".";

&Getopt::Long::Configure(qw(bundling));
&GetOptions(
        "D=s" => \$target_dir,
        "S=s" => \$source_dir,
        "q" => \$quiet,
        "s" => sub { $target_dir = "/usr/share/xt_geoip"; },
);

if (!-d $source_dir) {
        print STDERR "Source directory \"$source_dir\" does not exist.\n";
        exit 1;
}
if (!-d $target_dir) {
        print STDERR "Target directory \"$target_dir\" does not exist.\n";
        exit 1;
}

foreach (qw(LE BE)) {
        my $dir = "$target_dir/$_";
        if (!-e $dir && !mkdir($dir)) {
                print STDERR "Could not mkdir $dir: $!\n";
                exit 1;
        }
}

my %countryId;
my %countryName;
&loadCountries();
&dump(&collect());

sub loadCountries
{
        sub id; sub cc; sub long; sub ct; sub cn;

        %countryId = ();
        %countryName = ();

        my $file = "$source_dir/GeoLite2-Country-Locations-en.csv";
        print $file;
        open(my $fh, '<', $file) || die "Couldn't open list country names\n";

        # first line is headers
        my $row = $csv->getline($fh);

        my %header = map { ($row->[$_], $_); } (0..$#{$row});

        my %pairs = (
                country_iso_code => 'ISO Country Code',
                geoname_id => 'ID',
                country_name => 'Country Name',
                continent_code => 'Continent Code',
                continent_name => 'Continent Name',
        );

        # verify that the columns we need are present
        map { die "Table has no $pairs{$_} column\n" unless (exists $header{$_}); } keys %pairs;

        my %remapping = (
                id => 'geoname_id',
                cc => 'country_iso_code',
                long => 'country_name',
                ct => 'continent_code',
                cn => 'continent_name',
        );

        # now create a function which returns the value of that column #
        map { eval "sub $_ () { \$header{\$remapping{$_}}; }" ; } keys %remapping;

        while (my $row = $csv->getline($fh)) {
                if ($row->[cc] eq '' && $row->[long] eq '') {
                        $countryId{$row->[id]} = $row->[ct];
                        $countryName{$row->[ct]} = $row->[cn];
                } else {
                        $countryId{$row->[id]} = $row->[cc];
                        $countryName{$row->[cc]} = $row->[long];
                }
        }

        $countryName{A1} = 'Anonymous Proxy';
        $countryName{A2} = 'Satellite Provider';
        $countryName{O1} = 'Other Country';

        close($fh);

        # clean up the namespace
        undef &id; undef &cc; undef &long; undef &ct; undef &cn;
}

sub lookupCountry
{
        my ($id, $rid, $proxy, $sat) = @_;

        if ($proxy) {
                return 'A1';
        } elsif ($sat) {
                return 'A2';
        }
        $id ||= $rid;
        if ($id eq '') {
                return 'O1';
        }
        die "Unknown id: $id line $.\n" unless (exists $countryId{$id});
        return $countryId{$id};
}

sub collect
{
        my ($file, $fh, $row);
        my (%country, %header);

        sub net; sub id; sub rid; sub proxy; sub sat;

        my %pairs = (
                network => 'Network',
                registered_country_geoname_id => 'Registered Country ID',
                geoname_id => 'Country ID',
                is_anonymous_proxy => 'Anonymous Proxy',
                is_satellite_provider => 'Satellite',
        );

        foreach (sort keys %countryName) {
                $country{$_} = {
                        name => $countryName{$_},
                        pool_v4 => Net::CIDR::Lite->new(),
                        pool_v6 => Net::CIDR::Lite->new(),
                };
        }

        $file = "$source_dir/GeoLite2-Country-Blocks-IPv4.csv";
        open($fh, '<', $file) || die "Can't open IPv4 database\n";

        # first line is headers
        $row = $csv->getline($fh);

        %header = map { ($row->[$_], $_); } (0..$#{$row});

        # verify that the columns we need are present
        map { die "Table has no %pairs{$_} column\n" unless (exists $header{$_}); } keys %pairs;

        my %remapping = (
                net => 'network',
                id => 'geoname_id',
                rid => 'registered_country_geoname_id',
                proxy => 'is_anonymous_proxy',
                sat => 'is_satellite_provider',
        );

        # now create a function which returns the value of that column #
        map { eval "sub $_ () { \$header{\$remapping{$_}}; }" ; } keys %remapping;

        while ($row = $csv->getline($fh)) {
                my ($cc, $cidr);

                $cc = lookupCountry($row->[id], $row->[rid], $row->[proxy], $row->[sat]);
                $cidr = $row->[net];
                $country{$cc}->{pool_v4}->add($cidr);

                if ($. % 4096 == 0) {
                        print STDERR "\r\e[2K$. entries";
                }
        }

        print STDERR "\r\e[2K$. entries total\n";

        close($fh);

        # clean up the namespace
        undef &net; undef &id; undef &rid; undef &proxy; undef &sat;

        $file = "$source_dir/GeoLite2-Country-Blocks-IPv6.csv";
        open($fh, '<', $file) || die "Can't open IPv6 database\n";

        # first line is headers
        $row = $csv->getline($fh);

        %header = map { ($row->[$_], $_); } (0..$#{$row});

        # verify that the columns we need are present
        map { die "Table has no %pairs{$_} column\n" unless (exists $header{$_}); } keys %pairs;

        # unlikely the IPv6 table has different columns, but just to be sure
        # create a function which returns the value of that column #
        map { eval "sub $_ () { \$header{\$remapping{$_}}; }" ; } keys %remapping;

        while ($row = $csv->getline($fh)) {
                my ($cc, $cidr);

                $cc = lookupCountry($row->[id], $row->[rid], $row->[proxy], $row->[sat]);
                $cidr = $row->[net];
                $country{$cc}->{pool_v6}->add($cidr);

                if (!$quiet && $. % 4096 == 0) {
                        print STDERR "\r\e[2K$. entries";
                }
        }

        print STDERR "\r\e[2K$. entries total\n" unless ($quiet);

        close($fh);

        # clean up the namespace
        undef &net; undef &id; undef &rid; undef &proxy; undef &sat;

        return \%country;
}

sub dump
{
        my $country = shift @_;

        foreach my $iso_code (sort keys %{$country}) {
                &dump_one($iso_code, $country->{$iso_code});
        }
}

sub dump_one
{
        my($iso_code, $country) = @_;
        my($file, $fh_le, $fh_be);

        printf "%5u IPv6 ranges for %s %s\n",
                scalar(@{$country->{pool_v6}->list_range()}),
                $iso_code, $country->{name};

        $file = "$target_dir/LE/".uc($iso_code).".iv6";
        if (!open($fh_le, "> $file")) {
                print STDERR "Error opening $file: $!\n";
                exit 1;
        }
        $file = "$target_dir/BE/".uc($iso_code).".iv6";
        if (!open($fh_be, "> $file")) {
                print STDERR "Error opening $file: $!\n";
                exit 1;
        }
        foreach my $range (@{$country->{pool_v6}->list_range()}) {
                my ($start, $end) = split('-', $range);
                print $fh_be &ip6_pack($start), &ip6_pack($end);
                print $fh_le &ip6_swap($start), &ip6_swap($end);
        }
        close $fh_le;
        close $fh_be;

        printf "%5u IPv4 ranges for %s %s\n",
                scalar(@{$country->{pool_v4}->list_range()}),
                $iso_code, $country->{name};

        $file = "$target_dir/LE/".uc($iso_code).".iv4";
        if (!open($fh_le, "> $file")) {
                print STDERR "Error opening $file: $!\n";
                exit 1;
        }
        $file = "$target_dir/BE/".uc($iso_code).".iv4";
        if (!open($fh_be, "> $file")) {
                print STDERR "Error opening $file: $!\n";
                exit 1;
        }
        my($sc1, $sc2, $sc3, $sc4);
        my($start, $end);
        foreach my $range (@{$country->{pool_v4}->list_range()}) {
                ($start, $end) = split('-', $range);
                ($sc1, $sc2, $sc3, $sc4) = split('\.', $start);
                $start = $sc1 * 256**3 + $sc2 * 256**2 + $sc3 * 256 + $sc4;
                ($sc1, $sc2, $sc3, $sc4) = split('\.', $end);
                $end = $sc1 * 256**3 + $sc2 * 256**2 + $sc3 * 256 + $sc4;
                print $fh_le pack("VV", $start, $end);
                print $fh_be pack("NN", $start, $end);
        }
        close $fh_le;
        close $fh_be;
}

sub ip6_pack
{
        my $addr = shift @_;
        $addr =~ s{::}{:!:};
        my @addr = split(/:/, $addr);
        my @e = (0) x 8;
        foreach (@addr) {
                if ($_ eq "!") {
                        $_ = join(':', @e[0..(8-scalar(@addr))]);
                }
        }
        @addr = split(/:/, join(':', @addr));
        $_ = hex($_) foreach @addr;
        return pack("n*", @addr);
}

sub ip6_swap
{
        return pack("V*", unpack("N*", shift @_));
}
  • 加上执行权限
chmod +x xt_geoip_build 

5 安装xt_geoip_build脚本所需要的依赖

cd ~
yum -y install perl-Text-CSV_XS
wget https://dl.fedoraproject.org/pub/epel/7/aarch64/Packages/p/perl-Net-CIDR-Lite-0.21-11.el7.noarch.rpm
rpm -ivh perl-Net-CIDR-Lite-0.21-11.el7.noarch.rpm --force --nodeps
cd /usr/share/
mkdir xt_geoip
cd ~
unzip GeoLite2-Country-CSV_20230714.zip
./xt_geoip_build -D /usr/share/xt_geoip -S GeoLite2-Country-CSV_20230714
  1. 创建新的iptables安全规则
iptables -A INPUT -p tcp --syn -m limit --limit 5/second -j ACCEPT
iptables -I INPUT -m geoip --src-cc US -j DROP
iptables -I INPUT -m geoip --src-cc FR -j DROP
iptables-save > /etc/sysconfig/iptables
  • 看看结果
cat /etc/sysconfig/iptables
iptables -L -n -v
赞(1) 打赏

评论 抢沙发

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏