#!/bin/sh
# Generates ietf-intefaces + ietf-ip settings for all detected interfaces.
#
#   Interfaces must be of type Ethernet (copper/sfp), not be "internal"
#   DSA ports, and must have a "parentbus", i.e., be physical ports.
#
# The script mainly caters to two modes of operation: switch and router.
# There is also a mixed mode with limited usability.  Other use-cases or
# combinations we recommend overriding this script in your br2-external
# rootfs overlay.
#
### Router Mode (default) ##############################################
# By default this script generates plain interfaces with IPv6 autoconfig
# (EUI64) enabled.  Useful both in a plain router and a Fail Secure mode
# where any switchcore-backed ports should be prevented from switching.
#
# The default mode currently does not support the '-4' or '-d' options
# for IPv4 ZeroConf or DHCPv4 address, beacause they require source
# routing, or similar, to work.  IPv6 does not require that and is
# therefore the recommended access method.  A separate (and optional)
# 'gen-cfg-custom' script is checked for in the bootstrap script which
# can be used to enable IPv4 and DHCP on a single service interface if
# needed.
#
### Bridge Mode ########################################################
# The '-b brname' option triggers the bridge mode, creating a 'brname'
# bridge interface using all interfaces classified in 'group port' by
# the nameif script.  In the bridge mode port interfaces do not have any
# IP address, the IPv6 autoconfig address is instead set on 'brN'.  If
# the '-4' option is set, ZeroConfig (169.254.x.y) is anabled on 'brN'.
# If the '-d' option is set, a DHCPv4 client is enabled on 'brN'.
#
### Mixed Mode #########################################################
# A "mixed mode" is also supported, where the system may have multiple
# ports in a switchcore, but some Ethernet interfaces directly connected
# to the SoC.  In this case, if '-b brname' is given, all 'group port'
# interfaces will be placed in the bridge 'brname' and all other
# interfaces will be brought up and given an IPv6 address (like above).
#
set -e

bridge=
ipv4=false
dhcp=

usage()
{
    cat <<EOF
Usage: gen-interfaces [-4d] [-b brN]

  -4      Enable IPv4 ZeroConf, only for brN in bridge mode
  -b brN  Bridged mode, add all port interfaces to brN
  -d      Enable DHCPv4 client, only for brN in bridge mode

EOF
    exit 0
}

# shellcheck disable=SC3043
gen_iface_json()
{
    local iftype="$1"
    local ifname="$2"
    local br="$3"

    cat <<EOF
      ,{
        "name": "$ifname",
        "type": "$iftype",
EOF

    if [ -n "$br" ]; then
        cat <<EOF
        "ietf-ip:ipv6": {
            "enabled": false
        },
        "infix-interfaces:bridge-port": {
            "bridge": "$br"
        }
EOF
    else
        cat <<EOF
        "ietf-ip:ipv6": {}
EOF
    fi

    cat <<EOF
      }
EOF
}

filter_iface_ports()
{
    ifaces="$1"
    ports="$2"

    iface_devs=""
    for phy in $ifaces; do
        found=""
        for port in $ports; do
            if [ "$port" = "$phy" ]; then
               found=true
               break
            fi
        done
        if [ -z "$found" ]; then
            iface_devs="$iface_devs $phy"
        fi
    done
    echo -n "$iface_devs"
}

while [ "$1" != "" ]; do
    case $1 in
        -4)
            ipv4=true
            ;;
        -b)
            bridge="$2"
            shift
            ;;
        -d)
            dhcp=true
            ;;
        -h)
            usage
            ;;
        *)
            break
    esac
    shift
done

phys_ifaces=$(ip -d -j link show | jq -r '
         .[] |
         select(.link_type == "ether") |
         select(.group != "internal")  |
         select(has("parentbus")) |
         select(.parentbus != "virtio") |
         .ifname' | sort -V)
virtio_ifaces=$(ip -d -j link show | jq -r '
         .[] |
         select(.link_type == "ether") |
         select(.group != "internal")  |
         select(has("parentbus")) |
         select(.parentbus == "virtio") |
         .ifname' | sort -V)
ports=$(ip -d -j link show group port | jq -r '
         .[] |
         select(.link_type == "ether") |
         select(.group != "internal")  |
         select(has("parentbus")) |
         .ifname' | sort -V)

eth_ifaces="$(filter_iface_ports "$phys_ifaces" "$ports")"
ethlike_ifaces="$(filter_iface_ports "$virtio_ifaces" "$ports")"

cat <<EOF
{
  "ietf-interfaces:interfaces": {
    "interface": [
      {
        "name": "lo",
        "type": "infix-if-type:loopback",
        "ietf-ip:ipv4": {
          "address": [{ "ip": "127.0.0.1", "prefix-length": 8 }]
        },
        "ietf-ip:ipv6": {
          "address": [{ "ip": "::1", "prefix-length": 128 }]
        }
EOF
if [ -n "$bridge" ]; then
    cat <<EOF
      },
      {
        "name": "$bridge",
        "type": "infix-if-type:bridge",
        "ietf-ip:ipv4": {
            "infix-ip:autoconf": {
                "enabled": $ipv4
            }
        },
        "ietf-ip:ipv6": {
            "enabled": true
        }
EOF
fi

cat <<EOF
      }
$(for iface in $eth_ifaces; do gen_iface_json "infix-if-type:ethernet" "$iface"; done)
$(for iface in $ethlike_ifaces; do gen_iface_json "infix-if-type:etherlike" "$iface"; done)
$(for iface in $ports; do gen_iface_json "infix-if-type:ethernet" "$iface" "$bridge"; done)
    ]
EOF
if [ "$dhcp" = "true" ] && [ -n "$bridge" ]; then
cat <<EOF
  },
  "infix-dhcp-client:dhcp-client": {
    "enabled": true,
    "client-if": [
      {
        "if-name": "$bridge"
      }
    ]
EOF
fi
cat <<EOF
  }
}
EOF
