costales / gufw

Linux Firewall
GNU General Public License v3.0
139 stars 32 forks source link

Support for GNU hostname command #42

Open devansh08 opened 2 years ago

devansh08 commented 2 years ago

I noticed on clicking the "Paste your current local IP" the command hostname --all-ip-addresses is run. But with the GNU version of the hostname that I have pre installed on Arch Linux does not have this option. Instead it uses --ip-addresses option. So I've added a check for valid IP addresses that the command returns (as for me the command returned an error message string) and some logic to try the GNU version's option before falling back to 127.0.0.1.

Lemme know your thoughts. Thanks !

FYR, some info on the GNU version of hostname ``` hostname --help Usage: hostname [OPTION...] [NAME] Show or set the system's host name. -a, --aliases alias names -d, --domain DNS domain name -f, --fqdn, --long DNS host name or FQDN -F, --file=FILE set host name or NIS domain name from FILE -i, --ip-addresses addresses for the host name -s, --short short host name -y, --yp, --nis NIS/YP domain name -?, --help give this help list --usage give a short usage message -V, --version print program version Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options. Report bugs to hostname --version hostname (GNU inetutils) 2.2 Copyright (C) 2021 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by Debarshi Ray. ```
costales commented 2 years ago

Hi, Thank you so much. I need to review Ubuntu daily for this. I'll come back. Best regards.

costales commented 2 years ago

Hi,

As you can see, in Ubuntu 22.04 is working fine, even the help is right.

I'll leave the merge propose for the future, but I'll not merge right now.

1

2

Best regards and thank you so much for the help!

devansh08 commented 2 years ago

Glad to know it works well. Thanks for taking a look! Hope it makes it in the next releases :)

a13ssandr0 commented 2 years ago

@costales I saw there is this library netifaces (unfortunately it is now unmaintained but still works on Ubuntu 22.04) that can retrieve IP addresses on all platforms without using system commands at all.

In ufw_backend.py I imported from netifaces import interfaces, ifaddresses, AF_INET, and then edited get_net_ip this way:

def get_net_ip(self):
    return ' '.join([i['addr'] for ifaceName in interfaces() for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':None}]) if i['addr'] and not i['addr'].startswith('127.')])

By excluding loopback ips in subnet 127.0.0.0/8 (127.0.0.1 - 127.255.255.254) and interfaces that have no IP address we can reproduce hostname --all-ip-addresses output without checking if we are on a specific system.

@devansh08 can you confirm this works in Arch Linux too?

devansh08 commented 2 years ago

@a13ssandr0 Yes this does work on Arch for me :) But I don't think ' '.join should be done since systems having multiple network interfaces will return an array and this will give a space separated set of IPs. In my case it gave my WLAN interface IP and docker's default network interface IP. I think you can just use whatever is at the first index in the array.

Coming to the point of using netifaces, not sure if it would be a good idea to use since it is unmaintained right now (but that's @costales decision to make).

Although based on your idea (great idea btw :+1: ) I found another way to get IP addresses using the socket library (which is built-in for python3). This works for me on Arch, can you see if something like this works on Ubuntu for you ?

import socket

def get_net_ip(self):
    return socket.gethostbyname(socket.getfqdn())
a13ssandr0 commented 2 years ago

@a13ssandr0 Yes this does work on Arch for me :) But I don't think ' '.join should be done since systems having multiple network interfaces will return an array and this will give a space separated set of IPs. In my case it gave my WLAN interface IP and docker's default network interface IP. I think you can just use whatever is at the first index in the array.

@devansh08 you're right, using ' '.join isn't the right choice but I found that it perfectly reproduces hostname --all-ip-addresses output (even though ufw only accepts one IP per rule, but this part could be a bit off-topic).

Here you can see my physical network interface has two IPs, while the third belongs to virsh's virtual card.

#bash
$ hostname --all-ip-addresses
192.168.1.50 192.168.0.5 192.168.122.1 
#python
>>> from netifaces import interfaces, ifaddresses, AF_INET
>>> ' '.join([i['addr'] for ifaceName in interfaces() for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':None}]) if i['addr'] and not i['addr'].startswith('127.')])
'192.168.1.50 192.168.0.5 192.168.122.1'

Although based on your idea (great idea btw +1 ) I found another way to get IP addresses using the socket library (which is built-in for python3). This works for me on Arch, can you see if something like this works on Ubuntu for you ?

Good idea using built-ins, but your code only returns '127.0.1.1' (because it only relies on /etc/hosts that gives that IP for my hostname) so I looked online and found this solution that also uses socket but tries to connect to the internet to get the actual IP used for routing:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))    #you could also use `('<broadcast>', 0)` instead of Google DNS
print(s.getsockname()[0])
s.close()
devansh08 commented 2 years ago

Good idea using built-ins, but your code only returns '127.0.1.1' (because it only relies on /etc/hosts that gives that IP for my hostname) so I looked online and found this solution that also uses socket but tries to connect to the internet to get the actual IP used for routing:

Oh... My /etc/hosts file currently only has 127.0.0.1 localhost so for me it worked (not sure where exactly I have mapped my hostname arch to my dynamic LAN IP). I also did find the code I shared in the same StackOverflow thread :) Didn't think the 8.8.8.8 solution would be great since it needs to be able to connect to external IPs which might not always be possible for a machine.

Another similar solution from the same thread:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
s.connect(('10.255.255.255', 1))
ip = s.getsockname()[0]

Not sure if hitting 10.255.255.255 would be any issue on some kind of network setup, but otherwise looks fine to me. I don't think it should be a problem since it seems to be like just a ping to a local LAN IP on port 1. This should also not give 127.0.0.1 since the IP is still outside the machine. @a13ssandr0 What do you think ? And does this work for your machine as well ?

a13ssandr0 commented 2 years ago

Didn't think the 8.8.8.8 solution would be great since it needs to be able to connect to external IPs which might not always be possible for a machine. [ . . . ] Not sure if hitting 10.255.255.255 would be any issue on some kind of network setup, but otherwise looks fine to me. I don't think it should be a problem since it seems to be like just a ping to a local LAN IP on port 1. This should also not give 127.0.0.1 since the IP is still outside the machine.

@devansh08 any IP outside the machine is fine, even if it isn't reachable, because we only need to reach the local router, actual routing is not needed; 10.255.255.255 is the broadcast address of the a 10.x.x.x local network so it's still fine.

@a13ssandr0 What do you think ? And does this work for your machine as well ? Yes, your code worked like a charm on first try :+1: