#! /bin/sh
# Report on the MAC address and Open vSwitch port of a given container
#
# Usage: docker-ovs-port CONTAINER OVS_BRIDGE
#
# Used as a helper for translating Docker/Open vSwitch attachements into 
# OpenFlow rules.
#
# Relies on internal API of Open vSwitch (ovs-appctl) to do the Linux interface
# to OpenFlow port mapping.  See:
#
# http://ewen.mcneill.gen.nz/blog/entry/2014-10-12-finding-docker-containers-connection-to-openvswitch/
#
# for background information.
#
# Written by Ewen McNeill <ewen@naos.co.nz>, 2014-10-08
# Updated by Ewen McNeill <ewen@naos.co.nz>, 2014-10-12
#
#---------------------------------------------------------------------------
# Copyright (c) 2014 Naos Ltd
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# # modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#---------------------------------------------------------------------------

set -e

PATH=/usr/sbin:/usr/bin:/bin
export PATH

CGROUP_MOUNT=$(grep -w devices /proc/mounts | awk '{ print $2; }')

if [ -z "${CGROUP_MOUNT}" ]; then
  echo "Error: could not auto-locate cgroup mount point" >&2
  exit 1
fi

get_container_netns() {
  GUEST_NAME="$1"
  CONTAINER=$(find "${CGROUP_MOUNT}" -name "${GUEST_NAME}*")
  if [ -z "${CONTAINER}" ]; then
    # Not found as a container ID, try again to see if we can find it as
    # a running container name (this is a bit of a hack)
    CONTAINER_SHORTID=$(docker ps -a | 
		      awk 'substr($0,139) ~ '"/${GUEST_NAME}"'/ { print $1;}')
    if [ -n "${CONTAINER_SHORTID}" ]; then
      CONTAINER=$(find "${CGROUP_MOUNT}" -name "${CONTAINER_SHORTID}*")
    fi
  fi
  if [ -z "${CONTAINER}" ]; then
    echo "Error: no container found matching ${GUEST_NAME}" >&2
    return
  fi
  NUM_FOUND=$(echo "${CONTAINER}" | wc -l)
  if [ "${NUM_FOUND}" -eq 1 ]; then
    :  # Expected
  else
    echo "Error: multiple containers found matching ${GUEST_NAME}" >&2
    return
  fi

  #echo "Container found at: ${CONTAINER}" >&2

  #---------------------------------------------------------------------------
  # Figure out the namespace PID

  head -n 1 "${CONTAINER}/tasks"
}

get_ovs_portmap() {
  BRIDGE="${1}"
  sudo ovs-appctl dpif/show | 
       MATCH="${BRIDGE}" \
       perl -ne 'BEGIN                 { $in_switch=0;       } 
                 if (/$ENV{MATCH}/)    { $in_switch=1; next; }
                 if ($in_switch) {
                   if (/^(\t| {8})\S/) { $in_switch=0        }
                   else { print; }
                }'
}

#---------------------------------------------------------------------------
# Mainline

# Parse commandline
CONTAINER="$1"
OVS_BRIDGE="$2"

if [ -z "${CONTAINER}" -o -z "${OVS_BRIDGE}" ]; then
  echo "Usage: $0 CONTAINER OVS_BRIDGE" 2>&1
  exit 1
fi

# Find network namespace
NETNS=$(get_container_netns "${CONTAINER}")

if [ -z "${NETNS}" ]; then
  echo "Error: no tasks found in container ${GUEST_NAME}" >&2
  exit 1
fi

# Find ethernet interfaces inside that network namespace
CONTAINER_ETH=$(sudo ip netns exec "${NETNS}" ip link show | 
                grep -B 1 link/ether | grep '^[0-9]' | 
                cut -f 2 -d : | sed 's/ //g;')

if [ -z "${CONTAINER_ETH}" ]; then
  echo "$0: No ethernet interfaces found in container ${CONTAINER}" >&2
  exit 1
fi

# Iterate over all the guest ethernet interfaces
for GUEST_IF in ${CONTAINER_ETH}; do
  # Find Linux name for host end of the veth link
  HOST_IF_ID=$(sudo ip netns exec "${NETNS}" ethtool -S "${GUEST_IF}" | 
               awk '/peer_ifindex:/ { print $2; }')
  HOST_IF=$(ip link show | awk "/^${HOST_IF_ID}:/"' { print $2; }' | 
            cut -f 1 -d :)

  # Find the Open vSwitch _global_ (Linux) port number for the host interface
  # (not directly helpful to our problem; see blog post)
  #OVS_PORT_ID=$(sudo ovs-dpctl show | 
  #              awk '$3 ~ /'"${HOST_IF}"'/ { print $2; }' | cut -f 1 -d :)

  # Find the _OpenFlow_ port number for the host interface
  OF_PORT_ID=$(get_ovs_portmap "${OVS_BRIDGE}" | 
               awk "/^\s*${HOST_IF}/ "'{ print $2; }' | cut -f 1 -d /)

  # Also for completeness, get the MAC address of the container interface
  GUEST_MAC=$(sudo ip netns exec "${NETNS}" ip link show "${GUEST_IF}" |
               awk '/ether/ { print $2; }')

  # Output details we found in CSV format
  echo "${GUEST_IF},${OF_PORT_ID},${GUEST_MAC}"
done
