# Copyright (c) 2011 Mattias Nissler <mattias.nissler@gmx.de>
#
# 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.

import socket
import struct

# from net/route.h
RTM_ADD        = 0x1     # Add Route
RTM_DELETE     = 0x2     # Delete Route
RTM_CHANGE     = 0x3     # Change Metrics or flags
RTM_GET        = 0x4     # Report Metrics
RTM_LOSING     = 0x5     # Kernel Suspects Partitioning
RTM_REDIRECT   = 0x6     # Told to use different route
RTM_MISS       = 0x7     # Lookup failed on this address
RTM_LOCK       = 0x8     # fix specified metrics
RTM_OLDADD     = 0x9     # caused by SIOCADDRT
RTM_OLDDEL     = 0xa     # caused by SIOCDELRT
RTM_RESOLVE    = 0xb     # req to resolve dst to LL addr
RTM_NEWADDR    = 0xc     # address being added to iface
RTM_DELADDR    = 0xd     # address being removed from iface
RTM_IFINFO     = 0xe     # iface going up/down etc.
RTM_NEWMADDR   = 0xf     # mcast group membership being added to if
RTM_DELMADDR   = 0x10    # mcast group membership being deleted

RTF_UP         = 0x1             # route usable
RTF_GATEWAY    = 0x2             # destination is a gateway
RTF_HOST       = 0x4             # host entry (net otherwise)
RTF_REJECT     = 0x8             # host or net unreachable
RTF_DYNAMIC    = 0x10            # created dynamically (by redirect)
RTF_MODIFIED   = 0x20            # modified dynamically (by redirect)
RTF_DONE       = 0x40            # message confirmed
RTF_DELCLONE   = 0x80            # delete cloned route
RTF_CLONING    = 0x100           # generate new routes on use
RTF_XRESOLVE   = 0x200           # external daemon resolves name
RTF_LLINFO     = 0x400           # generated by link layer (e.g. ARP)
RTF_STATIC     = 0x800           # manually added
RTF_BLACKHOLE  = 0x1000          # just discard pkts (during updates)
RTF_PROTO2     = 0x4000          # protocol specific routing flag
RTF_PROTO1     = 0x8000          # protocol specific routing flag

RTF_PRCLONING  = 0x10000         # protocol requires cloning
RTF_WASCLONED  = 0x20000         # route generated through cloning
RTF_PROTO3     = 0x40000         # protocol specific routing flag
RTF_LOCAL      = 0x200000        # route represents a local address
RTF_BROADCAST  = 0x400000        # route represents a bcast address
RTF_MULTICAST  = 0x800000        # route represents a mcast address
RTF_IFSCOPE    = 0x1000000       # has valid interface scope
RTF_CONDEMNED  = 0x2000000       # defunct; no longer modifiable

RTA_DST        = 0x1     # destination sockaddr present
RTA_GATEWAY    = 0x2     # gateway sockaddr present
RTA_NETMASK    = 0x4     # netmask sockaddr present
RTA_GENMASK    = 0x8     # cloning mask sockaddr present
RTA_IFP        = 0x10    # interface name sockaddr present
RTA_IFA        = 0x20    # interface addr sockaddr present
RTA_AUTHOR     = 0x40    # sockaddr for author of redirect
RTA_BRD        = 0x80    # for NEWADDR, broadcast or p-p dest addr

RTM_VERSION    = 5

PF_ROUTE = 17

STRUCT_RTMSG = struct.Struct('HBBHiiHiiiI3Ii10I')

def _sendRouteMsg(type, index = 0, flags = 0, addrs = {}):
    def add_addr((addr_flags, payload), (addr, flag)):
        if not addr:
            return (addr_flags, payload)

        return (addr_flags | flag, payload + addr.encode())

    (addr_flags, payload) = reduce(add_addr,
                                   [ (addrs['dst'], RTA_DST),
                                     (addrs['gateway'], RTA_GATEWAY),
                                     (addrs['netmask'], RTA_NETMASK) ],
                                   (0, ''))
    msglen = STRUCT_RTMSG.size + len(payload)
    data = STRUCT_RTMSG.pack(msglen, RTM_VERSION, type, index, flags, addr_flags, *((0,) * 19))

    sock = socket.socket(PF_ROUTE, socket.SOCK_RAW)
    try:
        sock.send(data + payload)
    finally:
        sock.close()

def addNet(dst = None, gateway = None, netmask = None, interface = None):
    flags = RTF_STATIC | RTF_UP
    if gateway:
        flags |= RTF_GATEWAY
    elif interface:
        gateway = interface
    _sendRouteMsg(type = RTM_ADD, flags = flags,
                  addrs = dict(dst = dst, gateway = gateway, netmask = netmask))
