#!/usr/bin/env python3
#
# Copyright (c) 2019-2021, AT&T Intellectual Property.
# All rights reserved.
#
# SPDX-License-Identifier: GPL-2.0-only
#

"""Scripts for CGNAT error, summary, and policy op-mode commands"""

import sys
import json
import getopt
import vplaned
from time import localtime, strftime
from vyatta.npf.npf_addr_group import npf_show_address_group
from vyatta.npf.IPProto import num2proto

# List of CGNAT ALGs
alg_list = ['pptp', 'sip', 'ftp']


#
# num2str
#
def num2str(count, approx):
    """Convert a count into a string"""
    if approx:
        str = "~%u" % (count)
    else:
        str = "%u" % (count)
    return str


# Yes or No string
def yes_or_no(val):
    return "Yes" if val else "No"


# Enabled or Disabled string
def enabled_or_disabled(val):
    return "Enabled" if val else "Disabled"


#
# usage
#
def cgn_usage():
    """Show command help"""

    print("usage: {} --show {{errors | summary | "
          "policy [<name>]}}".format(sys.argv[0]),
          file=sys.stderr)


#
# cgn_op_show_summary
#
def cgn_op_show_summary():
    """Show CGN summary"""

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                cgn_dict = dp.json_command("cgn-op show summary")
                summary = cgn_dict.get('summary')
                if not summary:
                    return

                tbl_full = yes_or_no(summary.get('sess_table_full'))

                #
                # If we ever change to supporting multiple dataplanes then a
                # description of the dataplane should be displayed before each
                # summary.
                #
                print("CGNAT Summary")
                print("  %-32s" % ("Sessions:"))
                print("    %-30s %18u" % ("Active sessions",
                                          summary.get('sess_count')))
                print("      %-28s %18s" % ("Sessions created",
                                            num2str(summary.get('sess_created'), True)))
                print("      %-28s %18s" % ("Sessions destroyed",
                                            num2str(summary.get('sess_destroyed'), True)))
                print("    %-30s %18u" % ("Active sub-sessions",
                                          summary.get('sess2_count')))
                print("      %-28s %18s" % ("Sub-sessions created",
                                            num2str(summary.get('sess2_created'), True)))
                print("      %-28s %18s" % ("Sub-sessions destroyed",
                                            num2str(summary.get('sess2_destroyed'), True)))
                print("    %-30s %18u" % ("Maximum table size",
                                          summary.get('max_sess')))
                print("    %-30s %18s" % ("Table full", tbl_full))
                print("  %-32s" % ("Public address mapping table:"))
                print("    %-30s %18u" % ("Used",
                                          summary.get('apm_table_used')))
                print("  %-32s" % ("Subscriber address table:"))
                print("    %-30s %18u" % ("Used",
                                          summary.get('subs_table_used')))
                print("    %-30s %18u" % ("Max",
                                          summary.get('subs_table_max')))

                # Out
                print("  %-32s" % ("Out:"))
                print("    %-30s %18s" % ("Translated packets",
                                          num2str(summary.get('pkts_out'), True)))
                print("    %-30s %18s" % ("           bytes",
                                          num2str(summary.get('bytes_out'), True)))
                print("    %-30s %18u" % ("Did not match CGNAT policy",
                                          summary.get('nopolicy')))
                if summary.get('bypass'):
                    print("    %-30s %18u" % ("ALG bypass packets",
                                              summary.get('bypass')))
                print("    %-30s %18u" % ("Untranslatable packets",
                                          summary.get('etrans')))
                print("    %-30s %18u" % ("Hairpinned packets",
                                          summary.get('pkts_hairpinned')))
                if 'excluded_out' in summary:
                    print("    %-30s %18u" % ("Excluded",
                                              summary.get('excluded_out')))

                print("    %-30s" % ("ALGs:"))
                for alg in alg_list:
                    key = 'alg_' + alg + '_out'
                    if key in summary:
                        print("      %-28s %18u" % (alg.upper(),
                                                    summary.get(key)))

                # In
                print("  %-32s" % ("In:"))
                print("    %-30s %18s" % ("Translated packets",
                                          num2str(summary.get('pkts_in'), True)))
                print("    %-30s %18s" % ("           bytes",
                                          num2str(summary.get('bytes_in'), True)))
                print("    %-30s %18s" % ("Unknown source addr or port",
                                          num2str(summary.get('unk_pkts_in'), True)))
                print("    %-30s %18u" % ("Did not match CGNAT session",
                                          summary.get('nosess')))
                if 'nopool' in summary:
                    print("    %-30s %18u" % ("Did not match CGNAT pool",
                                              summary.get('nopool')))

                # Dest addr/port hash tables
                if 'sess_ht_created' in summary:
                    print("  %-32s" % ("Session hash tables:"))
                    print("    %-30s %18u" % ("Created",
                                              summary.get('sess_ht_created')))
                    print("    %-30s %18u" % ("Destroyed",
                                              summary.get('sess_ht_destroyed')))

                print("    %-30s" % ("ALGs:"))
                for alg in alg_list:
                    key = 'alg_' + alg + '_in'
                    if key in summary:
                        print("      %-28s %18u" % (alg.upper(),
                                                    summary.get(key)))

                # PCP
                if 'pcp_ok' in summary:
                    print("  %-32s %18u" % ("PCP sessions created",
                                            summary.get('pcp_ok')))
                if 'pcp_err' in summary:
                    print("  %-32s %18u" % ("PCP errors",
                                            summary.get('pcp_err')))

                # Other
                print("  %-32s %18u" % ("Memory allocation failures",
                                        summary.get('enomem')))
                print("  %-32s %18u" % ("Resource limitation failures",
                                        summary.get('enospc')))
                print("  %-32s %18u" % ("Thread contention errors",
                                        summary.get('ethread')))
                print("  %-32s %18u" % ("Packet buffer errors",
                                        summary.get('embuf')))
                if 'icmp_echoreq' in summary:
                    print("  %-32s %18u" % ("ICMP Echo Req for CGNAT addr",
                                            summary.get('icmp_echoreq')))
                print()


#
# Print one line in error/global count output
#
def cgn_op_show_error_one(key, fmt, in_d, out_d):
    if key in in_d and key in out_d:
        print(fmt % (in_d[key]['desc'], in_d[key]['count'],
                     out_d[key]['count']))


#
# cgn_op_show_alg_status
#
def cgn_op_show_alg_status():
    """Show CGN ALG Status"""

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                cgn_dict = dp.json_command("cgn-op show alg status")
                alg = cgn_dict.get('alg')
                if not alg:
                    continue

                print("CGNAT ALG Status")

                state = alg.get('status')
                for i in range(0, len(state)):
                    name = state[i].get('name')
                    enabled = enabled_or_disabled(state[i].get('enabled'))
                    print("%-5s %s" % (name, enabled))

                print()


#
# Sum one or more counts from the 'in' and 'out' count dictionaries
#
def cgn_op_sum_counts(in_d, out_d, rc_list):
    in_sum = 0
    out_sum = 0

    for i in range(0, len(rc_list)):
        key = rc_list[i]
        if key in in_d:
            in_sum += in_d[key]['count']
        if key in out_d:
            out_sum += out_d[key]['count']

    return in_sum, out_sum


#
# Show ALG packet inspection return code stats for control pkts
#
def alg_show_stats_inspect(insp, d_opt):
    """Show CGN ALG Packet Inspect Statistics"""

    # Simple way to enable all stats for demo'ing the output
    show_all = False

    #
    # Only display SIP or FTP stats if dataplane returns 'OK_SIP' or
    # 'OK_FTP' stats
    #
    show_sip = False
    show_ftp = False

    in_tmp = insp.get('in')
    out_tmp = insp.get('out')

    in_d = {}
    out_d = {}

    #
    # Counter names sub-divided into lists
    #
    err_pload = ['ERR_PLOAD_FETCH', 'ERR_PLOAD_UPDATE', 'ERR_PLOAD_NOSPC']
    err_phole = ['ERR_PHOLE_NOMEM', 'ERR_PHOLE_EXIST']
    err_pptp = ['ERR_PPTP_MAP', 'ERR_PPTP_OUT_REQ', 'ERR_PPTP_OUT_REPLY',
                'ERR_PPTP_MC']
    err_sip = ['ERR_SIP_MAP', 'ERR_SIP_UNSP', 'ERR_SIP_NOSPC', 'ERR_SIP_DREQ',
               'ERR_SIP_NOENT', 'ERR_SIP_NOMEM', 'ERR_SIP_MEDIA',
               'ERR_SIP_PHOLE', 'ERR_SIP_MAXCID', 'ERR_SIP_PARSE_REQ',
               'ERR_SIP_PARSE_RSP', 'ERR_SIP_PARSE_CID', 'ERR_SIP_PARSE_VIA',
               'ERR_SIP_PARSE_CTYPE', 'ERR_SIP_PARSE_CLEN', 'ERR_SIP_PARSE_SDP',
               'ERR_SIP_PARSE_C', 'ERR_SIP_PARSE_A']
    err_ftp = ['ERR_FTP_PARSE_PORT', 'ERR_FTP_PARSE_EPRT', 'ERR_FTP_PARSE_227',
               'ERR_FTP_PARSE_229']
    err_other = ['ERR_INT']

    # Convert counter list to a dictionary keyed by counter name
    for i in range(0, len(in_tmp)):
        key = in_tmp[i].get('name')
        count = in_tmp[i].get('count')
        desc = in_tmp[i].get('desc')

        if key in in_d:
            in_d[key][count] += count
        else:
            in_d[key] = {'count': count, 'desc': desc}

    for i in range(0, len(out_tmp)):
        key = out_tmp[i].get('name')
        count = out_tmp[i].get('count')
        desc = out_tmp[i].get('desc')

        if key in out_d:
            out_d[key][count] += count
        else:
            out_d[key] = {'count': count, 'desc': desc}

    fmt0s = "    %-36s %12s %12s"
    fmt1 = "      %-34s %12u %12u"
    fmt2 = "        %-32s %12u %12u"

    print("  CGNAT ALG Control Packets")

    print(fmt0s % ("Ok:", "In", "Out"))
    cgn_op_show_error_one('OK_PPTP', fmt1, in_d, out_d)

    # 'OK_SIP' may not me present in initial release
    if 'OK_SIP' in in_d:
        show_sip = True
        cgn_op_show_error_one('OK_SIP', fmt1, in_d, out_d)

    # 'OK_FTP' may not me present in initial release
    if 'OK_FTP' in in_d:
        show_ftp = True
        cgn_op_show_error_one('OK_FTP', fmt1, in_d, out_d)

    print("    Errors:")

    #
    # Payload errors
    #
    in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_pload)

    print(fmt1 % ("Payload errors:", in_sum, out_sum))

    # Only show individual payload errors if 'detail' was specified
    if d_opt:
        for rc in err_pload:
            cgn_op_show_error_one(rc, fmt2, in_d, out_d)

    #
    # Pinhole errors
    #
    in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_phole)

    print(fmt1 % ("Pinhole errors:", in_sum, out_sum))

    # Only show individual pinhole errors if 'detail' was specified
    if d_opt:
        for rc in err_phole:
            cgn_op_show_error_one(rc, fmt2, in_d, out_d)

    #
    # PPTP errors
    #
    in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_pptp)

    print(fmt1 % ("PPTP errors:", in_sum, out_sum))

    # Only show individual PPTP errors if 'detail' was specified
    if d_opt:
        for rc in err_pptp:
            cgn_op_show_error_one(rc, fmt2, in_d, out_d)

    #
    # SIP errors
    #
    if show_sip or show_all:
        in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_sip)

        print(fmt1 % ("SIP errors:", in_sum, out_sum))

        # Only show individual SIP errors if 'detail' was specified
        if d_opt:
            for rc in err_sip:
                cgn_op_show_error_one(rc, fmt2, in_d, out_d)

    #
    # FTP errors
    #
    if show_ftp or show_all:
        in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_ftp)

        print(fmt1 % ("FTP errors:", in_sum, out_sum))

        # Only show individual FTP errors if 'detail' was specified
        if d_opt:
            for rc in err_ftp:
                cgn_op_show_error_one(rc, fmt2, in_d, out_d)

    #
    # Other errors
    #
    in_sum, out_sum = cgn_op_sum_counts(in_d, out_d, err_other)

    print(fmt1 % ("Other errors:", in_sum, out_sum))

    # Only show individual 'other' errors if 'detail' was specified
    if d_opt:
        for rc in err_other:
            cgn_op_show_error_one(rc, fmt2, in_d, out_d)


#
# Show CGN ALG sessions stats for one ALG
#
def alg_show_stats_sessions_one(sess, name):
    """Show CGN ALG Sessions Statistics for one ALG"""

    if name not in sess:
        return

    sess = sess.get(name)

    ctrl_cur = 0
    if sess['ctrl_sessions_crtd'] > sess['ctrl_sessions_dstd']:
        ctrl_cur = sess['ctrl_sessions_crtd'] - sess['ctrl_sessions_dstd']

    data_cur = 0
    if sess['data_sessions_crtd'] > sess['data_sessions_dstd']:
        data_cur = sess['data_sessions_crtd'] - sess['data_sessions_dstd']

    fmt = "      %-34s %12s"

    print("  %s Sessions" % (name.upper()))
    print("    Control")
    print(fmt % ("Current", ctrl_cur))
    print(fmt % ("Created", sess['ctrl_sessions_crtd']))
    print(fmt % ("Destroyed", sess['ctrl_sessions_dstd']))
    print("    Data")
    print(fmt % ("Current", data_cur))
    print(fmt % ("Created", sess['data_sessions_crtd']))
    print(fmt % ("Destroyed", sess['data_sessions_dstd']))


#
# Show CGN ALG sessions stats for all ALGs
#
def alg_show_stats_sessions(sess):
    """Show CGN ALG Session Statistics"""
    for alg in alg_list:
        alg_show_stats_sessions_one(sess, alg)


#
# Show CGN ALG session stats and packet inspections return code stats
#
def alg_show_stats(stats, d_opt):
    """Show CGN ALG Statistics"""

    if 'sessions' in stats:
        alg_show_stats_sessions(stats.get('sessions'))

    if 'inspect' in stats:
        alg_show_stats_inspect(stats.get('inspect'), d_opt)


#
# alg_get_summary
#
# Fetch the ALG global counters from the dataplane
#
def alg_get_summary(dp):
    """Get CGN ALG Summary"""

    if not dp:
        return None

    cgn_dict = dp.json_command("cgn-op show alg summary")

    if 'alg' not in cgn_dict:
        return None
    alg = cgn_dict.get('alg')

    if 'summary' not in alg:
        return None

    return alg.get('summary')


#
# alg_show_summary
#
# Display CGNAT ALG global counts
#
def alg_show_summary(summary, d_opt):
    """Show CGN ALG Summary"""

    print("CGNAT ALG Summary")

    if 'stats' in summary:
        alg_show_stats(summary.get('stats'), d_opt)


#
# cgn_op_show_alg_summary
#
# Display CGNAT ALG global counts
#
def cgn_op_show_alg_summary(d_opt):
    """Show CGN ALG Summary"""

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                summary = alg_get_summary(dp)

                if summary:
                    alg_show_summary(summary, d_opt)


#
# alg_get_pinholes
#
# Fetch the ALG pinhole table from the dataplane
#
def alg_get_pinholes(dp, ph_opt):
    """Get CGN ALG Pinhole Table"""

    if not dp:
        return None

    cgn_dict = dp.json_command("cgn-op show alg pinholes %s" % (ph_opt))

    if 'alg' not in cgn_dict:
        return None
    alg = cgn_dict.get('alg')

    if 'pinholes' not in alg:
        return None

    return alg.get('pinholes')


#
# col1_width
#
# Column 1 and 2 are the pinhole ID columns and can vary in length quite a
# bit.  As such, we make it dynamic.  It starts at 4 chars wide and grows as
# the size of the pinhole IDs grow.
#
def col1_width(tmp):
    """Column 1 and 2 width static variable."""

    # 4 is minimum
    if tmp < 4:
        tmp = 4

    if 'cnt' not in col1_width.__dict__:
        col1_width.cnt = 0
    # Column width only ever increases
    if tmp > col1_width.cnt:
        col1_width.cnt = tmp
    return col1_width.cnt


def alg_show_pinholes(ph_list):
    col1 = col1_width(4)

    if False:
        print(json.dumps(ph_list, sort_keys=False, indent=4))

    print("%-*s %-*s %-5s %-4s "
          "%-5s %21s %21s %7s %7s" % (col1, "ID", col1, "Pair", "ALG", "Dir",
                                      "Proto", "Source", "Destination",
                                      "Timeout", "Session"))

    # Sort by pinhole 'id'
    tmp = sorted(ph_list, key=lambda d: (d['id']))
    ph_list = tmp

    for ph in ph_list:
        id1 = "%s" % (ph.get('id'))
        col1 = col1_width(len(id1))

        id2 = "%s" % (ph.get('pair_id'))
        col1 = col1_width(len(id2))

        #
        # PPTP pinholes do not have a source port
        # SIP pinholes may have a 'wildcard' source port
        #
        sport = '-'
        if ph.get('alg') != 'pptp':
            sport = ph.get('sport')
            if sport == 0:
                sport = 'any'

        src = "%s/%s" % (ph.get('saddr'), sport)
        dst = "%s/%s" % (ph.get('daddr'), ph.get('dport'))

        print("%-*s %-*s %-5s %-4s %-5s "
              "%21s %21s %7s %7s" % (col1, id1, col1, id2,
                                     ph.get('alg'), ph.get('dir'),
                                     num2proto(ph.get('ipproto')), src, dst,
                                     ph.get('timeout'), ph.get('session_id')))


#
# Show CGNAT ALG Pinhole table
#
def cgn_op_show_alg_pinholes(ph_opt):
    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                phs = alg_get_pinholes(dp, ph_opt)
                if phs:
                    alg_show_pinholes(phs)


#
# Get error counts
#
def cgn_op_get_errors():
    """Get cgnat error counts.  Returns 2 dictionaries"""

    #
    # Create two new dictionaries, keyed by name, e.g. 'PCY_ENOENT'.  Each
    # entry contains a sub-dictionary of 'count' and 'desc'.
    #
    in_d = {}
    out_d = {}

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                cgn_dict = dp.json_command("cgn-op show errors")
                errors = cgn_dict.get('errors')
                if not errors:
                    return {}, {}

                in_errors = errors.get('in')
                out_errors = errors.get('out')

                for i in range(0, len(in_errors)):
                    key = in_errors[i].get('name')
                    count = in_errors[i].get('count')
                    desc = in_errors[i].get('desc')

                    if key in in_d:
                        in_d[key][count] += count
                    else:
                        in_d[key] = {'count': count, 'desc': desc}

                for i in range(0, len(out_errors)):
                    key = out_errors[i].get('name')
                    count = out_errors[i].get('count')
                    desc = out_errors[i].get('desc')

                    if key in out_d:
                        out_d[key][count] += count
                    else:
                        out_d[key] = {'count': count, 'desc': desc}

    # Returns 2 dictionaries
    return in_d, out_d


#
# cgn_op_show_errors
#
def cgn_op_show_errors():
    """Show CGN errors"""

    in_d, out_d = cgn_op_get_errors()
    fmt = "    %-54s %12u %12u"

    print("%-58s %12s %12s" % ("CGNAT Global Counts", "In", "Out"))

    print("  Unable to translate packet:")
    cgn_op_show_error_one('PCY_ENOENT', fmt, in_d, out_d)
    cgn_op_show_error_one('SESS_ENOENT', fmt, in_d, out_d)
    cgn_op_show_error_one('POOL_ENOENT', fmt, in_d, out_d)
    cgn_op_show_error_one('PCY_BYPASS', fmt, in_d, out_d)
    cgn_op_show_error_one('BUF_PROTO', fmt, in_d, out_d)
    cgn_op_show_error_one('BUF_ICMP', fmt, in_d, out_d)

    print("  Resource limitations:")
    cgn_op_show_error_one('MBU_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('BLK_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('POOL_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('SRC_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('APM_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('S1_ENOSPC', fmt, in_d, out_d)
    cgn_op_show_error_one('S2_ENOSPC', fmt, in_d, out_d)

    print("  Memory allocation failures:")
    cgn_op_show_error_one('S1_ENOMEM', fmt, in_d, out_d)
    cgn_op_show_error_one('S2_ENOMEM', fmt, in_d, out_d)
    cgn_op_show_error_one('PB_ENOMEM', fmt, in_d, out_d)
    cgn_op_show_error_one('APM_ENOMEM', fmt, in_d, out_d)
    cgn_op_show_error_one('SRC_ENOMEM', fmt, in_d, out_d)

    print("  Thread contention errors:")
    cgn_op_show_error_one('S1_EEXIST', fmt, in_d, out_d)
    cgn_op_show_error_one('S2_EEXIST', fmt, in_d, out_d)
    cgn_op_show_error_one('SRC_ENOENT', fmt, in_d, out_d)

    print("  Packet buffer errors:")
    cgn_op_show_error_one('BUF_ENOL3', fmt, in_d, out_d)
    cgn_op_show_error_one('BUF_ENOL4', fmt, in_d, out_d)
    cgn_op_show_error_one('BUF_ENOMEM', fmt, in_d, out_d)

    print("  PCP errors:")
    cgn_op_show_error_one('PCP_EINVAL', fmt, in_d, out_d)
    cgn_op_show_error_one('PCP_ENOSPC', fmt, in_d, out_d)

    print("  ALG errors:")
    cgn_op_show_error_one('ALG_ERR_INSP', fmt, in_d, out_d)
    cgn_op_show_error_one('ALG_ERR_SESS', fmt, in_d, out_d)
    cgn_op_show_error_one('ALG_ERR_PHOLE', fmt, in_d, out_d)

    print("  Other:")
    cgn_op_show_error_one('ERR_UNKWN', fmt, in_d, out_d)

    print()


#
# cgn_op_show_policy_one
#
def cgn_op_show_policy_one(pol):
    """Show one CGN policy"""

    print("Policy: %s" % (pol.get('name')))

    if "match_group" in pol:
        npf_show_address_group(pol.get('match_group'), "ipv4", None,
                               "Match address-group",
                               2, 22, 28,
                               4, 14, 34)

    if "exclude_ag" in pol:
        npf_show_address_group(pol.get('exclude_ag'), "ipv4", None,
                               "Exclude address-group",
                               2, 22, 28,
                               4, 14, 34)

    print("  %-32s %18s" % ("Interface", pol.get('interface')))
    print("  %-32s %18s" % ("Priority", pol.get('priority')))
    print("  %-32s %18s" % ("Pool", pol.get('pool')))
    print("  %-32s %18s" % ("Log all sessions",
                            yes_or_no(pol.get('log_sess_all'))))

    log_grp = pol.get('log_sess_group')
    if log_grp:
        npf_show_address_group(log_grp, "ipv4", None,
                               "Log select sessions",
                               2, 22, 28,
                               6, 12, 34)

    print("    %-30s %18s" % ("Log session start",
                              yes_or_no(pol.get('log_sess_start'))))
    print("    %-30s %18s" % ("Log session end",
                              yes_or_no(pol.get('log_sess_end'))))
    print("    %-30s %18s" % ("Log session periodically",
                              yes_or_no(pol.get('log_sess_periodic'))))

    sess_crtd = pol.get('sess_created')
    sess_dstrd = pol.get('sess_destroyed')

    print("  %-32s %18u" % ("Active subscribers", pol.get('source_count')))
    print("  %-32s %18s" % ("Active sessions",
                            num2str(sess_crtd - sess_dstrd, True)))
    print("    %-30s %18s" % ("Sessions created", num2str(sess_crtd, True)))
    print("    %-30s %18s" % ("Sessions destroyed", num2str(sess_dstrd, True)))

    sess2_crtd = pol.get('sess2_created')
    sess2_dstrd = pol.get('sess2_destroyed')

    if sess2_crtd > 0:
        print("  %-32s %18s" % ("Active sub-sessions",
                                num2str(sess2_crtd - sess2_dstrd, True)))
        print("    %-30s %18s" % ("Sub-sessions created",
                                  num2str(sess2_crtd, True)))
        print("    %-30s %18s" % ("Sub-sessions destroyed",
                                  num2str(sess2_dstrd, True)))

    print("  %-32s %18s" % ("Out, packets", num2str(pol.get('out_pkts'), True)))
    print("  %-32s %18s" % ("     bytes", num2str(pol.get('out_bytes'), True)))
    print("  %-32s %18s" % ("In,  packets", num2str(pol.get('in_pkts'), True)))
    print("  %-32s %18s" % ("     bytes", num2str(pol.get('in_bytes'), True)))
    print("  %-32s %18s" % ("     unknown source",
                            num2str(pol.get('unk_pkts_in'), True)))

    print("  %s" % ("Max Session Rates:"))
    print("    %-16s %-8s  %-8s" % ("Subscriber", "Max Rate", "Time"))

    # Subscriber max session rate list.  Always returns 5 entries.
    # Stop when we reach the first 'empty' entry.
    #
    sr_list = pol.get('subs_sess_rates')

    for i in range(0, len(sr_list)):
        if sr_list[i].get('max_sess_rate') == 0:
            break
        max_rate_tm = sr_list[i].get('time') / 1000000
        tmp = "%s" % (strftime("%F %H:%M:%S +0000", localtime(max_rate_tm)))

        print("    %-16s %8u  %s" % (sr_list[i].get('subscriber'),
                                     sr_list[i].get('max_sess_rate'),
                                     tmp))

    print()


#
# cgn_op_show_policy
#
def cgn_op_show_policy(n_opt):
    """Show a CGN policy"""

    policy_list = []

    cmd = "cgn-op show policy"
    if n_opt:
        cmd = "%s %s" % (cmd, n_opt)

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                cgn_dict = dp.json_command(cmd)
                tmp_list = cgn_dict.get('policies')
                if tmp_list:
                    policy_list.extend(tmp_list)

    #
    # The json returned by the dataplane should already be in order of
    # interface and priority
    #
    for pol in policy_list:
        cgn_op_show_policy_one(pol)


#
# cgn_op_clear_policy_stats
#
def cgn_op_clear_policy_stats(n_opt):
    """Clear a CGN policies statistics"""

    cmd = "cgn-op clear policy %s statistics" % (n_opt)

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                dp.string_command(cmd)


#
# cgn_op_clear_errors
#
def cgn_op_clear_errors():
    """Clear CGNAT errors"""

    cmd = "cgn-op clear errors"

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                dp.string_command(cmd)


#
# cgn_op_clear_alg_stats
#
def cgn_op_clear_alg_stats():
    """Clear CGNAT ALG stats"""

    cmd = "cgn-op clear alg stats"

    with vplaned.Controller() as controller:
        for dp in controller.get_dataplanes():
            with dp:
                dp.string_command(cmd)


#
# cgn_op_main
#
def cgn_op_main():
    """Main function"""

    s_opt = None
    n_opt = None
    c_opt = None
    d_opt = False
    stats_opt = False
    status_opt = False
    summ_opt = False
    ph_opt = None

    #
    # Parse options
    #
    try:
        opts, args = getopt.getopt(sys.argv[1:],
                                   "",
                                   ['show=', 'name=',
                                    'clear=', 'stats',
                                    'status', 'summary',
                                    'detail', 'ph='])

    except getopt.GetoptError as r:
        print(r, file=sys.stderr)
        cgn_usage()
        sys.exit(2)

    for opt, arg in opts:
        if opt in '--show':
            s_opt = arg

        if opt in '--name':
            n_opt = arg

        if opt in '--clear':
            c_opt = arg

        if opt in '--detail':
            d_opt = True

        if opt in '--stats':
            stats_opt = True

        if opt in '--status':
            status_opt = True

        if opt in '--summary':
            summ_opt = True

        if opt in '--ph':
            ph_opt = arg

    # show ...
    if s_opt:
        if s_opt == 'policy':
            cgn_op_show_policy(n_opt)

        if s_opt == 'errors':
            cgn_op_show_errors()

        if s_opt == 'summary':
            cgn_op_show_summary()

        if s_opt == 'alg':
            if ph_opt:
                cgn_op_show_alg_pinholes(ph_opt)
            elif summ_opt:
                cgn_op_show_alg_summary(d_opt)
            elif status_opt:
                cgn_op_show_alg_status()

    # clear ...
    if c_opt:
        if c_opt == 'policy' and stats_opt:
            cgn_op_clear_policy_stats(n_opt)

        if c_opt == 'errors':
            cgn_op_clear_errors()

        if c_opt == 'alg' and stats_opt:
            cgn_op_clear_alg_stats()


#
# main
#
if __name__ == '__main__':
    cgn_op_main()
