#!/bin/sh
# minc-dens : Mini-container docker emulating NAT support
#
# Copyright (C) 2017 Masami Hiramatsu <masami.hiramatsu@gmail.com>
# This program is released under the MIT License, see LICENSE.

set -e
test "$MINC_DEBUG" && set -x

MINC_BR=mincbr0
MINC_BRTMP=${MINC_NETTMP:="192.168.139.X/24"}
MINC_LEASE=/var/run/minc.lease

BRCTL=$(which brctl) ||:
if [ -n "$BRCTL" -a -x "$BRCTL" ] ; then
  MINC_USE_BRCTL=yes
else
  MINC_USE_BRCTL=
fi

map_port(){ # destip hostport destport proto
  :;: 'Ingress packet to local ip is DNAT to given container';:
  iptables -t nat -A POSTROUTING -s $1 -d $1 -p $4 -m $4 --dport $2 -j MASQUERADE
  iptables -t nat -I MINCS_NAT ! -i $MINC_BR -p $4 -m $4 --dport $2 -j DNAT --to-destination $1:$3
  :;: 'From host (outside of minc container), accept it.';:
  iptables -A MINCS -d $1 ! -i $MINC_BR -o $MINC_BR -p $4 -m $4 --dport $3 -j ACCEPT
}

unmap_port(){ # destip hostport destport proto
  :;: 'Delete DNAT rules';:
  iptables -t nat -D MINCS_NAT ! -i $MINC_BR -p $4 -m $4 --dport $2 -j DNAT --to-destination $1:$3
  iptables -t nat -D POSTROUTING -s $1 -d $1 -p $4 -m $4 --dport $2 -j MASQUERADE
  iptables -D MINCS -d $1 ! -i $MINC_BR -o $MINC_BR -p $4 -m $4 --dport $3 -j ACCEPT
}

setup_nat() {
  MINC_BRNET=`echo $MINC_BRTMP | sed s/X/0/g`
  :;: 'Setup NAT (Masquerade & local forwarding) rules';:

  iptables -I FORWARD -i $MINC_BR -j ACCEPT
  iptables -I FORWARD -o $MINC_BR -m state --state RELATED,ESTABLISHED -j ACCEPT
  iptables -N MINCS
  iptables -I FORWARD -o $MINC_BR -j MINCS

  iptables -t nat -I POSTROUTING -s $MINC_BRNET ! -o $MINC_BR -j MASQUERADE
  iptables -t nat -N MINCS_NAT
  iptables -t nat -A MINCS_NAT -i $MINC_BR -j RETURN
  iptables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j MINCS_NAT
  iptables -t nat -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j MINCS_NAT
  echo 1 > /proc/sys/net/ipv4/ip_forward
}

discard_nat() {
  MINC_BRNET=`echo $MINC_BRTMP | sed s/X/0/g`
  :;: 'ignore errors on discarding rules';:
  set +e
  iptables -D FORWARD -o $MINC_BR -m state --state RELATED,ESTABLISHED -j ACCEPT
  iptables -D FORWARD -i $MINC_BR -j ACCEPT
  iptables -D FORWARD -o $MINC_BR -j MINCS
  iptables -X MINCS

  iptables -t nat -D OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j MINCS_NAT
  iptables -t nat -D PREROUTING -m addrtype --dst-type LOCAL -j MINCS_NAT
  iptables -t nat -D MINCS_NAT -i $MINC_BR -j RETURN
  iptables -t nat -X MINCS_NAT
  iptables -t nat -D POSTROUTING -s $MINC_BRNET ! -o $MINC_BR -j MASQUERADE
  set -e
  iptables-save | grep -i minc | tac | while read l; do
    iptables `echo $l | sed -e "s/-A POSTROUTING/-t nat -D POSTROUTING/" -e "s/^:\([A-Z]*\) \(.*\)/-X \1/" -e "s/-A/-D/"` 
  done
}

init_minclease(){
  echo -n > $MINC_LEASE
}

br_show(){
  if [ $MINC_USE_BRCTL ]; then
    brctl show
  else
    nmcli con show | grep -w bridge
  fi
}

br_addbr() { # bridge-name
  if [ $MINC_USE_BRCTL ]; then
    brctl addbr $1
  else
    nmcli con add type bridge ifname $1
  fi
}

br_delbr() { # bridge-name
  if [ $MINC_USE_BRCTL ]; then
    brctl delbr $1
  else
    nmcli con del $1
  fi
}

br_addif() { # bridge-name ifname
  if [ $MINC_USE_BRCTL ]; then
    brctl addif $1 $2
  else
    nmcli con add type bridge-slave ifname $2 master $1
  fi
}

setup_mincbr() {
  MINC_BRIP=`echo $MINC_BRTMP | sed s/X/1/g`
  ip -4 addr add $MINC_BRIP dev $MINC_BR
  ip link set $MINC_BR up
}

mkmincbr(){
  if br_show | grep -qw $MINC_BR ; then
    ip addr show dev $MINC_BR | grep -wq inet || setup_mincbr
    iptables-save | grep -qi minc || setup_nat
  else
    :;: 'Add a bridge for mincs';:
    br_addbr $MINC_BR
    setup_mincbr
    init_minclease
    setup_nat
  fi
}

rmmincbr(){
  discard_nat ||:
  ip link set $MINC_BR down
  br_delbr $MINC_BR
}

addmincif(){ # ifname
  ip link set $1 up
  br_addif $MINC_BR $1
}

_get_unused_ipaddr(){ # ifname
  for i in `seq 2 128`; do
    MINC_IP=`echo $MINC_BRTMP | sed s/X/$i/g`
    grep -sqw $MINC_IP $MINC_LEASE && continue
    echo $MINC_IP $1 >> $MINC_LEASE
    echo $MINC_IP
    break;
  done
}

addmincip(){ # ifname netns
  MINC_IP=`_get_unused_ipaddr $1`
  if [ -z "$MINC_IP" ]; then
    echo "Error: no available IP address remains" 1>&2
    return 1
  fi
  MINC_GWIP=`echo $MINC_BRTMP | sed s@X/24@1@g`
  ip link set $1 netns $2
  ip netns exec $2 ip -4 addr add $MINC_IP dev $1
  ip netns exec $2 ip link set $1 up
  ip netns exec $2 ip -4 route add default via $MINC_GWIP
  echo $MINC_IP | cut -d/ -f1
}

delmincip(){ # ifname|ID
  grep -v $1 $MINC_LEASE > ${MINC_LEASE}.new ||:
  mv ${MINC_LEASE}.new $MINC_LEASE
}

__parse_portmap() { # hport [cport [proto]]
  echo -n $*
  [ -z "$2" ] && echo -n " $1"
  [ -z "$3" ] && echo -n " tcp"
  echo
}

_parse_portmap() { # hport[:cport[:proto]]
  __parse_portmap `echo $1 | sed "s/:/ /"`
}

addmincport() { # ipaddr portmap
  map_port $1 `_parse_portmap $2`
}

delmincport() { # ipaddr hport:cport[:tcp|:udp]
  unmap_port $1 `_parse_portmap $2` ||:
}

setmincip(){ # ID
  export MINC_IP=`_get_unused_ipaddr $1 | sed s@/24@@`
  export MINC_GWIP=`echo $MINC_BRTMP | sed s@X/24@1@`
  export MINC_NETMASK='255.255.255.0'
}
