From cdbaa413646d57140fe33f6cf1b1b2cb06b8ec9b Mon Sep 17 00:00:00 2001 From: Vincent Li Date: Fri, 4 Oct 2024 02:06:31 +0000 Subject: [PATCH] xdp-dns UI: web interface to add XDP DNS blocklist Signed-off-by: Vincent Li --- config/menu/60-ipfire.menu | 5 + config/rootfiles/common/web-user-interface | 1 + html/cgi-bin/xdpdns.cgi | 432 +++++++++++++++++++++ langs/en/cgi-bin/en.pl | 7 + langs/zh/cgi-bin/zh.pl | 7 + 5 files changed, 452 insertions(+) create mode 100644 html/cgi-bin/xdpdns.cgi diff --git a/config/menu/60-ipfire.menu b/config/menu/60-ipfire.menu index 5099e56f9..0dd2bb886 100644 --- a/config/menu/60-ipfire.menu +++ b/config/menu/60-ipfire.menu @@ -3,6 +3,11 @@ 'title' => "$Lang::tr{'ebpf xdp ddos system'}", 'enabled' => 1, }; + $subipfire->{'15.xdpdns'} = {'caption' => $Lang::tr{'xdpdns domain'}, + 'uri' => '/cgi-bin/xdpdns.cgi', + 'title' => "$Lang::tr{'xdpdns domain'}", + 'enabled' => 1, + }; $subipfire->{'20.loxilb'} = { 'caption' => $Lang::tr{'loxilb enable'}, 'uri' => '/cgi-bin/loxilb.cgi', diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface index 47a8a9d1e..ea4b4e55b 100644 --- a/config/rootfiles/common/web-user-interface +++ b/config/rootfiles/common/web-user-interface @@ -95,6 +95,7 @@ srv/web/ipfire/cgi-bin/loxilb.cgi srv/web/ipfire/cgi-bin/loxilbconfig.cgi srv/web/ipfire/cgi-bin/loxilbfw.cgi srv/web/ipfire/cgi-bin/keepalived.cgi +srv/web/ipfire/cgi-bin/xdpdns.cgi #srv/web/ipfire/html srv/web/ipfire/html/blob.gif #srv/web/ipfire/html/captive diff --git a/html/cgi-bin/xdpdns.cgi b/html/cgi-bin/xdpdns.cgi new file mode 100644 index 000000000..c9517ee11 --- /dev/null +++ b/html/cgi-bin/xdpdns.cgi @@ -0,0 +1,432 @@ +#!/usr/bin/perl +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2020 IPFire Team # +# Copyright (C) 2024 BPFire # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +use strict; + +# enable only the following on debugging purpose +use warnings; +use CGI::Carp 'fatalsToBrowser'; + +use IO::Socket; + +require '/var/ipfire/general-functions.pl'; +require "${General::swroot}/location-functions.pl"; +require "${General::swroot}/lang.pl"; +require "${General::swroot}/header.pl"; + +#workaround to suppress a warning when a variable is used only once +my @dummy = ( ${Header::colouryellow} ); +undef (@dummy); + +my %color = (); +my %mainsettings = (); +my %settings=(); +my %checked=(); +my $errormessage=''; +my $setting = "${General::swroot}/main/settings"; +my $xdpdnssettingfile = "${General::swroot}/xdpdns/settings"; +my $xdpdnsdomainfile = "${General::swroot}/xdpdns/domainfile"; + +# Read configuration file. + +&General::readhash("${General::swroot}/main/settings", \%mainsettings); +&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); + +&Header::showhttpheaders(); + +$settings{'ENABLE_DNSBLOCK'} = 'off'; +$settings{'ACTION'} = ''; + +$settings{'KEY1'} = ''; # point record for ACTION +$settings{'domainName'} = ''; +my @nosaved=('domainName', 'KEY1', 'SORT_domainNameLIST'); + +#Define each field that can be used to sort columns +my $sortstring='^domainName'; +$settings{'SORT_domainNameLIST'} = 'domainName'; + +# Load multiline data +our @current = (); +if (open(FILE, "$xdpdnsdomainfile")) { + @current = ; + close (FILE); +} + +&Header::getcgihash(\%settings); + +if ($settings{'ACTION'} eq $Lang::tr{'save'}) +{ + + map (delete ($settings{$_}) ,(@nosaved)); + &General::writehash("$xdpdnssettingfile", \%settings); + + if ($settings{'ENABLE_DNSBLOCK'} eq 'on') { + &General::system('/usr/bin/touch', "${General::swroot}/xdpdns/enablexdpdns"); + &General::system('/usr/local/bin/xdpdnsctrl', 'start'); + } else { + &General::system('/usr/local/bin/xdpdnsctrl', 'stop'); + unlink "${General::swroot}/xdpdns/enablexdpdns"; + } + +} + +if ($settings{'ACTION'} eq $Lang::tr{'add'}) { + + #Check for already existing routing entry + foreach my $line (@current) { + chomp($line); # remove newline + #Same domain already used? + if($line eq $settings{'domainName'} && $settings{'KEY1'} eq ''){ + $errormessage = $Lang::tr{'ccd err loxilbconfigeexist'}; + last; + } + } + + unless ($errormessage) { + if ($settings{'KEY1'} eq '') { #add or edit ? + unshift (@current, "$settings{'domainName'}\n"); + &General::log($Lang::tr{'xdpdns domain added'}); + } else { + @current[$settings{'KEY1'}] = "$settings{'domainName'}\n"; + $settings{'KEY1'} = ''; # End edit mode + &General::log($Lang::tr{'xdpdns domain changed'}); + } + + &CreateDomain(%settings); + + # Write changes to config file. + &SortDataFile; # sort newly added/modified entry + + #map ($settings{$_}='' ,@nosaved); # Clear fields + } +} + +if ($settings{'ACTION'} eq $Lang::tr{'remove'}) { + + my $line = @current[$settings{'KEY1'}]; # KEY1 is the index in current + chomp($line); + $settings{'domainName'}=$line; + + &DeleteDomain(%settings); + + splice (@current,$settings{'KEY1'},1); # Delete line + open(FILE, ">$xdpdnsdomainfile") or die "$xdpdnsdomainfile open error"; + print FILE @current; + close(FILE); + $settings{'KEY1'} = ''; # End remove mode +} + +## Check if sorting is asked +# If same column clicked, reverse the sort. +if ($ENV{'QUERY_STRING'} =~ /$sortstring/ ) { + my $newsort=$ENV{'QUERY_STRING'}; + my $actual=$settings{'SORT_domainNameLIST'}; + #Reverse actual sort ? + if ($actual =~ $newsort) { + my $Rev=''; + if ($actual !~ 'Rev') { + $Rev='Rev'; + } + $newsort.=$Rev; + } + $settings{'SORT_domainNameLIST'}=$newsort; + map (delete ($settings{$_}) ,(@nosaved,'ACTION','KEY1'));# Must never be saved + &General::writehash($setting, \%settings); + &SortDataFile; + $settings{'ACTION'} = 'SORT'; # Create an 'ACTION' + map ($settings{$_} = '' ,@nosaved,'KEY1'); # and reinit vars to empty +} + +if ($settings{'ACTION'} eq '' ) { # First launch from GUI + # Place here default value when nothing is initialized + $settings{'domainName'} = ''; +} + +&Header::openpage($Lang::tr{'xdpdns'}, 1, ''); + +&Header::openbigbox('100%', 'left', '', $errormessage); + +if ($errormessage) { + &Header::openbox('100%', 'left', $Lang::tr{'error messages'}); + print "$errormessage \n"; + &Header::closebox(); +} + +# Read configuration file. +&General::readhash("$xdpdnssettingfile", \%settings); + +# Checkbox pre-selection. +my $checked; +if ($settings{'ENABLE_DNSBLOCK'} eq "on") { + $checked = "checked='checked'"; +} + +my $sactive = "
$Lang::tr{'stopped'}
"; + +my @status = &General::system_output('/usr/local/bin/xdpdnsctrl', 'status'); + +if (grep(/is running/, @status)){ + $sactive = "
$Lang::tr{'running'}
"; +} + +&Header::openbox('100%', 'center', $Lang::tr{'xdpdns status'}); + +print < +
+   +   +   + $Lang::tr{'xdpdns status'} + $sactive + + + $Lang::tr{'xdpdns enable'} + + + +END + +print "
\n"; + +&Header::closebox(); +# + +my $buttontext = $Lang::tr{'add'}; +if ($settings{'KEY1'} ne '') { + $buttontext = $Lang::tr{'update'}; + &Header::openbox('100%', 'left', $Lang::tr{'xdpdns domain edit'}); +} else { + &Header::openbox('100%', 'left', $Lang::tr{'xdpdns domain add'}); +} + +my @INTERFACES = ("red0", "green0"); + +#Edited line number (KEY1) passed until cleared by 'save' or 'remove' or 'new sort order' +print < + + + + + + + +
$Lang::tr{'xdpdns domain name'}: 
+
+ + + + +
+ +END + +&Header::closebox(); + +&Header::openbox('100%', 'left', $Lang::tr{'xdpdns domain entries'}); + +print < + + $Lang::tr{'xdpdns domain name'} + $Lang::tr{'action'} + +END + +# +# Print each line of @current list +# + +my $key = 0; +my $col=""; +foreach my $line (@current) { + chomp($line); # remove newline + + #Choose icon for checkbox + my $gif = ''; + my $gdesc = ''; + if ($line ne '' ) { + $gif = 'on.gif'; + $gdesc = $Lang::tr{'click to disable'}; + } else { + $gif = 'off.gif'; + $gdesc = $Lang::tr{'click to enable'}; + } + + #Colorize each line + if ($settings{'KEY1'} eq $key) { + print ""; + } elsif ($key % 2) { + print ""; + $col="bgcolor='$color{'color20'}'"; + } else { + print ""; + $col="bgcolor='$color{'color22'}'"; + } + print <$line + +
+ + + +
+ + + +
+ + + +
+ + +END + + $key++; +} +print ""; + +# If table contains entries, print 'Key to action icons' +if ($key) { +print < + +  $Lang::tr{'legend'}:  + $Lang::tr{ + $Lang::tr{'click to disable'} +    + $Lang::tr{ + $Lang::tr{'click to enable'} +    + $Lang::tr{ + $Lang::tr{'remove'} + + +END +} + +&Header::closebox(); + +&Header::closebigbox(); + +&Header::closepage(); + + +## Ouf it's the end ! + +# Sort the "current" array according to choices +sub SortDataFile +{ + our %entries = (); + + # Sort pair of record received in $a $b special vars. + # When IP is specified use numeric sort else alpha. + # If sortname ends with 'Rev', do reverse sort. + # + sub fixedleasesort { + my $qs=''; # The sort field specified minus 'Rev' + if (rindex ($settings{'SORT_domainNameLIST'},'Rev') != -1) { + $qs=substr ($settings{'SORT_domainNameLIST'},0,length($settings{'SORT_domainNameLIST'})-3); + if ($qs eq 'domainName') { + my @a = split(/\./,$entries{$a}->{$qs}); + my @b = split(/\./,$entries{$b}->{$qs}); + ($b[0]<=>$a[0]) || + ($b[1]<=>$a[1]) || + ($b[2]<=>$a[2]) || + ($b[3]<=>$a[3]); + } else { + $entries{$b}->{$qs} cmp $entries{$a}->{$qs}; + } + } else { #not reverse + $qs=$settings{'SORT_domainNameLIST'}; + if ($qs eq 'domainName') { + my @a = split(/\./,$entries{$a}->{$qs}); + my @b = split(/\./,$entries{$b}->{$qs}); + ($a[0]<=>$b[0]) || + ($a[1]<=>$b[1]) || + ($a[2]<=>$b[2]) || + ($a[3]<=>$b[3]); + } else { + $entries{$a}->{$qs} cmp $entries{$b}->{$qs}; + } + } + } + + #Use an associative array (%entries) + my $key = 0; + foreach my $line (@current) { + chomp( $line); #remove newline because can be on field 5 or 6 (addition of REMARK) + + # Build a pair 'Field Name',value for each of the data dataline. + # Each SORTABLE field must have is pair. + # Other data fields (non sortable) can be grouped in one + + my @record = ('KEY',$key++,'domainName',$line); + my $record = {}; # create a reference to empty hash + %{$record} = @record; # populate that hash with @record + $entries{$record->{KEY}} = $record; # add this to a hash of hashes + } + + open(FILE, ">$xdpdnsdomainfile") or die "$xdpdnsdomainfile open error"; + + # Each field value is printed , with the newline ! Don't forget separator and order of them. + foreach my $entry (sort fixedleasesort keys %entries) { + print FILE "$entries{$entry}->{domainName}\n"; + } + + close(FILE); + # Reload sorted @current + open (FILE, "$xdpdnsdomainfile"); + @current = ; + close (FILE); +} + +sub manageDomain { + my ($action, %settings) = @_; + + # Initialize variables + my @xdpdns_options; + my $command = 'xdp_dns'; + + my $domain = $settings{'domainName'}; + + push(@xdpdns_options, $action, $domain); + + #debug and display output in UI + #my @output = &General::system_output($command, @xdpdns_options); + #$errormessage = join('', @output); + &General::system($command, @xdpdns_options); + +} + +sub CreateDomain { + my (%settings) = @_; + manageDomain("add", %settings); +} + +sub DeleteDomain { + my (%settings) = @_; + manageDomain("delete", %settings); +} diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 2b4dfc0ff..1dfac8796 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -2509,6 +2509,13 @@ 'keepalived auth pass' => 'Auth Pass', 'keepalived unicast peer' => 'Unicast Peer', 'keepalived virtual address' => 'Virtual Address', +'xdpdns status' => 'Status', +'xdpdns enable' => 'Enable XDP DNS Deny', +'xdpdns domain' => 'XDP DNS Blocklist', +'xdpdns domain edit' => 'Edit Domain', +'xdpdns domain add' => 'Add Domain', +'xdpdns domain name' => 'Domain Name', +'xdpdns domain entries' => 'Domain Deny Entries', 'status' => 'Status', 'status information' => 'Status information', 'status ovpn' => 'OpenVPN', diff --git a/langs/zh/cgi-bin/zh.pl b/langs/zh/cgi-bin/zh.pl index 0b3647645..9e258a83d 100644 --- a/langs/zh/cgi-bin/zh.pl +++ b/langs/zh/cgi-bin/zh.pl @@ -2474,6 +2474,13 @@ 'keepalived auth pass' => '认证密码', 'keepalived unicast peer' => '单播同伴设备', 'keepalived virtual address' => '虚拟 IP地址', +'xdpdns status' => '运行状态', +'xdpdns enable' => '启动', +'xdpdns domain' => 'eBPF XDP 域名过滤服务', +'xdpdns domain edit' => '编辑域名', +'xdpdns domain add' => '添加域名', +'xdpdns domain name' => '域名', +'xdpdns domain entries' => '域名过滤列表', 'status' => '状态', 'status information' => '状态信息', 'status ovpn' => 'OpenVPN',