1. Introduction
Especially in the age of cloud computing, we often need to have the external IP address of a machine. Since scripts execute on different environments, it’s convenient and often necessary to have a way of getting such addresses automatically from many locations.
In this tutorial, we discuss how to get the external IP address in Bash under Linux. First, we talk about IP addresses and network configurations. After that, we see how Bash can check and filter network interface information. Next, we explore how local routers and third-party services can be a source for our external IP. Finally, we discuss some alternatives.
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It is POSIX-compliant and should work in any such environment.
2. Internet Protocol
Let’s start with a brief refresher.
Internet Protocol (IP) addresses can be private and public. Private IPs are within certain ranges for both IPv4 and IPv6. We call addresses outside these ranges public.
Furthermore, IPs can be internal (local) or external. Importantly, the latter can only be from the public range as only public IPs can be routed through the Internet. Because of this, internal and external mainly differ by the link owner.
In fact, an Internet Service Provider (ISP) is usually the owner of external IP addresses.
3. Networking Configuration
To choose the best way of getting the external address of a machine via a Bash shell script, we should know the following:
- devices in the network
- network interfaces
- network setup
- where the script is executed
Indeed, there is often separate hardware for Internet access. Also, there can be an interface for local networking.
Hence, we’ll briefly go through different setups and mark them with capital letters. Moreover, M is our client machine, and *External IP* is the address that interests us. The eif0 interface holds that address.
3.1. Direct Connection (A)
One possible network setup has the external IP directly assigned to the machine that executes the script:
\_\_\_\_\_
________ *External IP* ____| | (Internet)______________/eif1| M | (________) 169.254.6.66 \____|_____|
In this simple network, we have a single machine with one external interface.
3.2. Routed Setup (B)
\_\_\_
________ *External IP* __________________ Internal IP ____| | (Internet)______________/eif1| router |iif0\_____________/iif0| M | (________) 169.254.6.66 \____|________|____/ 192.168.1.x \____|___|
Here, the machine doesn’t have direct access to the external interface.
3.3. Router Host (C)
In more specialized environments, we can have a server that acts as the router:
\_\_\_\_\_\_\_\_ \_\_\_
________ *External IP* ____| router |____ Internal IP ____| | (Internet)______________/eif1| server |iif0\_____________/iif0| M | (________) 169.254.6.66 \____|________|____/ 192.168.1.x \____|___|
Note that a simple router does not have a complex command environment like a router server. While the difference between setups B and C is mild, it’s important, as we’ll see in the next section.
4. External Network Interface Handling in Bash
After exploring common network configurations, let’s get the external IP of a machine via a Bash shell script.
4.1. Interface Queries
Commonly, getting any IP address is done via the ip command:
$ ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eif0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 06:66:06:66:00:10 brd ff:ff:ff:ff:ff:ff
inet 169.254.6.66/24 brd 169.254.6.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 2001:db8:666:666::10/64 scope global
valid_lft forever preferred_lft forever
3: iif1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 06:66:06:66:00:11 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.2/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
Of course, to work with ip, we should run it locally on the machine with the external network.
In scenarios A and C, we directly use the interface name and extract (via perl) the IP address:
$ ip address show eif0 | perl -nwe 'print /^\s+inet\s+(.*?)\//;'
169.254.6.66
$ ip address show eif0 | perl -nwe 'print /^\s+inet6\s+(.*?)\//;'
2001:db8:666:666::10
The difference is, of course, where these commands run. While that’s the client in scenario A, in scenario C, we should find a way to run the above on the router server.
4.2. Remote External IP Checks
Indeed, there are standard protocols for running local commands from a remote machine. Commonly, we use ssh:
$ ssh user@server-router 'ip address show eif0'
[...]
For convenience, we suggest SSH keys be already:
- generated
- copied to the router-server
- configured locally and remotely
Similarly, we can use telnet, ftp, and other terminal protocols. Since many simple routers may still not provide terminal access, we should have more options.
5. Remotely Check External IP
Regardless of other protocols, routing devices usually have an HTTP/S configuration interface. How it’s structured and the information available there depends on the manufacturer.
Still, we can use tools like wget or curl to get the page we want. After that, we can again filter via perl or similar tools.
Due to the many specific HTTP/S portals, we can only provide examples:
$ curl --user USERNAME:PASSWORD http://router/
[...]
$ curl "http://router/" --data "frashnum=&action=login&Frm_Logintoken=TOKEN&Username=USERNAME&Password=PASSWORD"
[...]
After getting the page, we can again filter with regular expressions or use specialized scraping tools like quickscrape.
While it’s not recommended to parse HTML DOM without a library for the purpose, we can still do it to easily extract data:
$ echo "${PAGE_DATA}" | perl -nwe 'print /input id="internet_ip_address".*?snapshot="(.*?)"/;'
169.254.6.66
Despite all these options, there are times when we don’t know how to apply them in our case. We could also not have the needed information, e.g., username or password. There exists a more universal way.
6. External IP Echo
Notice the common element between diagrams A, B, and C – the Internet. Unless specifically filtered, we can use an outside source to get our Internet-facing IP in nearly all configurations. While convenient, this method sends queries outside the local network, which could be undesirable.
6.1. IP Services
There are tons of services for getting our address via different protocols:
$ ssh sshmyip.com
{
"comment": "## Your IP Address is 169.254.6.66 (2904) ##",
"family": "ipv4",
"ip": "169.254.6.66",
"port": "2904",
"protocol": "ssh",
"version": "v1.3.0",
"force_ipv4": "ipv4.telnetmyip.com",
"force_ipv6": "ipv6.telnetmyip.com",
"website": "https://github.com/packetsar/checkmyip",
[...]
}
Connection to sshmyip.com closed by remote host.
Connection to sshmyip.com closed.
$ telnet telnetmyip.com
[...]
$ curl telnetmyip.com
[...]
$ wget -qO- telnetmyip.com
[...]
In this instance, the format is a JSON object. Providers of such services can return:
- just our IP address as a string
- location information based on our IP address
- IP address based on protocol version, e.g., IPv4 or IPv6
- complex data formats and more information
For more privacy, there is a way to get our external IP address without requesting it from an unknown third party.
6.2. Domain Name Service (DNS) IP Query
Since there is rarely a machine not attached to at least one DNS server, we can use our DNS resolver to get the external IP via the dig or host commands:
$ dig +short myip.opendns.com @resolver1.opendns.com
169.254.6.66
$ host myip.opendns.com resolver1.opendns.com
169.254.6.66
$ dig +short txt ch whoami.cloudflare @1.1.1.1
169.254.6.66
$ dig +short txt o-o.myaddr.test.l.google.com @ns1.google.com
169.254.6.66
Here, we use different providers, but for enhanced security, we should use the DNS server configured for the machine. This means we won’t send requests to a machine, which is not already aware of our IP.
7. Additional Protocols
Sometimes, hardware or configuration specifics allow or enforce ways for external IP checks. Let’s explore some scenarios.
The UPnP protocols are mainly for home users. They enable easy automatic device discovery and communication on the network.
In fact, we can use the upnpc tool to list and query UPnP devices:
$ upnpc -s
upnpc : miniupnpc library test client, version 2.2.1.
(c) 2005-2020 Thomas Bernard.
Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
for more information.
List of UPNP devices found on the network :
desc: http://192.168.1.1:1900/rootDesc.xml
st: urn:schemas-upnp-org:device:InternetGatewayDevice:1
Found valid IGD : http://router:1900/ctl/IPConn
Local LAN ip address : 192.168.1.2
[...]
ExternalIPAddress = 169.254.6.66
[...]
Here, we see our router and the external IP address, which we can filter out via tools like grep and cut:
$ echo "${UPNP_DATA}" | grep ^ExternalIPAddress | cut -c21-
Like UPnP, the Network Address Translation Port Mapping Protocol (NAT-PMP) enables information exchange between network devices. There is also a tool called natpmpc, which allows us to get that information.
8. Summary
In this tutorial, we explored how to check a machine’s external IP address via a Bash shell script.
Sometimes, routing devices have extra functionality, such as:
- Virtual Private Network (VPN) layers
- Socket Secure (SOCKS) or HTTP/S proxies
- traffic filtering, e.g., firewalls and other filters
In such environments, the suggestions that we discussed provide many possible workarounds for getting our external IP.