Skip to content

Instantly share code, notes, and snippets.

@chintanparikh
Last active August 29, 2015 14:19
Show Gist options
  • Save chintanparikh/b802e6f0e2f5b115de99 to your computer and use it in GitHub Desktop.
Save chintanparikh/b802e6f0e2f5b115de99 to your computer and use it in GitHub Desktop.
require_relative '../rtp_socket'
require 'socket'
socket = RTPSocket.new '0.0.0.0', ARGF.argv[0], ARGF.argv[1], ARGF.argv[2]
# @socket = RTPSocket.new '0.0.0.0', '5678', '0.0.0.0', '5000'
def parse command
case command
when "connect"
@socket.connect ARGF.argv[1], ARGF.argv[2]
when /^get/
filename = command.split(' ')[1]
@socket.send("GET #{filename}")
response = @socket.receive
File.open(filename, 'w') {|f| f.write(response)}
when /^post/
when /^window/
when 'disconnect'
begin
@socket.client_disconnect
rescue IOError
end
end
end
loop do
print "> "
command = gets.chomp
parse command
end
client.rb:10:in `parse': undefined method `connect' for nil:NilClass (NoMethodError)
from client.rb:30:in `block in <main>'
from client.rb:27:in `loop'
from client.rb:27:in `<main>'
require 'timeout'
require 'debugger'
class RTPSocket
def initialize ip_address, port, emu_ip, emu_port
@source_port = port
@source_ip = ip_address
@dest_port = nil
@dest_ip = nil
@emu_ip = emu_ip
@emu_port = emu_port
@connected = false
@terminated = false
@window = 1024
@timeout = 1
@rtt = 1
@mss = 1024
@buffer_size = @mss + 1024
@seq_num = 0
@send_seq_number = 0
@expected_seq_number = 0
@ack_number = 0
@udp_socket = UDPSocket.new
@udp_socket.bind(@source_ip, @source_port)
end
# Server socket will connect to an IP Address and Port if a SYN packet is received:
# Validate checksum. If SYN, extract sequence number, IP, port and checksum
# Randomly generate initial sequence number
# Send SYNACK back with ACK = sequence_num + 1. Also send servers initial seq num. Increment seq num
def connect ip, port
port = @source_port.to_i + 1
ip = @source_ip
@dest_ip = ip
@dest_port = port
@seq_num = Random.rand(1..(2**32))
# Send SYN
# Receive SYNACK
# Send ACK
# Wait for timeout
states = ['INITIAL', 'SENT_SYN', 'GOT_SYNACK', 'SENT_ACK']
state = 'INITIAL'
synack = nil
loop do
case state
when 'INITIAL'
puts "Sending SYN packet"
send_syn_packet @seq_num
state = 'SENT_SYN'
when 'SENT_SYN'
packet = receive_packet
if !packet.nil? and packet.SYN.to_b and packet.ACK.to_b
@ack_num = packet.seq_num + 1
synack = packet
puts "Got SYNACK packet"
state = 'GOT_SYNACK'
else
state = 'INITIAL'
end
when 'GOT_SYNACK'
puts "Sending ACK packet"
send_ack_packet synack
state = "SENT_ACK"
when 'SENT_ACK'
packet = receive_packet(@timeout*3)
if packet and packet.SYN.to_b and packet.ACK.to_b
puts "Got SYNACK packet, not connected yet"
state = 'GOT_SYNACK'
else
@connected = true
puts 'Connection established'
break
end
end
end
end
def client_disconnect
# States
# Sent FIN, waiting for FINACK
# Got FINACK, waiting for FIN
# Got FIN, sending FINACK
# Sent FINACK, waiting to see if we get another FIN until @timeout, otherwise disconenct
# If we get another FIN decrement state
states = ['INITIAL', 'SENT_FIN', 'GOT_FINACK', 'GOT_FIN', 'WAITING_FOR_TIMEOUT']
unless @connected
puts "Already disconnected"
return
end
state = states[0]
loop do
case state
when 'INITIAL'
fin_packet = make_fin_packet
puts "Sending FIN packet"
send_special_packet fin_packet
state = states[1]
when 'SENT_FIN'
packet = receive_packet
if packet and packet.FIN.to_b and packet.ACK.to_b
puts "Got FINACK packet"
state = states[2]
else
state = states[0]
end
when 'GOT_FINACK'
packet = receive_packet
if packet and packet.FIN.to_b
puts "Got FIN packet"
state = states[3]
else
state = states[1]
end
when 'GOT_FIN'
puts "Sending FINACK packet"
finack_packet = make_and_send_special_packet do |packet|
packet.FIN = true
packet.ACK = true
end
state = states[4]
when 'WAITING_FOR_TIMEOUT'
packet = receive_packet @timeout * 3
if packet and packet.FIN.to_b
puts "Got FIN, not disconnecting"
state = states[2]
elsif packet and packet.FIN.to_b and packet.ACK.to_b
puts "Got FINACK, not disconnecting"
state = states[3]
else
puts "No packet received, disconnecting"
@udp_socket.close
@connected = false
break
end
end
end
end
def server_disconnect
states = ['INITIAL', 'GOT_FIN', 'SENT_FINACK', 'SENT_FIN', 'GOT_FINACK']
state = states[1]
loop do
case state
when 'INITIAL'
packet = receive_packet
if packet and packet.FIN.to_b
puts "Got FIN packet"
state = 'GOT_FIN'
end
when 'GOT_FIN'
puts "Sending FINACK packet"
finack_packet = make_and_send_special_packet do |packet|
packet.FIN = true
packet.ACK = true
end
state = 'SENT_FINACK'
when 'SENT_FINACK'
fin_packet = make_fin_packet
puts "Sending FIN packet"
send_special_packet fin_packet
state = 'SENT_FIN'
when 'SENT_FIN'
packet = receive_packet
if packet and packet.FIN.to_b and packet.ACK.to_b
puts "Got FINACK packet"
state = 'GOT_FINACK'
else
state = 'GOT_FIN'
end
when 'GOT_FINACK'
packet = receive_packet @timeout * 3
if packet and packet.FIN.to_b and packet.ACK.to_b
state = 'GOT_FINACK'
puts "Got FINACK, not disconnecting"
elsif packet and packet.FIN.to_b
puts "Got FIN, not disconnecting"
state = 'GOT_FIN'
else
puts "No packet received, disconnecting"
@udp_socket.close
@connected = false
break
end
end
end
# Wait for FIN
# Send FINACK
# Send FIN
# Recieve FINACK
end
def listen
puts "Listening for a client to connect"
if @connected
puts "Already connected to a client"
return -1
end
# Get SYN
# Send SYNACK
# Get ACK
states = ['INITIAL', 'GOT_SYN', 'SENT_SYNACK', 'GOT_ACK']
state = 'INITIAL'
loop do
case state
when 'INITIAL'
packet = receive_packet
if packet and packet.SYN.to_b
puts 'Got SYN packet'
@dest_ip = packet.dest_ip
@dest_port = packet.dest_port
@ack_num = packet.seq_num + 1
@seq_num = Random.rand(1..(2**32))
state = 'GOT_SYN'
end
when 'GOT_SYN'
puts "Sending SYNACK packet"
send_syn_ack_packet @seq_num, @ack_num
state = 'SENT_SYNACK'
when 'SENT_SYNACK'
packet = receive_packet
if packet and packet.ACK.to_b
puts 'Got ACK packet'
state = 'GOT_ACK'
elsif packet and packet.SYN.to_b
state = 'GOT_SYN'
puts 'Got SYN packet'
else
state = "GOT_SYN"
end
when 'GOT_ACK'
packet = receive_packet @timeout*3
if packet and packet.SYN.to_b
state = 'GOT_SYN'
puts "Got SYN packet, not connected yet"
else
puts "Connected to client"
@connected = true
break
end
end
end
end
def packetize data
packets = []
byte_pointer = 0
while byte_pointer + @mss < data.length
bytes_to_send = data[byte_pointer..byte_pointer + @mss]
packet = make_packet(bytes_to_send)
packets.push(packet)
byte_pointer += @mss
end
last_bytes = data[byte_pointer..-1]
packet = make_packet(last_bytes)
packets.push(packet)
packets
end
def send data
packets = packetize data
packets.each do |packet|
ack = nil
send_packet packet, @timeout * 3
end
packet = make_packet nil
send_packet packet, @timeout * 3
end
def receive
data_bytes = ''
disconnect = false
length = 0
loop do
begin
packet = receive_packet
puts "Received packet - #{packet.data}" if packet
rescue Timeout::Error
continue
end
if packet and packet.FIN.to_b
disconnect = true
break
elsif packet and packet.data == '' and not packet.ACK.to_b
puts "Received entire message"
send_ack_packet packet
fallback = receive_packet
if fallback.nil?
break
else
send_ack_packet packet
end
elsif packet and not packet.ACK.to_b
length = packet.length
data_bytes += packet.data
send_ack_packet packet
end
end
puts "Received entire message"
if disconnect
server_disconnect
return nil
end
return data_bytes[0..length-1]
end
def send_packet packet, timeout = @timeout
puts "Sending packet #{packet.data} with sequence number #{packet.seq_num}"
ack_packet = nil
packet_string = packet.to_s
@seq_num += packet_string.bytesize
while ack_packet == nil
begin
Timeout.timeout(timeout) do
@udp_socket.send(packet_string, 0, @emu_ip, @emu_port)
puts "Sent packet with data #{packet.data}"
recv_packet = receive_packet
puts "Received packet with ack number #{recv_packet.ack_num}" if recv_packet
# debugger if recv_packet
if recv_packet and recv_packet.ACK.to_b
ack_packet = recv_packet
return ack_packet
end
end
rescue Timeout::Error
rescue Timeout::ExitException
rescue ArgumentError
end
end
end
def receive_packet timeout = @timeout
begin
Timeout.timeout(timeout) do
packet_string, addr = @udp_socket.recvfrom(@buffer_size)
packet = Packet.from_string(packet_string)
puts "Received packet with:"
puts "\t seq_num: #{packet.seq_num}"
puts "\t ack_num: #{packet.ack_num}"
puts "\t #{"SYN, " if packet.SYN.to_b}#{"ACK, " if packet.ACK.to_b}#{"FIN" if packet.FIN.to_b}"
return packet
# unless packet.corrupted?
# unless packet.is_duplicate?(@expected_seq_number)
# return packet
# else
# puts "Received duplicate packet"
# end
# else
# puts "Corrupted packets"
# end
end
rescue Timeout::Error
return nil
rescue Timeout::ExitException
return nil
end
end
def make_packet data
packet = Packet.new
packet.data = data
packet.source_ip = @source_ip
packet.source_port = @source_port
packet.dest_ip = @dest_ip
packet.dest_port = @dest_port
packet.seq_num = @seq_num
packet.SYN = false
packet.ACK = false
packet.FIN = false
packet.recalculate_checksum!
return packet
end
def make_syn_packet seq_num
packet = make_packet nil
packet.SYN = true
packet.seq_num = seq_num
packet.recalculate_checksum!
return packet
end
def make_fin_packet
packet = make_packet nil
packet.FIN = true
packet.recalculate_checksum!
return packet
end
def make_ack_packet packet_to_ack
packet = make_packet nil
packet.ACK = true
packet.ack_num = packet_to_ack.seq_num + 1
packet.recalculate_checksum!
return packet
end
def send_special_packet packet
puts "Sending packet with:"
puts "\t seq_num: #{packet.seq_num}"
puts "\t ack_num: #{packet.ack_num}"
puts "\t #{"SYN, " if packet.SYN.to_b}#{"ACK, " if packet.ACK.to_b}#{"FIN" if packet.FIN.to_b}"
@udp_socket.send(packet.to_s, 0, @emu_ip, @emu_port)
end
def make_and_send_special_packet
packet = make_packet nil
yield packet
packet.recalculate_checksum!
puts "Sending packet with:"
puts "\t seq_num: #{packet.seq_num}"
puts "\t ack_num: #{packet.ack_num}"
puts "\t #{"SYN" if packet.SYN.to_b}#{"ACK" if packet.ACK.to_b}#{"FIN" if packet.FIN.to_b}"
@udp_socket.send(packet.to_s, 0, @emu_ip, @emu_port)
end
def send_syn_packet seq_num
syn = make_syn_packet(seq_num)
packet_string = syn.to_s
puts "Sending SYN packet with seq num #{syn.seq_num}"
@seq_num = seq_num + syn.to_s.bytesize
@udp_socket.send(packet_string, 0, @emu_ip, @emu_port)
end
def send_ack_packet packet_to_ack
ack = make_ack_packet(packet_to_ack)
packet_string = ack.to_s
puts "Sending ACK packet with ack num #{ack.ack_num}"
@udp_socket.send(packet_string, 0, @emu_ip, @emu_port)
end
def send_syn_ack_packet seq_num, ack_num
packet = make_packet nil
packet.SYN = 1
packet.ACK = 1
packet.seq_num = seq_num
packet.ack_num = ack_num
packet.recalculate_checksum!
@seq_num = seq_num + packet.to_s.bytesize
puts "Sending SYNACK packet with seq num #{seq_num} ack num #{ack_num}"
@udp_socket.send(packet.to_s, 0, @emu_ip, @emu_port)
end
end
class Packet
HEADER_SIZE = 26
attr_accessor :source_ip, :dest_ip, :source_port, :dest_port, :seq_num, :ack_num, :SYN, :ACK, :FIN, :checksum, :length, :data
def initialize
@source_ip = nil
@source_port = nil
@dest_ip = nil
@dest_port = nil
@seq_num = 0
@ack_num = 0
@SYN = false
@ACK = false
@FIN = false
@checksum = nil
@length = 0
@data = nil
end
def to_a
flags = 0
flags |= @SYN.to_i << 2
flags |= @ACK.to_i << 1
flags |= @FIN.to_i << 0
[@source_ip.split('.').map{|e| e.to_i}, @dest_ip.split('.').map{|e| e.to_i}, @source_port.to_i, @dest_port.to_i, @seq_num, @ack_num, flags, @checksum, @length, @data].flatten
end
def to_s
to_a.pack('CCCCCCCCSSLLSSCa*')
end
def self.from_string packet_string
pack = Packet.new
packet_array = packet_string.unpack('CCCCCCCCSSLLSSCa*')
pack.source_ip = packet_array[0..3].join('.')
pack.dest_ip = packet_array[4..7].join('.')
pack.source_port = packet_array[8]
pack.dest_port = packet_array[9]
pack.seq_num = packet_array[10]
pack.ack_num = packet_array[11]
flags = packet_array[12]
pack.SYN = flags & 100
pack.ACK = flags & 10
pack.FIN = flags & 1
pack.checksum = packet_array[13]
pack.length = packet_array[14]
pack.data = packet_array[15]
return pack
end
def recalculate_checksum!
@checksum = checksum(self)
@length = @data.length unless @data.nil?
end
# needs to return a 16 bit number
def checksum packet
return 1
end
def corrupted?
return false
end
def is_duplicate? num
return false
end
end
class TrueClass
def to_i
1
end
def to_b
self
end
end
class FalseClass
def to_i
0
end
def to_b
self
end
end
class Integer
def to_b
!self.zero?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment