Saturday, June 11, 2016

Pinging to a host using raw sockets

Raw sockets API is an interesting interface available on Unix-like operating systems to put our hands on network packets in their 'raw form' that includes the headers. Usually TCP and UDP sockets which we often use are a only giving us access to the payload of a transport layer datagram. But, there are times where we need to see the IP packet or may be the whole ethernet frame without loosing any content. Raw socket are the solution for this.

In the following example I'm presenting a simple ping-like program written in python that utilizes a raw socket to create the content of an IP packet with an ICMP payload.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import socket
import sys
import time
from struct import pack, unpack

# checksum function
def checksum(msg):
    s = 0
    # loop taking 2 characters at a time
    for i in range(0, len(msg), 2):
        w = (ord(msg[i]) << 8) + (ord(msg[i+1]) )
        s = s + w

    s = (s>>16) + (s & 0xffff);
    #s = s + (s >> 16);
    #complement and mask to 4 byte short
    s = ~s & 0xffff
    return s

#create a raw socket
try:
    # this socket is to send custom made raw IP packets
    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)

    # this socket is to receive ICMP packets
    s2 = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

    print 'Sockets created'

except socket.error, msg:
    print 'Socket failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

#create a raw socket
try:
    # binding to the IP address configured to the network interface in my computer
    # through which I need to send and receive packets.
    s.bind(('10.22.220.239',0))
    s2.bind(('10.22.220.239',0))

    print 'Binding sockets was successful'

except socket.error, msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

num_rounds = 1

while num_rounds:
    # creating a packet to send
    packet = ''
    source_ip = '10.22.220.239'
    dest_ip = '8.8.8.8' # or socket.gethostbyname('www.google.com')

    # ip header fields
    ip_ihl =5
    ip_ver = 4
    ip_tos = 0
    ip_tot_len = 0 #kernel will fill the correct total length
    ip_id = 54321 #Id of this packet
    ip_frag_off = 0
    ip_ttl = 255
    #ip_proto = socket.IPPROTO_TCP
    ip_proto = socket.IPPROTO_ICMP
    ip_check = 0 # kernel will fill the correct checksum
    ip_saddr = socket.inet_aton (source_ip) #Spoof the source ip address if you want to
    ip_daddr = socket.inet_aton (dest_ip)
    ip_ihl_ver = (ip_ver << 4) + ip_ihl

    # the ! in the pack format string means network order
    ip_header = pack('!BBHHHBBH4s4s', ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)

    ICMP_ECHO_REQUEST = 8

    # Header is type (8), code (8), checksum (16), id (16), sequence (16)
    #icmp_header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
    icmp_header = pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, 1, 1)

    icmp_data = 192 * 'Q'
    # Calculate the checksum on the data and the dummy header.
    my_checksum = checksum(icmp_header + icmp_data)
    # Now that we have the right checksum, we put that in.
    # It's just easier
    # to make up a new header than to stuff it into the
    # dummy.
    #icmp_header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
    icmp_header = pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), 1, 1)

    # final full packet
    packet = ip_header + icmp_header + icmp_data

    #Send the packet finally - the port specified has no effect
    s.sendto(packet, (dest_ip , 0 ))    # put this in a loop
    print 'sent a packet'
    #---------------------------------------------------------------------------
    # receive a packet
    packet = s2.recvfrom(65565)

    #packet string from tuple
    packet = packet[0]

    #take first 20 characters for the ip header
    ip_header = packet[0:20]

    #now unpack them :)
    iph = unpack('!BBHHHBBH4s4s', ip_header)

    version_ihl = iph[0]
    version = version_ihl >> 4
    ihl = version_ihl & 0xF

    iph_length = ihl * 4
    ttl = iph[5]
    protocol = iph[6]
    s_addr = socket.inet_ntoa(iph[8])
    d_addr = socket.inet_ntoa(iph[9])

    print 'received a packet'
    print 'Version : ' + str(version) + ' IP Header Length : ' + str(iph_length) + ' TTL : ' + str(ttl) + ' Protocol : ' + str(protocol) + ' Source Address : ' + str(s_addr) + ' Destination Address : ' + str(d_addr)

    time.sleep(1)
    #num_rounds = 0

Perhaps I would write a more comprehensive blog post about how to use raw sockets. Anyway, everything depends on whether I can spend an enough time on such an exercise. Anyway, let's see. Until then, that's all folks!

No comments:

Post a Comment