Last active
July 13, 2016 09:29
-
-
Save stereocat/6839659 to your computer and use it in GitHub Desktop.
simple-router + udp echo server function using Pio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'pio-l4hdr' | |
dst_mac = "54:52:00:01:00:02" | |
src_mac = "04:20:9a:44:cf:63" | |
## test data 1 | |
## tcp syn packet, checksum: 0x08e4 | |
## condition: has options. | |
src_ip = "192.168.1.16" | |
dst_ip = "192.168.1.1" | |
src_port = 53920 | |
dst_port = 7 | |
seq_number = 0x6b7884aa | |
ack_number = 0x0 | |
control_flag = Pio::TcpHeader::CF_SYN | |
window = 0x2000 | |
urg_pointer = 0x0 | |
options = [ | |
{ :mss => { :segment_size => 1460 } }, | |
:noop, | |
{ :wsopt => { :shift_count => 8 } }, | |
:noop, | |
:noop, | |
:sackp | |
] | |
payload = '' | |
# ## test data2 | |
# ## echo response, checksum: 0x8425 | |
# ## condition: no options | |
# src_ip = "192.168.1.1" | |
# dst_ip = "192.168.1.16" | |
# src_port = 7 | |
# dst_port = 53920 | |
# seq_number = 0xd6ccd4c0 | |
# ack_number = 0x6b7884b0 | |
# control_flag = Pio::TcpHeader::CF_ACK | Pio::TcpHeader::CF_PSH | |
# window = 0x101b | |
# urg_pointer = 0x0 | |
# options = [] | |
# payload = %q/abcde/ | |
# ## sample, for options-test of eol/align | |
# options = [ | |
# { :mss => { :segment_size => 0x0586 } }, | |
# :noop, { :wsopt => { :shift_count => 6 } }, | |
# :noop, :noop, { :sack => { | |
# :sequence_number_list => [ 0xd8fd48ae, 0xd8fd48af ] | |
# }}, | |
# :sackp, :eol, :eol # pattern1 (eol at last) | |
# # :nop, :nop, :sackp # pattern2 (32bit align) | |
# # :eol, :sackp, :eol # pattern3 (irregular eol) | |
# ] | |
############################################################ | |
# topt0 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 0 | |
# ) | |
# topt1 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 1 | |
# ) | |
# topt2 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 2, | |
# :tlv_body => { | |
# # :tlv_length => 0x04, | |
# :segment_size => 0x0586 | |
# } | |
# ) | |
# topt3 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 3, | |
# :tlv_body => { | |
# # :tlv_length => 3, | |
# :shift_count => 0x06 | |
# } | |
# ) | |
# topt4 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 4 | |
# ) | |
# topt5 = Pio::TcpOptionalTlv.new( | |
# :tlv_type => 5, | |
# :tlv_body => { | |
# # :tlv_length => 0x0a, | |
# :sequence_number_list => [ | |
# 0xd8fd48ae, 0xd8fd48af | |
# ] | |
# } | |
# ) | |
# # print topt5.to_binary_s | |
# thdr = Pio::TcpHeader.new( | |
# :src_port => src_port, | |
# :dst_port => dst_port, | |
# :seq_number => seq_number, | |
# :ack_number => ack_number, | |
# :checksum => 0xefdc, # dummy! | |
# :urg_pointer => urg_pointer, | |
# :optional_tlv => [ | |
# topt2, | |
# topt1, topt3, | |
# topt1, topt1, topt5, | |
# # topt4, topt0, topt0, # pattern1 (eol at last) | |
# # topt1, topt1, topt4, # pattern2 (32bit align) | |
# topt0, topt4, topt1 # pattern3 (irregular eol) | |
# ], | |
# :payload => "abcde" | |
# ) | |
### tests | |
# print thdr.to_binary_s | |
# => | |
# stereocat@oftest06:~/training/packetgen$ ruby packetgen_tcp.rb | od -x | |
# 0000000 cdab 2301 3412 7856 6587 2143 0260 0000 | |
# 0000020 dcef 0000 0402 8605 0301 0603 0101 0a05 | |
# 0000040 fdd8 ae48 fdd8 af48 0204 0000 6261 6463 | |
# 0000060 0065 | |
# 0000061 | |
# stereocat@oftest06:~/training/packetgen$ | |
# print thdr.optional_tlv.to_binary_s | |
# => | |
# stereocat@oftest06:~/training/packetgen$ ruby packetgen_tcp.rb | od -x | |
# 0000000 0402 8605 0301 0603 0101 0a05 fdd8 ae48 | |
# 0000020 fdd8 af48 0400 0102 | |
# 0000030 | |
# stereocat@oftest06:~/training/packetgen$ | |
# options: 24bytes | |
# thdr.optional_tlv.to_binary_s.each_byte do | each | | |
# puts "byte : #{ sprintf("%02x", each) }" | |
# end | |
# => | |
# stereocat@oftest06:~/training/packetgen$ ruby packetgen_tcp.rb | |
# byte : 02 | |
# byte : 04 | |
# byte : 05 | |
# byte : 86 | |
# byte : 01 | |
# byte : 03 | |
# byte : 03 | |
# byte : 06 | |
# byte : 01 | |
# byte : 01 | |
# byte : 05 | |
# byte : 0a | |
# byte : d8 | |
# byte : fd | |
# byte : 48 | |
# byte : ae | |
# byte : d8 | |
# byte : fd | |
# byte : 48 | |
# byte : af | |
# byte : 00 | |
# byte : 04 | |
# byte : 02 | |
# byte : 01 | |
# stereocat@oftest06:~/training/packetgen$ | |
############################################################ | |
tdgm = Pio::TcpDatagram.new( | |
:src_ip => src_ip, | |
:dst_ip => dst_ip, | |
:src_port => src_port, | |
:dst_port => dst_port, | |
:seq_number => seq_number, | |
:ack_number => ack_number, | |
:control_flag => control_flag, | |
:window => window, | |
:urg_pointer => urg_pointer, | |
:optional_tlv => options, | |
:payload => payload, | |
) | |
# print tdgm.to_binary | |
############################################################ | |
ippct = Pio::IPv4Packet.new( | |
:payload => tdgm | |
) | |
ipfrm = Pio::IPv4Frame.new( | |
:src_mac => src_mac, | |
:dst_mac => dst_mac, | |
:packet => ippct | |
) | |
print ipfrm.to_binary |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative "pio-l4hdr" | |
dst_mac = "54:52:00:01:00:02" | |
src_mac = "04:20:9a:44:cf:63" | |
src_ip = "192.168.1.83" | |
dst_ip = "192.168.1.103" | |
src_port = 7 | |
dst_port = 64942 | |
payload = %q/abcde/ | |
udgm = Pio::UdpDatagram.new( | |
:src_ip => src_ip, | |
:dst_ip => dst_ip, | |
:src_port => src_port, | |
:dst_port => dst_port, | |
:payload => payload | |
) | |
#print udgm.to_binary | |
ippct = Pio::IPv4Packet.new( | |
:payload => udgm | |
) | |
#print ippct.to_binary | |
ipfrm = Pio::IPv4Frame.new( | |
:src_mac => src_mac, | |
:dst_mac => dst_mac, | |
:packet => ippct | |
) | |
print ipfrm.to_binary | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
require_relative "pio-l4hdr" | |
ipfrm = Pio::IPv4Frame.read $stdin | |
puts "* ipv4 frame" | |
puts " src mac : #{ ipfrm.src_mac }" | |
puts " dst mac : #{ ipfrm.dst_mac }" | |
puts " ether type : #{ sprintf("%04x", ipfrm.ether_type) }" | |
puts "* ipv4 packet" | |
ippct = ipfrm.packet | |
puts " version : #{ ippct.version }" | |
puts " header length: #{ ippct.header_length * 4 } (octets)" | |
puts " tos : #{ ippct.tos }" | |
puts " total length : #{ ippct.total_length } (octets)" | |
puts " identificat : #{ ippct.identification }" | |
puts " flags : #{ ippct.flags }" | |
puts " frag offset : #{ ippct.fragment_offset }" | |
puts " ttl : #{ ippct.ttl }" | |
puts " protocol : #{ ippct.protocol }" | |
puts " checksum : #{ sprintf("%04x", ippct.header_checksum) }" | |
puts " src addr : #{ ippct.src_ip }" | |
puts " dst addr : #{ ippct.dst_ip }" | |
puts " options : #{ ippct.options }" | |
puts " valid? : #{ ippct.valid? }" | |
puts "* ipv4 payload (proto:#{ippct.protocol} datagram)" | |
dgm = ippct.datagram | |
puts " src port : #{ dgm.src_port }" | |
puts " dst port : #{ dgm.dst_port }" | |
puts " seq number : #{ sprintf("%08x", dgm.seq_number) }" | |
puts " ack number : #{ sprintf("%08x", dgm.ack_number) }" | |
puts " data offset : #{ dgm.data_offset }" | |
puts " ctrl flag : #{ sprintf("%06b", dgm.control_flag) }" | |
puts " window size : #{ sprintf("%04x", dgm.window) }" | |
puts " checksum : #{ sprintf("%04x", dgm.checksum) }" | |
puts " urg pointer : #{ sprintf("%04x", dgm.urg_pointer) }" | |
puts " options : #{ (dgm.optional_tlv.map{ |e| e.tlv_type }).join(", ") }" | |
# puts " options : #{ dgm.optional_tlv }" | |
# if dgm.optional_tlv | |
# dgm.optional_tlv.each do | each | | |
# puts " - type : #{ each.tlv_type }" | |
# end | |
# end | |
puts " payload : #{ dgm.payload }" | |
puts " valid? : #{ dgm.valid? }" | |
############################################################ | |
# hdr = Pio::TcpHeader.read $stdin | |
# puts "* tcp datagram" | |
# puts " src port : #{ hdr.src_port }" | |
# puts " dst port : #{ hdr.dst_port }" | |
# puts " seq number : #{ sprintf("%08x", hdr.seq_number) }" | |
# puts " ack number : #{ sprintf("%08x", hdr.ack_number) }" | |
# puts " data offset : #{ sprintf("%x", hdr.data_offset) }" | |
# puts " ctrl flag : #{ sprintf("%x", hdr.control_flag) }" | |
# puts " window size : #{ sprintf("%04x", hdr.window) }" | |
# puts " checksum : #{ sprintf("%04x", hdr.checksum) }" | |
# puts " urg pointer : #{ sprintf("%04x", hdr.urg_pointer) }" | |
# puts " options : #{ hdr.optional_tlv }" | |
# if hdr.optional_tlv | |
# hdr.optional_tlv.each do | each | | |
# puts " - type : #{ each.tlv_type }" | |
# end | |
# end | |
# puts " payload : #{ hdr.payload }" | |
# src_ip = "192.168.1.16" | |
# dst_ip = "192.168.1.1" | |
# dgm = Pio::TcpDatagram.new( | |
# :src_ip => src_ip, | |
# :dst_ip => dst_ip, | |
# :datagram => hdr, | |
# ) | |
# puts " valid? : #{ dgm.valid? }" | |
############################################################ | |
# TODO | |
# OK? | |
# - oiptional_tlv ナシのデータをよむとエラー | |
# => onlyif で data_offset == 5 のときは array いれない。 | |
# - payload がよめてない。-> count_bytes_remaining がよくわからん。動い | |
# てる気配がない。 | |
# => 'rest' keyword でのこり全部よむ | |
# - option が 32bit align only (eolでおわらない)場合にエラー | |
# - option 中の変なところに eol がはいった場合はどうなる? (よみとばしできる?) | |
# => 上ふたつは option 読んだ分のカウント + skip で対応 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# $LOAD_PATH.unshift File.expand_path( File.join File.dirname( __FILE__ ), "lib" ) | |
require_relative "pio-l4hdr" | |
# stereocat@oftest06:~/training/packetgen$ ruby pgdummy.rb | od -x | |
# 0000000 0000 0100 0200 1800 c1b9 c34f 0008 0045 | |
# 0000020 2400 712a 0040 113f a48c a8c0 1002 a8c0 | |
# 0000040 5301 f285 0700 1000 98a5 6a6d 5c33 5f5a | |
# 0000060 6254 0000 0000 0000 0000 0000 | |
# 0000074 | |
# stereocat@oftest06:~/training/packetgen$ | |
# str = ["0000000100020018b9c14fc30800450000242a7140003f118ca4c0a80210c0a8015385f200070010a5986d6a335c5a5f546200000000000000000000"].pack("H*") | |
# ipfrm = Pio::IPv4Frame.read str | |
ipfrm = Pio::IPv4Frame.read $stdin | |
puts "* ipv4 frame" | |
puts " src mac : #{ ipfrm.src_mac }" | |
puts " dst mac : #{ ipfrm.dst_mac }" | |
puts " ether type : #{ sprintf("%04x", ipfrm.ether_type) }" | |
puts "* ipv4 packet" | |
# ippct = Pio::IPv4Packet.read $stdin | |
ippct = ipfrm.packet | |
puts " version : #{ ippct.version }" | |
puts " header length: #{ ippct.header_length * 4 } (octets)" | |
puts " tos : #{ ippct.tos }" | |
puts " total length : #{ ippct.total_length } (octets)" | |
puts " identificat : #{ ippct.identification }" | |
puts " flags : #{ ippct.flags }" | |
puts " frag offset : #{ ippct.fragment_offset }" | |
puts " ttl : #{ ippct.ttl }" | |
puts " protocol : #{ ippct.protocol }" | |
puts " checksum : #{ sprintf("%04x", ippct.header_checksum) }" | |
puts " src addr : #{ ippct.src_ip }" | |
puts " dst addr : #{ ippct.dst_ip }" | |
puts " options : #{ ippct.options }" | |
puts " valid? : #{ ippct.valid? }" | |
puts "* ipv4 payload (proto:#{ippct.protocol} datagram)" | |
dgm = ippct.datagram | |
puts " src port : #{ dgm.src_port }" | |
puts " dst port : #{ dgm.dst_port }" | |
puts " total length : #{ dgm.total_length } (octets)" | |
puts " checksum : #{ sprintf("%04x", dgm.checksum) }" | |
puts " payload : #{ dgm.payload }" | |
puts " valid? : #{ dgm.valid? }" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
require "forwardable" | |
require 'rubygems' | |
require 'bindata' | |
require 'pio' | |
module Pio | |
module HeaderUtil | |
def get_checksum csum, val | |
# $stderr.puts("val : #{sprintf("%06x, %04x", csum, val)}") | |
sum = ( ~csum & 0xffff ) + val | |
while sum > 0xffff | |
sum = ( sum & 0xffff ) + ( sum >> 16 ) | |
end | |
~sum & 0xffff | |
end | |
def get_str_checksum csum, str | |
dbyte = 0 | |
str.each_byte do | byte | | |
if dbyte == 0 | |
dbyte = byte << 8 | |
else | |
dbyte = dbyte + byte | |
csum = get_checksum( csum, dbyte ) | |
dbyte = 0 | |
end | |
end | |
# padding (align 16bit) | |
csum = get_checksum( csum, dbyte ) if dbyte != 0 | |
return csum | |
end | |
end | |
############################################################ | |
## IPv4 | |
############################################################ | |
class IPv4FrameHeader < BinData::Record | |
extend Type::EthernetHeader | |
endian :big | |
ethernet_header :ether_type => 0x0800 | |
end | |
class IPv4Frame | |
extend Forwardable | |
attr_reader :src_mac | |
attr_reader :dst_mac | |
attr_reader :frame_hdr | |
attr_reader :packet | |
def_delegators :@frame_hdr, :ether_type | |
# minimum frame length = 60 octets | |
MIN_FRAME_LEN = 60 | |
# ethernet header length = 14 octets | |
# [ mac:6octets * 2 + ether-type 2octets ] | |
ETH_HEADER_LEN = 14 | |
# minimum packet length (octets) | |
# packet must be larger than 46 octets = 60 - 14 | |
MIN_PACKET_LEN = MIN_FRAME_LEN - ETH_HEADER_LEN | |
def initialize opts | |
if opts[ :frame_header ] | |
@frame_hdr = opts[ :frame_header ] # IPv4FrameHeader | |
@packet = opts[ :packet ] # IPv4Packet | |
@src_mac = Mac.new( @frame_hdr.source_mac.to_s ) | |
@dst_mac = Mac.new( @frame_hdr.destination_mac.to_s ) | |
else | |
@src_mac = Mac.new( opts[ :src_mac ] ) | |
@dst_mac = Mac.new( opts[ :dst_mac ] ) | |
@packet = opts[ :packet ] # IPv4Packet | |
@frame_hdr = IPv4FrameHeader.new( | |
:source_mac => @src_mac.to_ary, | |
:destination_mac => @dst_mac.to_ary | |
) | |
end | |
end | |
def to_binary | |
padcount = 0 | |
if @packet.total_length < MIN_PACKET_LEN | |
padcount = MIN_PACKET_LEN - @packet.total_length | |
end | |
@frame_hdr.to_binary_s + @packet.to_binary + ( "\000" * padcount ) | |
end | |
def self.read io | |
io = StringIO.new(io, 'r') if String === io | |
# 分割して read するので IO class compatible にする。 | |
frame = IPv4FrameHeader.read io | |
ippct = IPv4Packet.read io | |
IPv4Frame.new( | |
:frame_header => frame, | |
:packet => ippct | |
) | |
end | |
end | |
class IPv4Header < BinData::Record | |
endian :big | |
bit4 :version, :value => 4 | |
bit4 :header_length, :initial_value => 5 # without options | |
uint8 :tos, :initial_value => 0 | |
uint16 :total_length, :value => lambda { | |
payload.bytesize + header_length_in_bytes | |
} | |
uint16 :identification | |
bit3 :flags, :initial_value => 2 # 2:don't fragment | |
bit13 :fragment_offset, :initial_value => 0 | |
uint8 :ttl, :initial_value => 64 | |
uint8 :protocol | |
uint16 :header_checksum, :initial_value => 0 | |
ip_address :src_ip | |
ip_address :dst_ip | |
string :options, :read_length => lambda { | |
# length of ipv4 header without options is 20 octets | |
header_length_in_bytes - 20 | |
} | |
string :payload, :read_length => lambda { | |
total_length - header_length_in_bytes | |
} | |
def header_length_in_bytes | |
# IHL(internet header length) is 4 octets count | |
header_length * 4 | |
end | |
end | |
class IPv4Packet | |
extend Forwardable | |
include HeaderUtil | |
attr_reader :packet | |
attr_reader :src_ip | |
attr_reader :dst_ip | |
def_delegator :@packet, :to_binary_s, :to_binary | |
def_delegators :@packet, :version | |
def_delegators :@packet, :header_length | |
def_delegators :@packet, :tos | |
def_delegators :@packet, :total_length | |
def_delegators :@packet, :identification | |
def_delegators :@packet, :flags | |
def_delegators :@packet, :fragment_offset | |
def_delegators :@packet, :ttl | |
def_delegators :@packet, :protocol | |
def_delegators :@packet, :header_checksum | |
def_delegators :@packet, :src_ip | |
def_delegators :@packet, :dst_ip | |
def_delegators :@packet, :options | |
def_delegators :@packet, :payload | |
@@id = nil | |
def id | |
unless @@id | |
srand Random.new_seed | |
@@id = Random.rand(1..65535) | |
else | |
@@id = (@@id + 1) & 0xffff | |
end | |
end | |
def initialize opts | |
if opts[ :packet ] | |
@packet = opts[ :packet ] | |
@src_ip = @packet.src_ip | |
@dst_ip = @packet.dst_ip | |
@protocol = @packet.protocol | |
@payload = @packet.payload | |
else | |
if opts[ :payload ] | |
@payload = opts[ :payload ] | |
case @payload | |
when TransportLayerProtocol | |
@src_ip = @payload.src_ip | |
@dst_ip = @payload.dst_ip | |
@protocol = @payload.protocol | |
else | |
raise ArgmentError, "## TBD ##" | |
end | |
@packet = IPv4Header.new( | |
:src_ip => @src_ip.to_ary, | |
:dst_ip => @dst_ip.to_ary, | |
:protocol => @protocol, | |
:identification => id, | |
:payload => @payload.to_binary | |
) | |
@packet.header_checksum = header_checksum | |
else | |
raise ArgmentError | |
end | |
end | |
end | |
def self.read io | |
iphdr = IPv4Header.read io | |
IPv4Packet.new( :packet => iphdr ) | |
end | |
def datagram | |
case @protocol | |
when 6 # tcp | |
thdr = TcpHeader.read( @payload ) | |
TcpDatagram.new( | |
:src_ip => @src_ip.to_s, | |
:dst_ip => @dst_ip.to_s, | |
:datagram => thdr | |
) | |
when 17 # udp | |
uhdr = UdpHeader.read( @payload ) | |
UdpDatagram.new( | |
:src_ip => @src_ip.to_s, | |
:dst_ip => @dst_ip.to_s, | |
:datagram => uhdr | |
) | |
end | |
end | |
def valid? | |
csum = header_checksum | |
csum = get_checksum( csum, @packet.header_checksum ) | |
return csum == 0 ? true : false | |
end | |
def header_checksum | |
csum = 0 | |
csum = get_checksum( csum, | |
@packet.version << 12 | @packet.header_length << 8 | @packet.tos ) | |
csum = get_checksum( csum, @packet.total_length ) | |
csum = get_checksum( csum, @packet.identification ) | |
csum = get_checksum( csum, | |
@packet.flags << 13 + @packet.fragment_offset ) | |
csum = get_checksum( csum, | |
@packet.ttl << 8 | @packet.protocol ) | |
csum = get_checksum( csum, @src_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @src_ip.to_i & 0xffff ) | |
csum = get_checksum( csum, @dst_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @dst_ip.to_i & 0xffff ) | |
# cannot handle ip options | |
return csum | |
end | |
end | |
############################################################ | |
## L4 Protocol Base | |
############################################################ | |
class TransportLayerProtocol | |
extend Forwardable | |
include HeaderUtil | |
attr_reader :datagram # Bindata::Record Struct | |
attr_reader :src_ip | |
attr_reader :dst_ip | |
# attr_reader :src_port | |
# attr_reader :dst_port | |
attr_reader :total_length | |
# attr_reader :payload | |
def_delegator :@datagram, :to_binary_s, :to_binary | |
def_delegators :@datagram, :src_port | |
def_delegators :@datagram, :dst_port | |
def_delegators :@datagram, :checksum | |
def_delegators :@datagram, :payload | |
# abstract | |
def protocol; 0; end | |
# abstract | |
def header_checksum; 0; end | |
def valid? | |
csum = header_checksum | |
csum = get_checksum( csum, @datagram.checksum ) | |
return csum == 0 ? true : false | |
end | |
end | |
############################################################ | |
## TCP | |
############################################################ | |
class TcpOption < BinData::Record | |
endian :big | |
end | |
# class EndOfOptionList < TcpOption | |
# # tlv_type only | |
# end | |
# class NoOperation < TcpOption | |
# # tlv_type only | |
# end | |
class MaximumSegmentSize < TcpOption | |
# type: 2 | |
bit8 :tlv_length, :value => 4 | |
bit16 :segment_size | |
end | |
class WindowScaleOption < TcpOption | |
# type: 3 | |
bit8 :tlv_length, :value => 3 | |
bit8 :shift_count | |
end | |
class SackPermitted < TcpOption | |
# type: 4 | |
bit8 :tlv_length, :value => 2 | |
# tlv_type and length only | |
end | |
class Sack < TcpOption | |
# type: 5 | |
bit8 :tlv_length, :value => lambda { | |
sequence_number_list.length * 4 + 2 | |
} | |
array :sequence_number_list, | |
:type => :bit32, | |
:initial_length => lambda { ( tlv_length - 2 ) / 4 } | |
end | |
class TimeStampOption < TcpOption | |
# type: 8 | |
bit8 :tlv_length, :value => 10 | |
bit32 :ts_val # timestamp vlaue | |
bit32 :ts_ecr # timestamp reply | |
end | |
class PartialOrderConnectionPermitted < TcpOption | |
# time: 9 | |
bit8 :tlv_length, :value => 2 | |
end | |
class PartialOrderServiceProfile < TcpOption | |
# type: 10 | |
bit8 :tlv_length, :value => 3 | |
bit1 :start_flag | |
bit1 :end_flag | |
bit6 :filler | |
end | |
class Cc < TcpOption | |
# type: 11 | |
bit8 :tlv_length, :value => 6 | |
bit32 :connection_count | |
end | |
class CcNew < TcpOption | |
# type: 12 | |
bit8 :tlv_length, :value => 6 | |
bit32 :connection_count | |
end | |
class CcEcho < TcpOption | |
# type: 13 | |
bit8 :tlv_length, :value => 6 | |
bit32 :connection_count | |
end | |
class TcpAlternateChecksumRequest < TcpOption | |
# type: 14 | |
bit8 :tlv_length, :value => 3 | |
bit8 :checksum | |
end | |
class TcpAlternateChecksumData < TcpOption | |
# type: 15 | |
bit8 :tlv_length, :value => lambda { | |
data.bytesize + 2 | |
} | |
string :data | |
end | |
class TcpOptionalTlv < BinData::Record | |
endian :big | |
bit8 :tlv_type | |
choice :tlv_body, | |
:onlyif => lambda { not type_only? }, | |
:selection => :chooser do | |
# end_of_option_list 0 | |
# no_operation 1 | |
maximum_segment_size 2 | |
window_scale_option 3 | |
sack_permitted 4 | |
sack 5 | |
time_stamp_option 8 | |
partial_order_connection_permitted 9 | |
partial_order_service_profile 10 | |
cc 11 | |
cc_new 12 | |
cc_echo 13 | |
tcp_alternate_checksum_request 14 | |
tcp_alternate_checksum_data 15 | |
string "unknown" | |
end | |
def chooser | |
case tlv_type | |
when 2,3,4,5,8,9,10,11,12,13,14,15 | |
# $stderr.puts "## type = #{tlv_type}" | |
tlv_type | |
else | |
"unknown" | |
end | |
end | |
def end_of_option_list? | |
tlv_type == 0 | |
end | |
def type_only? | |
tlv_type == 0 or tlv_type == 1 | |
end | |
def bytesize | |
case tlv_type | |
when 0, 1 | |
1 | |
when 2,3,4,5,8,9,10,11,12,13,14,15 | |
tlv_body.tlv_length | |
else | |
0 | |
end | |
end | |
end | |
class TcpHeader < BinData::Record | |
endian :big | |
CF_FIN = 0b000001 | |
CF_SYN = 0b000010 | |
CF_RST = 0b000100 | |
CF_PSH = 0b001000 | |
CF_ACK = 0b010000 | |
CF_URG = 0b100000 | |
uint16 :src_port | |
uint16 :dst_port | |
uint32 :seq_number | |
uint32 :ack_number, :initial_value => 0 | |
bit4 :data_offset, :initial_value => lambda { | |
# $stderr.puts "# A optlen: #{ option_length_in_bytes }" | |
5 + option_length_in_bytes / 4 | |
} | |
bit6 :reserved, :value => 0 | |
bit6 :control_flag, :initial_value => CF_SYN | |
uint16 :window | |
uint16 :checksum | |
uint16 :urg_pointer, :initial_value => 0 | |
array :optional_tlv, | |
:type => :tcp_optional_tlv, | |
:onlyif => :has_options?, | |
:read_until => lambda { | |
@readbytes ||= header_length_in_bytes - 20 | |
@readbytes = @readbytes - element.bytesize | |
# $stderr.puts "# B optlen: #{ element.tlv_type} => #{ option_length_in_bytes }, #{ @readbytes }" | |
element.end_of_option_list? or @readbytes <= 0 | |
} | |
skip :length => lambda { @readbytes or 0 } | |
rest :payload | |
def has_options? | |
data_offset > 5 | |
end | |
def option_length_in_bytes | |
bytes = 0 | |
if optional_tlv | |
optional_tlv.each do | each | | |
bytes += each.bytesize | |
end | |
end | |
return bytes | |
end | |
def header_length_in_bytes | |
# data_offset = tcp header length, 4 octets count | |
data_offset * 4 | |
end | |
def fin? ; ( control_flag & CF_FIN ) > 0 ; end | |
def syn? ; ( control_flag & CF_SYN ) > 0 ; end | |
def rst? ; ( control_flag & CF_RST ) > 0 ; end | |
def psh? ; ( control_flag & CF_PSH ) > 0 ; end | |
def ack? ; ( control_flag & CF_ACK ) > 0 ; end | |
def urg? ; ( control_flag & CF_URG ) > 0 ; end | |
def fin_ack? ; ( fin? and ack? ) ; end | |
def syn_ack? ; ( syn? and ack? ) ; end | |
def psh_ack? ; ( psh? and ack? ) ; end | |
end | |
class TcpDatagram < TransportLayerProtocol | |
def_delegators :@datagram, :seq_number | |
def_delegators :@datagram, :ack_number | |
def_delegators :@datagram, :data_offset | |
def_delegators :@datagram, :control_flag | |
def_delegators :@datagram, :window | |
def_delegators :@datagram, :urg_pointer | |
def_delegators :@datagram, :optional_tlv | |
def_delegators :@datagram, :fin?, :syn?, :rst?, :psh?, :ack?, :urg? | |
def_delegators :@datagram, :fin_ack?, :syn_ack?, :psh_ack? | |
def protocol; 6; end | |
def initialize opts | |
@src_ip = IPv4Address.new( opts[ :src_ip ] ) | |
@dst_ip = IPv4Address.new( opts[ :dst_ip ] ) | |
if opts[ :datagram ] | |
@datagram = opts[ :datagram ] | |
@src_port = @datagram.src_port | |
@dst_port = @datagram.dst_port | |
@payload = @datagram.payload | |
@seq_number = @datagram.seq_number | |
@ack_number = @datagram.ack_number | |
@control_flag = @datagram.control_flag | |
@window = @datagram.window | |
@urg_pointer = @datagram.urg_pointer | |
# array of TcpOptionalTlv | |
@options = @datagram.optional_tlv | |
@total_length = @datagram.to_binary_s.bytesize | |
@header_length = @datagram.header_length_in_bytes | |
# $stderr.puts "#[B] hdrlen=#{@header_length}, tlen=#{@total_length}" | |
# TBD | |
# ハンパなheaderわたされることを想定して | |
# 各パラメタの再計算をするか? | |
else | |
@src_port = opts[ :src_port ] | |
@dst_port = opts[ :dst_port ] | |
@payload = opts[ :payload ] | |
@seq_number = opts[ :seq_number ] | |
@ack_number = opts[ :ack_number ] | |
@control_flag = opts[ :control_flag ] || Pio::TcpHeader::CF_SYN | |
@window = opts[ :window ] | |
@urg_pointer = opts[ :urg_pointer ] || 0 | |
# array of TcpOptionalTlv | |
@options = options_by( opts[ :optional_tlv ] ) | |
# TBD | |
# need total_length and header_length (data_offset) | |
# to calculate checksum! | |
# Optionsの処理を先にどうにかしたほうがいいか... | |
# header = fixed fields (20 bytes) + option fields length | |
@header_length = 20 + option_length_in_bytes | |
@total_length = @header_length + @payload.bytesize | |
# $stderr.puts "#[A] hdrlen=#{@header_length}, tlen=#{@total_length}" | |
@datagram = TcpHeader.new( | |
:src_port => @src_port, | |
:dst_port => @dst_port, | |
:seq_number => @seq_number, | |
:ack_number => @ack_number, | |
:control_flag => @control_flag, | |
:window => @window, | |
:urg_pointer => @urg_pointer, | |
:checksum => header_checksum, | |
:optional_tlv => @options, | |
:payload => @payload | |
) | |
end | |
end | |
def header_checksum | |
csum = 0 | |
# pseudo header | |
csum = get_checksum( csum, @src_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @src_ip.to_i & 0xffff ) | |
csum = get_checksum( csum, @dst_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @dst_ip.to_i & 0xffff ) | |
csum = get_checksum( csum, protocol & 0x00ff ) | |
csum = get_checksum( csum, @total_length ) | |
# tcp header (without checksum) | |
csum = get_checksum( csum, @src_port ) | |
csum = get_checksum( csum, @dst_port ) | |
csum = get_checksum( csum, @seq_number >> 16 ) | |
csum = get_checksum( csum, @seq_number & 0xffff ) | |
csum = get_checksum( csum, @ack_number >> 16 ) | |
csum = get_checksum( csum, @ack_number & 0xffff ) | |
# $stderr.puts "hdrlen=#{@header_length}, flag=#{@control_flag}, value=#{sprintf("%04x", (( @header_length/4 << 12 ) & 0xf000 | @control_flag))}" | |
csum = get_checksum( csum, | |
( @header_length/4 << 12 ) & 0xf000 | @control_flag | |
) | |
csum = get_checksum( csum, @window ) | |
csum = get_checksum( csum, @urg_pointer ) | |
# tcp options | |
csum = get_str_checksum( csum, @options.map{ |e| e.to_binary_s }.join ) | |
# payload | |
csum = get_str_checksum( csum, @payload.to_s ) | |
return csum | |
end | |
def options_by opt_words | |
return [] unless opt_words | |
# opt_words format: | |
# [ | |
# :tlv_type_symbol, | |
# { :tlv_type_symbol => { | |
# :arg_symbol => value, | |
# :arg_symbol => value, | |
# ... | |
# }}, | |
# ... | |
# ] | |
opts = [] | |
opt_words.each do | each | | |
case each | |
when Symbol | |
if type = option_type_table[ each ] | |
opts.push Pio::TcpOptionalTlv.new( | |
:tlv_type => type | |
) | |
else | |
$stderr.puts "warning: unknown option: #{each}" | |
end | |
when Hash | |
( type_sym, arg ) = each.to_a.shift | |
if type = option_type_table[ type_sym ] | |
opts.push Pio::TcpOptionalTlv.new( | |
:tlv_type => type, | |
:tlv_body => arg | |
) | |
else | |
$stderr.puts "warning: unknown option: #{type_sym}" | |
end | |
end | |
end | |
return opts | |
end | |
def option_type_table | |
{ | |
# short | |
:eol => 0, | |
:noop => 1, | |
:mss => 2, | |
:wsopt => 3, | |
:sackp => 4, | |
:tsopt => 8, | |
:pocp => 9, | |
:posp => 10, | |
:tcpacr => 14, | |
:tcpacd => 15, | |
# long | |
:end_of_option_list => 0, | |
:no_operation => 1, | |
:maximum_segment_size => 2, | |
:window_scale_option => 3, | |
:sack_permitted => 4, | |
:sack => 5, | |
:time_stamp_option => 8, | |
:partial_order_connection_permitted => 9, | |
:partial_order_service_profile => 10, | |
:cc => 11, | |
:cc_new => 12, | |
:cc_echo => 13, | |
:tcp_alternate_checksum_request => 14, | |
:tcp_alternate_checksum_data => 15 | |
} | |
end | |
def option_length_in_bytes | |
bytes = 0 | |
if @options | |
@options.each do | each | | |
bytes = bytes + each.bytesize | |
end | |
end | |
return bytes | |
end | |
end | |
class TcpServer | |
attr_reader :curr_state | |
def initialize | |
@curr_state = :closed | |
srand Random.new_seed | |
@seq = Random.rand(1..0xffffffff) | |
@handler = {} | |
end | |
def send_handler &block | |
@handler[ :send ] = block | |
end | |
def request_handler &block | |
@handler[ :request ] = block | |
end | |
def response_datagram req_dgm, ack, cflag, payload | |
TcpDatagram.new( | |
:src_ip => req_dgm.dst_ip.to_s, | |
:dst_ip => req_dgm.src_ip.to_s, | |
:src_port => req_dgm.dst_port, | |
:dst_port => req_dgm.src_port, | |
:seq_number => @seq, | |
:ack_number => ack, | |
:window => 2048, # ok? | |
:control_flag => cflag, | |
:optional_tlv => [], | |
:payload => payload | |
) | |
end | |
def calc_ack rcv_dgm, count | |
@seq = rcv_dgm.ack_number unless rcv_dgm.ack_number == 0 | |
( rcv_dgm.seq_number + count ) & 0xffffffff | |
end | |
def receive rcv_dgm | |
case @curr_state | |
when :closed, :listen | |
if rcv_dgm.syn? | |
ack = calc_ack( rcv_dgm, 1 ) | |
cflag = TcpHeader::CF_SYN | TcpHeader::CF_ACK | |
reply_dgm = response_datagram( rcv_dgm, ack, cflag, '' ) | |
## send syn_ack | |
@handler[ :send ].call( reply_dgm ) | |
@curr_state = :syn_received | |
end | |
when :syn_received | |
if rcv_dgm.ack? | |
# finish handshake. wait request | |
@curr_state = :established | |
end | |
when :established | |
if rcv_dgm.fin? | |
ack = calc_ack( rcv_dgm, 1 ) | |
cflag = TcpHeader::CF_ACK | |
reply_dgm = response_datagram( rcv_dgm, ack, cflag, '' ) | |
## send ack | |
@handler[ :send ].call( reply_dgm ) | |
@curr_state = :close_wait | |
cflag = TcpHeader::CF_FIN | |
reply_dgm = response_datagram( rcv_dgm, ack, cflag, '' ) | |
## send fin | |
@handler[ :send ].call( reply_dgm ) | |
@curr_state = :last_ack | |
elsif | |
## request processing | |
ack = calc_ack( rcv_dgm, rcv_dgm.payload.bytesize ) | |
reply_dgm = @handler[ :request ].call( rcv_dgm, @seq, ack ) | |
## send response | |
@handler[ :send ].call( reply_dgm ) | |
end | |
when :last_ack | |
if rcv_dgm.ack? | |
@curr_state = :closed | |
puts "info, session closed." | |
end | |
else | |
$stderr.puts "warning, receive out-of-state packet" | |
end | |
end | |
end | |
############################################################ | |
## UDP | |
############################################################ | |
class UdpHeader < BinData::Record | |
endian :big | |
UDP_HDR_LEN = 8 | |
uint16 :src_port | |
uint16 :dst_port | |
uint16 :total_length, :value => lambda { | |
payload.bytesize + UDP_HDR_LEN | |
} | |
uint16 :checksum | |
string :payload, :read_length => lambda { | |
total_length - UDP_HDR_LEN | |
} | |
end | |
class UdpDatagram < TransportLayerProtocol | |
def protocol; 17; end | |
def initialize opts | |
@src_ip = IPv4Address.new( opts[ :src_ip ] ) | |
@dst_ip = IPv4Address.new( opts[ :dst_ip ] ) | |
if opts[ :datagram ] | |
@datagram = opts[ :datagram ] | |
@src_port = @datagram.src_port | |
@dst_port = @datagram.dst_port | |
@total_length = @datagram.total_length | |
@payload = @datagram.payload | |
else | |
@src_port = opts[ :src_port ] | |
@dst_port = opts[ :dst_port ] | |
@payload = opts[ :payload ] | |
# udp datagram length | |
# = payload length (octets) + udp header length (8 octets) | |
@total_length = @payload.to_s.bytesize + 8 | |
@datagram = UdpHeader.new( | |
:src_port => @src_port, | |
:dst_port => @dst_port, | |
:total_length => @total_length, | |
:checksum => header_checksum, | |
:payload => @payload | |
) | |
end | |
end | |
def header_checksum | |
csum = 0 | |
# pseudo header | |
csum = get_checksum( csum, @src_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @src_ip.to_i & 0xffff ) | |
csum = get_checksum( csum, @dst_ip.to_i >> 16 ) | |
csum = get_checksum( csum, @dst_ip.to_i & 0xffff ) | |
csum = get_checksum( csum, protocol & 0x00ff ) | |
csum = get_checksum( csum, @total_length ) | |
# udp header (without checksum) | |
csum = get_checksum( csum, @src_port ) | |
csum = get_checksum( csum, @dst_port ) | |
csum = get_checksum( csum, @total_length ) | |
# udp payload | |
csum = get_str_checksum( csum, @payload.to_s ) | |
return csum | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# | |
# A router implementation in Trema | |
# | |
# Copyright (C) 2013 NEC Corporation | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License, version 2, as | |
# published by the Free Software Foundation. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License along | |
# with this program; if not, write to the Free Software Foundation, Inc., | |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
# | |
require "arp-table" | |
require "interface" | |
require "router-utils" | |
require "routing-table" | |
require "pio" | |
require_relative "pio-l4hdr" | |
class SimpleRouter < Controller | |
include RouterUtils | |
# add_timer_event :send_echo_request_periodically, 5, :periodic | |
def start | |
info "[start]" | |
# load "simple_router.conf" | |
load "echoserver.conf" | |
@interfaces = Interfaces.new( $interface ) | |
@arp_table = ARPTable.new | |
@routing_table = RoutingTable.new( $route ) | |
@tcp_sessions = {} | |
end | |
def switch_ready dpid | |
info "[switch_ready] #{ dpid.to_hex }" | |
send_message dpid, FeaturesRequest.new | |
# echo server settings | |
@srv_dpid = dpid | |
@srv_edst = Pio::IPv4Address.new( "192.168.1.16" ) # echo dst | |
srv_addr = Pio::IPv4Address.new( "192.168.1.83" ) | |
@srv_int = @interfaces.find_by_ipaddr( srv_addr ) | |
unless @srv_int | |
error "server #{srv_addr.to_s} : interface not found" | |
exit | |
end | |
@ready = true | |
end | |
def features_reply(dpid, message) | |
info "[features_reply] Datapath ID: #{ dpid.to_hex }" | |
message.ports.each do |each| | |
puts "Port no: #{ each.number }" | |
puts " Hardware address: #{ each.hw_addr.to_s }" | |
puts " Port name: #{ each.name }" | |
end | |
end | |
def send_udp_echo_request_periodically | |
if @ready | |
puts "** current arp table **" | |
@arp_table.dump | |
arp_entry = @arp_table.lookup( @srv_edst ) | |
if arp_entry | |
send_udp_echo_request( | |
@srv_dpid, | |
@srv_int.hwaddr, arp_entry.hwaddr, | |
@srv_int.ipaddr, @srv_edst, | |
32697, | |
"1234567890abcdefGHIJK", | |
@srv_int.port | |
) | |
else | |
arp_request = Pio::Arp::Request.new( | |
:source_mac => @srv_int.hwaddr.to_s, | |
:target_protocol_address => @srv_edst.to_s, | |
:sender_protocol_address => @srv_int.ipaddr.to_s | |
) | |
packet_out @srv_dpid, arp_request.to_binary, SendOutPort.new( @srv_int.port ) | |
end | |
else | |
warn "DPID:#{@dpid.to_hex} is not ready" | |
end | |
end | |
def packet_in( dpid, message ) | |
return if not to_me?( message ) | |
if message.arp_request? | |
handle_arp_request dpid, message | |
elsif message.arp_reply? | |
handle_arp_reply message | |
elsif message.ipv4? | |
handle_ipv4 dpid, message | |
else | |
# noop. | |
end | |
end | |
private | |
def to_me?( message ) | |
return true if message.macda.broadcast? | |
interface = @interfaces.find_by_port( message.in_port ) | |
if interface and interface.has?( message.macda ) | |
return true | |
end | |
end | |
def handle_arp_request( dpid, message ) | |
info "[handle_arp_request] #{dpid.to_hex}" | |
port = message.in_port | |
daddr = message.arp_tpa | |
interface = @interfaces.find_by_port_and_ipaddr( port, daddr ) | |
if interface | |
arp_reply = Pio::Arp::Reply.new( | |
:source_mac => interface.hwaddr.to_s, | |
:destination_mac => message.arp_sha.to_s, | |
:sender_protocol_address => message.arp_tpa.to_s, | |
:target_protocol_address => message.arp_spa.to_s | |
) | |
packet_out dpid, arp_reply.to_binary, SendOutPort.new( interface.port ) | |
end | |
end | |
def handle_arp_reply( message ) | |
info "[handle_arp_reply]" | |
@arp_table.update message.in_port, message.arp_spa, message.arp_sha | |
end | |
def send_udp_echo_request dpid, src_mac, dst_mac, src_ip, dst_ip, src_port, payload, outport | |
info "[send_udp_echo_request]" | |
frame = make_udp_echo_frame( | |
src_mac, dst_mac, | |
src_ip, dst_ip, | |
src_port, 7, | |
payload | |
) | |
packet_out( dpid, frame, SendOutPort.new( outport ) ) | |
end | |
def send_udp_echo_reply dpid, message, data | |
info "[send_udp_echo_reply]" | |
frame = make_udp_echo_frame( | |
message.macda, message.macsa, | |
message.ipv4_daddr, message.ipv4_saddr, | |
message.udp_dst_port, message.udp_src_port, | |
data | |
) | |
packet_out( dpid, frame, SendOutPort.new( message.in_port ) ) | |
end | |
def make_udp_echo_frame( src_mac, dst_mac, src_ip, dst_ip, src_port, dst_port, payload ) | |
puts "[make_udp_echo_frame]" | |
puts "src mac : #{ src_mac.to_s }" | |
puts "dst mac : #{ dst_mac.to_s }" | |
puts "src addr : #{ src_ip.to_s }" | |
puts "dst addr : #{ dst_ip.to_s }" | |
puts "src port : #{ src_port }" | |
puts "dst port : #{ dst_port }" | |
puts "payload : #{ payload }" | |
udgm = Pio::UdpDatagram.new( | |
:src_ip => src_ip.to_s, | |
:dst_ip => dst_ip.to_s, | |
:src_port => src_port, | |
:dst_port => dst_port, | |
:payload => payload | |
) | |
ipfrm = make_ip_frame( src_mac, dst_mac, udgm ) | |
return ipfrm.to_binary | |
end | |
def send_tcp_reply dpid, message, tdgm | |
info "[send_tcp_reply]" | |
frame = make_ip_frame( | |
message.macda, message.macsa, tdgm | |
) | |
packet_out( dpid, | |
frame.to_binary, | |
SendOutPort.new( message.in_port ) | |
) | |
end | |
def make_tcp_echo_datagram rcv_dgm, seq, ack | |
info "[make_tcp_echo_datagram]" | |
cflag = Pio::TcpHeader::CF_PSH | Pio::TcpHeader::CF_ACK | |
Pio::TcpDatagram.new( | |
:src_ip => rcv_dgm.dst_ip.to_s, | |
:dst_ip => rcv_dgm.src_ip.to_s, | |
:src_port => rcv_dgm.dst_port, | |
:dst_port => rcv_dgm.src_port, | |
:seq_number => seq, | |
:ack_number => ack, | |
:control_flag => cflag, | |
:window => 2048, # ok? | |
:payload => rcv_dgm.payload # echo back | |
) | |
end | |
def make_ip_frame( src_mac, dst_mac, dgm ) | |
info "[make_ip_frame]" | |
ippct = Pio::IPv4Packet.new( | |
:payload => dgm | |
) | |
ipfrm = Pio::IPv4Frame.new( | |
:src_mac => src_mac.to_s, | |
:dst_mac => dst_mac.to_s, | |
:packet => ippct | |
) | |
return ipfrm | |
end | |
def handle_ipv4( dpid, message ) | |
info "[handle_ipv4], #{dpid.to_hex}" | |
if should_forward?( message ) | |
forward dpid, message | |
elsif message.icmpv4_echo_request? | |
handle_icmpv4_echo_request dpid, message | |
else | |
begin | |
ipfrm = Pio::IPv4Frame.read message.data | |
rescue | |
warn "[handle_ipv4] can not handle packet: #{message.ipv4_saddr.to_s}->#{message.ipv4_daddr.to_s}" | |
puts "data -> #{message.data.class}, #{message.data.unpack('H*')}" | |
return | |
end | |
puts " src mac : #{ ipfrm.src_mac }" | |
puts " dst mac : #{ ipfrm.dst_mac }" | |
puts " ether type : #{ sprintf("%04x", ipfrm.ether_type) }" | |
ippct = ipfrm.packet | |
puts " version : #{ ippct.version }" | |
puts " header length: #{ ippct.header_length * 4 } (octets)" | |
puts " tos : #{ ippct.tos }" | |
puts " total length : #{ ippct.total_length } (octets)" | |
puts " identificat : #{ ippct.identification }" | |
puts " flags : #{ ippct.flags }" | |
puts " frag offset : #{ ippct.fragment_offset }" | |
puts " ttl : #{ ippct.ttl }" | |
puts " protocol : #{ ippct.protocol }" | |
puts " checksum : #{ sprintf("%04x", ippct.header_checksum) }" | |
puts " src addr : #{ ippct.src_ip }" | |
puts " dst addr : #{ ippct.dst_ip }" | |
puts " options : #{ ippct.options }" | |
puts " valid? : #{ ippct.valid? }" | |
if ippct.protocol == 17 # udp | |
dgm = ippct.datagram | |
puts " src port : #{ dgm.src_port }" | |
puts " dst port : #{ dgm.dst_port }" | |
puts " total length : #{ dgm.total_length } (octets)" | |
puts " checksum : #{ sprintf("%04x", dgm.checksum) }" | |
puts " payload : #{ dgm.payload }" | |
puts " valid? : #{ dgm.valid? }" | |
if dgm.dst_port == 7 | |
# udp echo request received, send reply. | |
send_udp_echo_reply dpid, message, dgm.payload | |
end | |
elsif ippct.protocol == 6 # tcp | |
dgm = ippct.datagram | |
puts " src port : #{ dgm.src_port }" | |
puts " dst port : #{ dgm.dst_port }" | |
puts " seq number : #{ sprintf("%08x", dgm.seq_number) }" | |
puts " ack number : #{ sprintf("%08x", dgm.ack_number) }" | |
puts " data offset : #{ dgm.data_offset }" | |
puts " ctrl flag : #{ sprintf("%06b", dgm.control_flag) }" | |
puts " window size : #{ sprintf("%04x", dgm.window) }" | |
puts " checksum : #{ sprintf("%04x", dgm.checksum) }" | |
puts " urg pointer : #{ sprintf("%04x", dgm.urg_pointer) }" | |
puts " options : #{ (dgm.optional_tlv.map{ |e| e.tlv_type }).join(", ") }" | |
puts " payload : #{ dgm.payload }" | |
puts " valid? : #{ dgm.valid? }" | |
if dgm.dst_port == 7 | |
s_ip = ippct.src_ip.to_s | |
s_port = dgm.src_port | |
unless @tcp_sessions[ s_ip ] | |
@tcp_sessions[ s_ip ] = {} | |
end | |
unless @tcp_sessions[ s_ip ][ s_port ] | |
srv = Pio::TcpServer.new | |
srv.send_handler do | reply_dgm | | |
# reply datagram | |
send_tcp_reply dpid, message, reply_dgm | |
end | |
srv.request_handler do | rcv_dgm, seq, ack | | |
# generate reply datagram | |
make_tcp_echo_datagram rcv_dgm, seq, ack | |
end | |
@tcp_sessions[ s_ip ][ s_port ] = srv | |
end | |
srv = @tcp_sessions[ s_ip ][ s_port ] | |
srv.receive( dgm ) | |
end | |
end | |
end | |
end | |
def should_forward?( message ) | |
not @interfaces.find_by_ipaddr( message.ipv4_daddr ) | |
end | |
def handle_icmpv4_echo_request( dpid, message ) | |
interface = @interfaces.find_by_port( message.in_port ) | |
saddr = message.ipv4_saddr.value | |
arp_entry = @arp_table.lookup( saddr ) | |
if arp_entry | |
icmpv4_reply = create_icmpv4_reply( arp_entry, interface, message ) | |
packet_out dpid, icmpv4_reply, SendOutPort.new( interface.port ) | |
else | |
handle_unresolved_packet dpid, message, interface, saddr | |
end | |
end | |
def forward( dpid, message ) | |
next_hop = resolve_next_hop( message.ipv4_daddr ) | |
interface = @interfaces.find_by_prefix( next_hop ) | |
if not interface or interface.port == message.in_port | |
return | |
end | |
arp_entry = @arp_table.lookup( next_hop ) | |
if arp_entry | |
macsa = interface.hwaddr | |
macda = arp_entry.hwaddr | |
action = create_action_from( macsa, macda, interface.port ) | |
flow_mod dpid, message, action | |
packet_out dpid, message.data, action | |
else | |
handle_unresolved_packet dpid, message, interface, next_hop | |
end | |
end | |
def resolve_next_hop( daddr ) | |
interface = @interfaces.find_by_prefix( daddr.value ) | |
if interface | |
daddr.value | |
else | |
@routing_table.lookup( daddr.value ) | |
end | |
end | |
def flow_mod( dpid, message, action ) | |
send_flow_mod_add( | |
dpid, | |
:match => ExactMatch.from( message ), | |
:actions => action | |
) | |
end | |
def packet_out( dpid, packet, action ) | |
send_packet_out( | |
dpid, | |
:data => packet, | |
:actions => action | |
) | |
end | |
def handle_unresolved_packet( dpid, message, interface, ipaddr ) | |
arp_request = Pio::Arp::Request.new( | |
:source_mac => interface.hwaddr.to_s, | |
:target_protocol_address => ipaddr.to_s, | |
:sender_protocol_address => interface.ipaddr.to_s | |
) | |
packet_out dpid, arp_request.to_binary, SendOutPort.new( interface.port ) | |
end | |
def create_action_from( macsa, macda, port ) | |
[ | |
SetEthSrcAddr.new( macsa ), | |
SetEthDstAddr.new( macda ), | |
SendOutPort.new( port ) | |
] | |
end | |
end | |
### Local variables: | |
### mode: Ruby | |
### coding: utf-8 | |
### indent-tabs-mode: nil | |
### End: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment