ip snippets

"What's my IP" bash script

Tagged ip, bash, wanip, dns  Languages bash

This bash alias displays your IP address:

alias wanip='dig +short myip.opendns.com @resolver1.opendns.com'

How to access a service running on the host from a docker container

Tagged rtfm, container, docker, host, ip, docker-compose, subnet, ufw  Languages bash

How to access a service running on the host from a docker container? Easy…

First, RTFM:

https://docs.docker.com/docker-for-mac/networking/

https://docs.docker.com/docker-for-windows/networking/

Linux (bridge network)

  1. Create a custom bridge network

You can also use the default bridge network.

  1. Make the docker container use the custom network

See network configuration in the docker compose example below.

  1. On the host make the service listen to the hosts IP address on the custom network

For example, change listen_addresses in postgresql.conf for PostgreSQL.

Example docker-compose.yml:

version: "3.8"
services:
  "mycontainer":
    image: registry.gitlab.com/xxx/mycontainer:v1
    networks:
      - mynetwork
    env_file: ./mycontainer.env
networks:
  mynetwork:
    ipam:
      config:
        - subnet: 172.25.0.0/16

The docker container will be assigned an IP from the 172.25.0.0/16 subnet.

Make sure the service is listening to the host’s IP address on the network which should be 172.25.0.1.

Remember to allow the traffic in the firewall. See UFW example below:

# Check where the connection is coming from:
sudo dmesg
# Unblock the connections by source IP
sudo ufw allow in from "172.25.0.5" to 172.17.0.1 port 5432
# Or, unblock the connections by network name
sudo ufw allow in on <name of network> to 172.17.0.1 port 5432

Mac

Host networking is not supported by MacOS. You can use an IP address alias instead or one of the host’s IP addresses.

Option 1: Using an IP address alias

We can use an IP address that does not resolve inside the container to access the host. Unresolved IP addresses are routed to the host for resolving.

First, create an alias:

sudo ifconfig lo0 10.0.0.13

Make Postgres listen to the address in postgresql.conf:

listen_addresses = 'localhost,10.0.0.13'

Allow connections in pg_hba.conf:

host    all             all             10.0.0.13/32            trust

Restart PostgreSQL.

Option 2: Using an existing host IP address

Same as above except you don’t have to create an alias as the IP address already exists.

Run ifconfig -a on the host to list all IP addresses. Select one and see the instructions above.

Bad ideas

There are many other ways of achieving this, which are more or less bad ideas…

  • Option 1: docker hostnames

This option is mostly useful in development environments:

# Mac
ping docker.for.mac.localhost

# Windows
ping docker.for.win.localhost

# Linux: use the Docker IP or the hosts external IP, see:
# https://github.com/docker/for-linux/issues/264#issuecomment-385698947
  • Option 2: host networking

This option is not a good idea if you plan on hosting many projects on the same server.

Note that it’s not possible to use host networking on Mac or Windows, only Linux:

The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.

See:

How to check if an IP address is private (IPv6, IPv4, cidr)

Tagged ipv4, ipv6, private, validate, cidr, ip  Languages ruby
require 'resolv'
require 'ipaddr'

class PrivateIP
  # https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
  IPV4_NETWORKS = %w[0.0.0.0/8
  10.0.0.0/8
  100.64.0.0/10
  127.0.0.0/8
  169.254.0.0/16
  172.16.0.0/12
  192.0.0.0/24
  192.0.0.0/29
  192.0.0.8/32
  192.0.0.9/32
  192.0.0.170/32
  192.0.0.171/32
  192.0.2.0/24
  192.31.196.0/24
  192.52.193.0/24
  192.88.99.0/24
  192.168.0.0/16
  192.175.48.0/24
  198.18.0.0/15
  198.51.100.0/24
  203.0.113.0/24
  240.0.0.0/4
  255.255.255.255/32
  224.0.0.0/24
  239.0.0.0/8].map { |cidr| IPAddr.new(cidr) }

  # https://en.wikipedia.org/wiki/Unique_local_address
  IPV6_NETWORKS = %w[fd00::/8
  fc00::/8
  0000:0000:0000:0000:0000:0000:0000:0000/64].map { |cidr| IPAddr.new(cidr) }

  class InvalidHost < ArgumentError; end

  # Examples:
  # private?('127.0.0.1') => true
  # private?('localhost') => true
  # private?('google.com') => false
  def self.private?(ip_or_host)
    address = IPAddr.new(ip_or_host)
    if address.ipv4?
      IPV4_NETWORKS.any? { |cidr| cidr.include?(ip_or_host) }
    elsif address.ipv6?
      IPV6_NETWORKS.any? { |cidr| cidr.include?(ip_or_host) }
    else
      false
    end
  rescue IPAddr::InvalidAddressError
    private_host?(ip_or_host)
  end

  # Example: private_host?('localhost') => true
  def self.private_host?(host)
    host_ips(host).any? do |type, ips|
      ips.any? { |ip| private?(ip) }
    end
  end

  # Example: host_ips('localhost') => {ipv4: ['127.0.0.1'], ipv6: []}
  def self.host_ips(host)
    ipv4 = Resolv::DNS.new.getresources(host, Resolv::DNS::Resource::IN::A)
    ipv6 = Resolv::DNS.new.getresources(host, Resolv::DNS::Resource::IN::AAAA)
    raise InvalidHost, "unknown host: #{host}" if ipv4.empty? && ipv6.empty?
    { ipv4: ipv4.map { |r| r.address.to_s },
      ipv6: ipv6.map { |r| r.address.to_s } }
  end
end
require 'test_helper'
require 'private_ip'

class PrivateIPTest < ActiveSupport::TestCase
  test "private?" do
    assert PrivateIP.private?('localhost')
    assert PrivateIP.private?('127.0.0.1')
    assert PrivateIP.private?('0.0.0.0')
    refute PrivateIP.private?('google.com')
    refute PrivateIP.private?('209.216.230.240')
  end

  test "private? (IPV6)" do
    assert PrivateIP.private?('fd7b:5886:20a0:11a0:1111:2222:3333:4444')
    assert PrivateIP.private?('::1') # Try http://[::1]:3000 in browser
    assert PrivateIP.private?('::')
    assert PrivateIP.private?('0:0:0:0:0:0:0:1')
  end

  test "private? (CIDR)" do
    PrivateIP::IPV4_NETWORKS.each do |cidr|
      assert PrivateIP.private?(cidr.to_s)
    end
    PrivateIP::IPV6_NETWORKS.each do |cidr|
      assert PrivateIP.private?(cidr.to_s)
    end
  end

  test "private_host?" do
    assert PrivateIP.private_host?('localhost')
    refute PrivateIP.private_host?('google.com')
  end

  test "host_ips" do
    assert_equal({ ipv4: ["127.0.0.1"], ipv6: [] }, PrivateIP.host_ips('localhost'))
    assert_equal({ ipv4: ["209.216.230.240"], ipv6: []}, PrivateIP.host_ips('news.ycombinator.com'))
  end

  test "host_ips (invalid)" do
    assert_raise PrivateIP::InvalidHost do
      PrivateIP.host_ips('https://google.com')
    end
    assert_raise PrivateIP::InvalidHost do
      PrivateIP.host_ips('127.0.0.1')
    end
    assert_raise PrivateIP::InvalidHost do
      PrivateIP.host_ips('::1')
    end
    assert_raise PrivateIP::InvalidHost do
      PrivateIP.host_ips('[::1]')
    end
    assert_raise PrivateIP::InvalidHost do
      PrivateIP.host_ips('::')
    end
  end
end

"ipaddr show" for MacOS

Tagged address, ip, ipaddr  Languages bash
alias wanip='dig +short myip.opendns.com @resolver1.opendns.com'
alias ipaddr='wanip && ipconfig getifaddr en1'