# -*- coding: utf-8 -*-
#
##########################################################################
# pyeole.diagnose - Diagnostic tools for EOLE
# Copyright © 2013 Pôle de compétences EOLE <eole@ac-dijon.fr>
#
# License CeCILL:
#  * in french: http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
#  * in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
##########################################################################

"""Diagnostic tools for EOLE

Test external TCP/UDP daemon
----------------------------

The diagnose module allows you to test connexion in UDP/TCP services.

.. Warning::

    UDP is a connectionless protocol so there is no equivalent to a TCP SYN packet.
    The absence of a response to infer that a port is open.
    :wp:`Port_scanner#UDP_scanning`

    test_upd is trusted only if port is closed.

Examples::

Test UDP service:

    >>> from pyeole.diagnose import test_upd
    >>> test_udp('localhost', 123)
    True
    >>> test_udp('localhost', 124)
    False
    >>> test_udp('localhost', 123, 4)
    True

Test TCP service:

    >>> from pyeole.diagnose import test_tcp
    >>> test_tcp('localhost', 22)
    True
    >>> test_tcp('localhost', 23)
    False
    >>> test_tcp('localhost', 22, 4)
    True

Test HTTP reachability
----------------------


Examples::

    >>> from pyeole.diagnose import test_http
    >>> test_http('http://localhost:5000')
    True
    >>> test_http('http://localhost/nothing')
    False

"""
from socket import socket
from socket import AF_INET
from socket import SOCK_STREAM
from socket import SOCK_RAW
from socket import getprotobyname
from socket import error as socket_error

from dpkt.udp import UDP
from dpkt.ip import IP
from dpkt.icmp import ICMP

import logging

log = logging.getLogger(__name__)

from pyeole.i18n import i18n
_ = i18n('diagnose')


def test_udp(ip_address, port, timeout=1):
    """
    test if a udp service is alive

    :param ip_address: destination ip
    :param port: destination port
    :return: if the port is reachable on a address
    :rtype: `bool`

    """
    log.debug(u'Test UDP connection on {0} port {1}'.format(ip_address, port))
    udp_socket = socket(AF_INET, SOCK_RAW, getprotobyname('udp'))
    udp_socket.settimeout(timeout)
    udp_socket.connect((ip_address, int(port)))
    dest_ip = udp_socket.getpeername()[0]
    local_port = udp_socket.getsockname()[1]
    udp_data = UDP(dport=int(port), sport=local_port, data=0)
    #for ICMP Destination unreachable (Port unreachable)
    icmp_socket = socket(AF_INET, SOCK_RAW, getprotobyname('icmp'))
    icmp_socket.settimeout(timeout)
    udp_socket.send(str(udp_data))
    ret = True
    try:
        buf, addr = icmp_socket.recvfrom(1024)
        data = IP(buf).data.data
        if addr[0] == dest_ip and type(data) == ICMP.Unreach:
            ret = False
    except socket_error:
        #this append when open, filtering, unreachable host or ...
        pass
    udp_socket.close()
    icmp_socket.close()
    return ret


def test_tcp(ip_address, port, timeout=1):
    """
    test if a tcp service is alive

    :param ip_address: destination ip
    :param port: destination port
    :return: if the port is reachable on a address
    :rtype: `bool`

    """
    log.debug(u'Test TCP connection on {0} port {1}'.format(ip_address, port))
    try:
        port = int(port)
        if port > 65535 or port < 1:
            raise TypeError
    except TypeError:
        raise TypeError(_(u'Tested port for tcp connection to {} is not valid: {}').format(ip_address, port))
    try:
        soc = socket(AF_INET, SOCK_STREAM)
        soc.settimeout(int(timeout))
        soc.connect((ip_address, port))
        soc.close()
    except socket_error:
        return False
    return True
