Preserve last version without PacketFu

This commit is contained in:
pjht 2021-04-25 08:13:16 -05:00
parent 3ad32310bb
commit 99c5e76d5d
14 changed files with 579 additions and 467 deletions

57
arp_client.rb Normal file
View File

@ -0,0 +1,57 @@
require_relative 'ethernet_device'
require_relative 'ethernet_frame'
require_relative 'arp_packet'
module ArpClient
@@eth_dev = nil
@@mac = nil
@@ip = nil
@@arp_table = { '255.255.255.255': 'FF:FF:FF:FF:FF:FF' }
@@arp_queue = {}
def self.initialize(eth_device, mac, ip)
@@eth_dev = eth_device
@@mac = mac
@@ip = ip
resp_packet = ARPPacket.new(nil, ARPPacket::REQUEST, mac, ip, '00:00:00:00:00:00', ip)
eth_device.send_frame(EthernetFrame::ARP, 'FF:FF:FF:FF:FF:FF', resp_packet.bytes)
resp_packet = ARPPacket.new(nil, ARPPacket::REPLY, mac, ip, mac, ip)
eth_device.send_frame(EthernetFrame::ARP, 'FF:FF:FF:FF:FF:FF', resp_packet.bytes)
eth_device.register_callback(EthernetFrame::ARP) do |frame, device|
packet = ARPPacket.new(frame.data)
@@arp_table[packet.src_ip]=packet.src_mac
if packet.is_request?
puts "Received ARP request from #{packet.src_ip} (#{packet.src_mac}) for #{packet.dest_ip}"
if packet.dest_ip == @@ip
resp_packet = ARPPacket.new(nil, ARPPacket::REPLY, mac, ip, 'FF:FF:FF:FF:FF:FF', packet.src_ip)
puts 'Sending ARP response'
device.send_frame(EthernetFrame::ARP, packet.src_mac, resp_packet.bytes)
end
else
puts "Received ARP response for #{packet.src_ip} (#{packet.src_mac})"
next unless @@arp_queue[packet.src_ip]
@@arp_queue[packet.src_ip].each do |packet|
@@eth_dev.send_frame(EthernetFrame::IPV4, frame.src_mac, packet.bytes)
end
@@arp_queue[packet.src_ip] = []
end
end
end
def self.send_packet(packet)
dest_ip = packet.dest_ip
if @@arp_table[dest_ip]
@@eth_dev.send_frame(EthernetFrame::IPV4, @@arp_table[dest_ip], packet.bytes)
else
@@arp_queue[dest_ip] = [] unless @@arp_queue[dest_ip]
@@arp_queue[dest_ip].push packet
puts "Sending ARP request for #{dest_ip}"
packet = ARPPacket.new(nil, ARPPacket::REQUEST, @@mac, @@ip, '00:00:00:00:00:00', dest_ip)
@@eth_dev.send_frame(EthernetFrame::ARP, 'FF:FF:FF:FF:FF:FF', packet.bytes)
end
end
end

View File

@ -1,52 +1,48 @@
require_relative "net_utils.rb"
# frozen_string_literal: true
require_relative 'net_utils'
class ARPPacket
IP4=0x800
REQUEST=1
REPLY=2
IP4 = 0x800
REQUEST = 1
REPLY = 2
@@empty_reply=[0,1,8,0,6,4,0]
@@empty_reply = [0, 1, 8, 0, 6, 4, 0]
attr_reader :bytes
def initialize(bytes,op=nil,src_mac=nil,src_ip=nil,dest_ip=nil)
def initialize(bytes, op = nil, src_mac = nil, src_ip = nil, dest_mac = nil, dest_ip = nil)
if !bytes
@bytes=@@empty_reply
@bytes+=[op&0xFF]
@bytes+=NetUtils.decode_mac(src_mac)
@bytes+=NetUtils.decode_ip4(src_ip)
@bytes+=[0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
@bytes+=NetUtils.decode_ip4(dest_ip)
@bytes = @@empty_reply
@bytes += [op & 0xFF]
@bytes += NetUtils.decode_mac(src_mac)
@bytes += NetUtils.decode_ip4(src_ip)
@bytes += NetUtils.decode_mac(dest_mac)
@bytes += NetUtils.decode_ip4(dest_ip)
else
@bytes=bytes
@bytes = bytes
end
end
def dest_ip
if proto_type==IP4
return NetUtils.format_ip4(@bytes[24, 4])
end
NetUtils.format_ip4(@bytes[24, 4]) if proto_type == IP4
end
def src_mac
return NetUtils.format_mac(@bytes[8, 6])
NetUtils.format_mac(@bytes[8, 6])
end
def src_ip
if proto_type==IP4
return NetUtils.format_ip4(@bytes[14, 4])
end
NetUtils.format_ip4(@bytes[14, 4]) if proto_type == IP4
end
def proto_type
type=@bytes[3]
type+=@bytes[2]<<8
return type
type = @bytes[3]
type += @bytes[2] << 8
type
end
def is_request?
return @bytes[7]==1
@bytes[7] == 1
end
end

View File

@ -1,88 +1,87 @@
require_relative "net_utils.rb"
# frozen_string_literal: true
require_relative 'net_utils'
class DHCPOption
attr_reader :bytes
SubnetMask=1
Router=3
DNServer=6
BroadcastAddr=28
RequestedIP=50
LeaseTime=51
MessageType=53
ServerIdentifier=54
RequestList=55
RenewalTime=58
RebindingTime=59
End=255
SubnetMask = 1
Router = 3
DNServer = 6
BroadcastAddr = 28
RequestedIP = 50
LeaseTime = 51
MessageType = 53
ServerIdentifier = 54
RequestList = 55
RenewalTime = 58
RebindingTime = 59
End = 255
@@lengths={
SubnetMask=>4,Router=>4,BroadcastAddr=>4,
RequestedIP=>4,LeaseTime=>4,MessageType=>1,
ServerIdentifier=>4,RenewalTime=>4,RebindingTime=>4,
End=>0
@@lengths = {
SubnetMask => 4, Router => 4, BroadcastAddr => 4,
RequestedIP => 4, LeaseTime => 4, MessageType => 1,
ServerIdentifier => 4, RenewalTime => 4, RebindingTime => 4,
End => 0
}
def decode_type()
def decode_type
case @bytes[0]
when SubnetMask
return "Subnet Mask"
return 'Subnet Mask'
when Router
return "Router"
return 'Router'
when DNServer
return "DNS Servers"
return 'DNS Servers'
when BroadcastAddr
return "Broadcast Address"
return 'Broadcast Address'
when LeaseTime
return "Lease Time"
return 'Lease Time'
when MessageType
return "Message Type"
return 'Message Type'
when ServerIdentifier
return "Server Identifier"
return 'Server Identifier'
when RequestList
return "Request List"
return 'Request List'
when RenewalTime
return "Renewal Time"
return 'Renewal Time'
when RebindingTime
return "Rebinding Time"
return 'Rebinding Time'
when End
return "End"
return 'End'
end
return "unknown"
'unknown'
end
def type
return @bytes[0]
@bytes[0]
end
def initialize(type,*args)
args=args.flatten
@bytes=[type,@@lengths[type]]
if @@lengths[type]==nil
@bytes[1]=args.length
end
@bytes+=args
def initialize(type, *args)
args = args.flatten
@bytes = [type, @@lengths[type]]
@bytes[1] = args.length if @@lengths[type].nil?
@bytes += args
@bytes.flatten!
end
def data()
def data
case @bytes[0]
when SubnetMask,Router,BroadcastAddr,RequestedIP,ServerIdentifier
return NetUtils.format_ip4(@bytes[2,4])
when SubnetMask, Router, BroadcastAddr, RequestedIP, ServerIdentifier
NetUtils.format_ip4(@bytes[2, 4])
when MessageType
return @bytes[2]
when LeaseTime,RenewalTime,RebindingTime
return NetUtils.decode_int(@bytes[2,4])
@bytes[2]
when LeaseTime, RenewalTime, RebindingTime
NetUtils.decode_int(@bytes[2, 4])
when DNServer
num_ips=@bytes[1]/4
i=2
ips=[]
num_ips = @bytes[1] / 4
i = 2
ips = []
num_ips.times do
ips.push NetUtils.format_ip4(@bytes[i,4])
i+=4
ips.push NetUtils.format_ip4(@bytes[i, 4])
i += 4
end
return ips
ips
end
end
end

View File

@ -1,85 +1,79 @@
require_relative "net_utils.rb"
require_relative "dhcp_option.rb"
# frozen_string_literal: true
require_relative 'net_utils'
require_relative 'dhcp_option'
class DHCPPacket
DISCOVER=1
OFFER=2
REQUEST=3
DECLINE=4
ACK=5
NACK=6
RELEASE=7
INFORM=8
DISCOVER = 1
OFFER = 2
REQUEST = 3
DECLINE = 4
ACK = 5
NACK = 6
RELEASE = 7
INFORM = 8
@@requests=[DISCOVER,REQUEST,RELEASE]
@@requests = [DISCOVER, REQUEST, RELEASE]
attr_reader :bytes,:options
attr_reader :bytes, :options
def initialize(bytes,my_ip=nil,mac=nil,type=nil,options=[])
def initialize(bytes, my_ip = nil, mac = nil, type = nil, options = [])
if !bytes
if @@requests.include? type
op=1
else
op=2
op = if @@requests.include? type
1
else
2
end
@bytes = [op, 1, 6, 0, 0x4, 0x57, 0x70, 0x99, 0, 0, 0x80, 0]
@bytes += if my_ip
NetUtils.decode_ip4(my_ip)
else
[0, 0, 0, 0]
end
@bytes += [0] * 12
@bytes += NetUtils.decode_mac(mac)
@bytes += [0] * 202
@bytes += [0x63, 0x82, 0x53, 0x63] # DHCP magic cookie
@bytes += DHCPOption.new(DHCPOption::MessageType, type).bytes
options.each do |option|
@bytes += option.bytes
end
@bytes=[op,1,6,0,0x4,0x57,0x70,0x99,0,0,0x80,0]
if my_ip
@bytes+=NetUtils.decode_ip4(my_ip)
else
@bytes+=[0,0,0,0]
end
12.times do
@bytes.push 0
end
@bytes+=NetUtils.decode_mac(mac)
10.times do
@bytes.push 0
end
192.times do
@bytes.push 0
end
@bytes+=[0x63,0x82,0x53,0x63] # DHCP magic cookie
@bytes+=DHCPOption.new(DHCPOption::MessageType,type).bytes
for option in options
@bytes+=option.bytes
end
@bytes+=DHCPOption.new(DHCPOption::End,type).bytes
@bytes += DHCPOption.new(DHCPOption::End, type).bytes
else
@bytes=bytes.unpack("C*")
@bytes = bytes.unpack('C*')
end
option_bytes=@bytes[240..-1]
@options=[]
i=0
while i<option_bytes.length
break if option_bytes[i]==0xff
opt_bytes=[option_bytes[i],option_bytes[i+1]]
i+=2
option_bytes[i-1].ord.times do
option_bytes = @bytes[240..-1]
@options = []
i = 0
while i < option_bytes.length
break if option_bytes[i] == 0xff
opt_bytes = [option_bytes[i], option_bytes[i + 1]]
i += 2
option_bytes[i - 1].ord.times do
opt_bytes.push option_bytes[i]
i+=1
i += 1
end
@options.push DHCPOption.new(opt_bytes[0],opt_bytes[2..-1])
@options.push DHCPOption.new(opt_bytes[0], opt_bytes[2..-1])
end
end
def mac()
return NetUtils.format_mac(@bytes[28,6])
def mac
NetUtils.format_mac(@bytes[28, 6])
end
def yiaddr()
return NetUtils.format_ip4(@bytes[16,4])
def yiaddr
NetUtils.format_ip4(@bytes[16, 4])
end
def siaddr()
return NetUtils.format_ip4(@bytes[20,4])
def siaddr
NetUtils.format_ip4(@bytes[20, 4])
end
def type()
for option in @options
if option.type==DHCPOption::MessageType
return option.data
end
def type
@options.each do |option|
return option.data if option.type == DHCPOption::MessageType
end
end
end

View File

@ -1,14 +1,20 @@
require "socket"
require "io/wait"
require_relative "ethernet_frame.rb"
# frozen_string_literal: true
require 'socket'
require 'io/wait'
require_relative 'ethernet_frame'
require 'hexdump'
require 'packetfu'
class EthernetDevice
BUFFER_SIZE = 16777216
BUFFER_SIZE = 16_777_216
def initialize(iface,mac)
@mac=mac
@callbacks=[]
def initialize(iface, mac)
@mac = mac
@callbacks = []
@pcap_file = "ns-#{iface}.pcap"
PacketFu::PcapFile.new.write(@pcap_file)
# Make an ifreq structure
ifreq_size = 0x0028 # Size in bytes of the ifreq structure
@ -25,7 +31,7 @@ class EthernetDevice
# Pull the bytes containing the result out of the string
# (where the ifr_ifindex field would be)
index = ifreq[Socket::IFNAMSIZ, ifindex_size]
eth_p_all = 0x0300 # Receive every packet
eth_p_all = 0x0300 # Receive every packet
# Make a sockaddr_ll structure
sockaddr_ll_size = 0x0014 # Size in bytes of the sockaddr_ll structure
@ -37,45 +43,37 @@ class EthernetDevice
@socket.bind(sockaddr_ll)
Thread.new do
while true
loop do
if @socket.ready?
frame=EthernetFrame.new(@socket.recv(BUFFER_SIZE).bytes)
frame = EthernetFrame.new(@socket.recv(BUFFER_SIZE).bytes)
got_frame(frame)
end
end
end
end
def send_frame(type,dest_mac,data)
broadcast=dest_mac=="FF:FF:FF:FF:FF:FF"
# if broadcast
# puts "Broadcast #{NetUtils.decode_ethertype(type)} frame"
# else
# puts "#{NetUtils.decode_ethertype(type)} frame to #{dest_mac}"
# end
@socket.print EthernetFrame.new(nil,type,@mac,dest_mac,data).bytes.pack("C*")
def send_frame(type, dest_mac, data)
frame = EthernetFrame.new(nil, type, @mac, dest_mac, data)
pkt = PacketFu::Packet.parse(frame.bytes.pack('C*'))
PacketFu::PcapFile.new.array_to_file({ file: @pcap_file, array: [pkt], append: true })
frame.dump
@socket.print frame.bytes.pack('C*')
end
def register_callback(type,&callback)
@callbacks[type]=callback
def register_callback(type, &callback)
@callbacks[type] = callback
end
private
def got_frame(frame)
return if frame.dest_mac!=@mac and frame.dest_mac!="FF:FF:FF:FF:FF:FF"
broadcast=frame.dest_mac=="FF:FF:FF:FF:FF:FF"
# if broadcast
# if NetUtils.decode_ethertype(frame.ethertype)=="Unknown"
# puts "Unknown broadcast frame from #{frame.src_mac}"
# else
# puts "Broadcast #{NetUtils.decode_ethertype(frame.ethertype)} frame from #{frame.src_mac}"
# end
# else
# puts "#{NetUtils.decode_ethertype(frame.ethertype)} frame from #{frame.src_mac}"
# end
@callbacks[frame.ethertype].call(frame,self) if @callbacks[frame.ethertype]
end
return if (frame.dest_mac != @mac) && (frame.dest_mac != 'FF:FF:FF:FF:FF:FF')
pkt = PacketFu::Packet.parse(frame.bytes.pack('C*'))
PacketFu::PcapFile.new.array_to_file({ file: @pcap_file, array: [pkt], append: true })
frame.dump
@callbacks[frame.ethertype]&.call(frame, self)
end
end

View File

@ -1,20 +1,21 @@
require "crc"
require_relative "net_utils.rb"
# frozen_string_literal: true
require_relative 'net_utils'
class EthernetFrame
ARP=0x0806
IPV4=0x0800
IPV6=0x86DD
ARP = 0x0806
IPV4 = 0x0800
IPV6 = 0x86DD
attr_reader :bytes
def initialize(bytes,type=nil,src_mac=nil,dest_mac=nil,data=nil)
def initialize(bytes, type = nil, src_mac = nil, dest_mac = nil, data = nil)
if !bytes
@bytes=[]
@bytes = []
@bytes += NetUtils.decode_mac(dest_mac)
@bytes += NetUtils.decode_mac(src_mac)
@bytes[12] = (type&0xFF00)>>8
@bytes[13] = type&0xFF
@bytes[12] = (type & 0xFF00) >> 8
@bytes[13] = type & 0xFF
@bytes += data
else
@bytes = bytes
@ -22,20 +23,48 @@ class EthernetFrame
end
def dest_mac
return NetUtils.format_mac(@bytes[0, 6])
NetUtils.format_mac(@bytes[0, 6])
end
def src_mac
return NetUtils.format_mac(@bytes[6, 6])
NetUtils.format_mac(@bytes[6, 6])
end
def ethertype
type=@bytes[13]
type+=@bytes[12]<<8
return type
type = @bytes[13]
type += @bytes[12] << 8
type
end
def data
return @bytes[14...-4] # Drop the first 14 bytes (MAC Header) and last 4 bytes (CRC Checksum)
@bytes[14..-1] # Drop the first 14 bytes (MAC Header) and last 4 bytes (CRC Checksum)
end
def dump
broadcast = dest_mac == 'FF:FF:FF:FF:FF:FF'
if broadcast
if NetUtils.decode_ethertype(ethertype) == 'Unknown'
if src_mac == MAC
print 'Sending unknown broadcast frame'
else
print 'Received unknown broadcast frame'
end
else
if src_mac == MAC
print "Sending broadcast #{NetUtils.decode_ethertype(ethertype)} frame"
else
print "Received broadcast #{NetUtils.decode_ethertype(ethertype)} frame"
end
end
else
if src_mac == MAC
print "Sending #{NetUtils.decode_ethertype(ethertype)} frame"
else
print "Received #{NetUtils.decode_ethertype(ethertype)} frame"
end
end
puts src_mac == MAC ? (broadcast ? '' : " to #{dest_mac}") : " from #{src_mac}"
# puts "Bytes:"
# print bytes.pack("C*").hexdump
end
end

View File

@ -1,71 +1,42 @@
require_relative "ipv4_packet.rb"
require_relative "ethernet_frame.rb"
# frozen_string_literal: true
require_relative 'ipv4_packet'
require_relative 'arp_client'
class IPV4Device
attr_reader :ip
# @@ip_to_mac={"10.0.0.180"=>"d4:61:9d:18:72:08"}
@@ip_to_mac={}
def initialize(dev, ip)
@callbacks = []
@dev = dev
@ip = ip
def initialize(dev,ip)
@callbacks=[]
@dev=dev
@ip=ip
dev.register_callback(EthernetFrame::IPV4) do |frame,device|
packet=IPV4Packet.new(frame.data)
dev.register_callback(EthernetFrame::IPV4) do |frame, _device|
packet = IPV4Packet.new(frame.data)
got_packet(packet)
end
end
def send_packet(type,dest_ip,data,broad=false)
if broad
mac="FF:FF:FF:FF:FF:FF"
dest_ip="255.255.255.255"
else
if !@@ip_to_mac[dest_ip]
puts "Sending ARP for #{dest_ip}"
packet=ARPPacket.new(nil,ARPPacket::REQUEST,MAC,ip,dest_ip)
@dev.send_frame(EthernetFrame::ARP,"FF:FF:FF:FF:FF:FF",packet.bytes)
until @@ip_to_mac[dest_ip]
puts "NO MAP"
sleep 1
end
puts "MAP"
end
mac=@@ip_to_mac[dest_ip]
end
@dev.send_frame(EthernetFrame::IPV4,mac,IPV4Packet.new(nil,type,@ip,dest_ip,data).bytes)
def send_packet(type, dest_ip, data)
packet = IPV4Packet.new(nil, type, @ip, dest_ip, data)
packet.dump(@ip)
ArpClient.send_packet(packet)
end
def register_callback(type,&callback)
@callbacks[type]=[] if !@callbacks[type]
def register_callback(type, &callback)
@callbacks[type] = [] unless @callbacks[type]
@callbacks[type].push callback
end
def map_arp(ip,mac)
@@ip_to_mac[ip]=mac
end
private
def got_packet(packet)
return if packet.dest_ip!=@ip and packet.dest_ip!="255.255.255.255"
broadcast=(packet.dest_ip=="255.255.255.255")
# if broadcast
# if NetUtils.decode_ip4type(packet.type)=="Unknown"
# puts "Unknown broadcast packet from #{packet.src_ip}"
# else
# puts "Broadcast #{NetUtils.decode_ip4type(packet.type)} packet from #{packet.src_ip}"
# end
# else
# puts "#{NetUtils.decode_ip4type(packet.type)} packet from #{packet.src_ip}"
# end
if @callbacks[packet.type]
for callback in @callbacks[packet.type]
callback.call(packet,self)
end
return if (packet.dest_ip != @ip) && (packet.dest_ip != '255.255.255.255')
packet.dump(@ip)
@callbacks[packet.type]&.each do |callback|
callback.call(packet, self)
end
end
end

View File

@ -1,51 +1,67 @@
require_relative "net_utils.rb"
# frozen_string_literal: true
require_relative 'net_utils'
class IPV4Packet
UDP=0x11
ICMP = 0x1
UDP = 0x11
attr_reader :bytes
def initialize(bytes,type=nil,src_ip=nil,dest_ip=nil,data=nil)
def initialize(bytes, type = nil, src_ip = nil, dest_ip = nil, data = nil)
if !bytes
@bytes=[0x45,0,0,0xe6,79,0,0,0,255,0,0,0]
size=20+data.length
@bytes[2]=(size&0xFF00)>>8
@bytes[3]=size&0xFF
@bytes[9]=type
@bytes+=NetUtils.decode_ip4(src_ip)
@bytes+=NetUtils.decode_ip4(dest_ip)
i=0
checksum=0
10.times do
checksum+=(@bytes[i]<<8)+@bytes[i+1]
i+=2
end
while (checksum>>16)!=0
checksum=(checksum&0xffff)+(checksum>>16)
end
checksum=(~checksum)&0xFFFF
@bytes[10]=(checksum&0xFF00)>>8
@bytes[11]=(checksum&0xFF)
@bytes+=data
size = 20 + data.length
@bytes = [0x45, 0, size >> 8 , size & 0xFF, 79, 0, 0, 0, 255, type, 0, 0]
# @bytes[2] = size >> 8
# @bytes[3] = size & 0xFF
# @bytes[9] = type
@bytes += NetUtils.decode_ip4(src_ip)
@bytes += NetUtils.decode_ip4(dest_ip)
@bytes[10], @bytes[11] = NetUtils.compute_checksum(@bytes[(0...20)])
@bytes += data
else
@bytes=bytes
@bytes = bytes
end
end
def src_ip()
return NetUtils.format_ip4(@bytes[12,4])
def src_ip
NetUtils.format_ip4(@bytes[12, 4])
end
def dest_ip()
return NetUtils.format_ip4(@bytes[16,4])
def dest_ip
NetUtils.format_ip4(@bytes[16, 4])
end
def type()
return @bytes[9]
def type
@bytes[9]
end
def data()
length=(@bytes[0]&0xF)*4
return @bytes[(length)..-1]
def data
length = (@bytes[0] & 0xF) * 4
@bytes[length..-1]
end
def dump(my_ip)
broadcast = (dest_ip == '255.255.255.255')
if broadcast
if NetUtils.decode_ip4type(type) == 'Unknown'
if src_ip == my_ip
print 'Sending unknown broadcast packet'
else
print 'Received unknown broadcast packet'
end
elsif src_ip == my_ip
print "Sending broadcast #{NetUtils.decode_ip4type(type)} packet"
else
print "Received broadcast #{NetUtils.decode_ip4type(type)} packet"
end
else
if src_ip == my_ip
print "Sending #{NetUtils.decode_ip4type(type)} packet"
else
print "Received #{NetUtils.decode_ip4type(type)} packet"
end
end
puts src_ip == my_ip ? (broadcast ? '' : " to #{dest_ip}") : " from #{src_ip}"
end
end

157
main.rb
View File

@ -1,96 +1,117 @@
require_relative "ethernet_frame.rb"
require_relative "arp_packet.rb"
require_relative "ethernet_device.rb"
require_relative "ipv4_packet.rb"
require_relative "ipv4_device.rb"
require_relative "udp_datagram.rb"
require_relative "my_udp_socket.rb"
require_relative "dhcp_packet.rb"
require_relative "dhcp_option.rb"
# frozen_string_literal: true
require "hexdump"
require_relative 'ethernet_device'
require_relative 'ipv4_packet'
require_relative 'ipv4_device'
require_relative 'my_udp_socket'
require_relative 'dhcp_packet'
require_relative 'dhcp_option'
require_relative 'udp_device'
require_relative 'arp_client'
require_relative 'net_utils'
ip="0.0.0.0"
MAC="08:00:27:D3:71:E7"
require 'macaddr'
require 'json'
require 'packetfu'
eth_device=EthernetDevice.new("enp0s8",MAC)
ip = "10.0.0.#{ARGV[0]}"
ipv4_device=IPV4Device.new(eth_device,ip)
iface = JSON.parse(`ip -json l`).reject { _1['ifname'] == 'lo' }[0]
udp_socket=MyUDPSocket.new(ipv4_device,68,true)
udp_socket.connect("255.255.255.255",67)
sys_mac = iface['address']
option=DHCPOption.new(DHCPOption::RequestList,1,3,6)
sys_mac[0]="0"
sys_mac[1]="A"
packet=DHCPPacket.new(nil,nil,MAC,DHCPPacket::DISCOVER,[option])
MAC = sys_mac.upcase
puts "Using interface #{iface['ifname']} with MAC #{MAC}"
def send_packet(socket,msg,interval,packet)
$t=Thread.new do
while true
puts msg if msg
socket.send(packet.bytes.pack("C*"))
sleep interval
end
end
end
eth_device = EthernetDevice.new(iface['ifname'], MAC)
send_packet(udp_socket,nil,10,packet)
ipv4_device = IPV4Device.new(eth_device, ip)
packet=nil
while true
data,src_ip,src_port=udp_socket.read
packet=DHCPPacket.new(data)
break if packet.mac==MAC and packet.type==DHCPPacket::OFFER
end
udp_device = UDPDevice.new(ipv4_device)
$t.exit
# udp_socket = MyUDPSocket.new(ipv4_device, 68, true)
# udp_socket.connect('255.255.255.255', 67)
option1=DHCPOption.new(DHCPOption::RequestedIP,NetUtils.decode_ip4(packet.yiaddr))
# option = DHCPOption.new(DHCPOption::RequestList, 1, 3, 6)
option2=DHCPOption.new(DHCPOption::ServerIdentifier,NetUtils.decode_ip4(packet.siaddr))
# packet = DHCPPacket.new(nil, nil, MAC, DHCPPacket::DISCOVER, [option])
packet=DHCPPacket.new(nil,nil,MAC,DHCPPacket::REQUEST,[option1,option2])
# def send_packet(socket, msg, interval, packet)
# $t = Thread.new do
# loop do
# puts msg if msg
# socket.send(packet.bytes.pack('C*'))
# sleep interval
# end
# end
# end
send_packet(udp_socket,nil,10,packet)
# send_packet(udp_socket,nil,10,packet)
packet=nil
while true
data,src_ip,src_port=udp_socket.read
packet=DHCPPacket.new(data)
break if packet.mac==MAC and packet.type==DHCPPacket::ACK or packet.type==DHCPPacket::NACK
end
# packet=nil
# while true
# data,src_ip,src_port=udp_socket.read
# packet=DHCPPacket.new(data)
# break if packet.mac==MAC and packet.type==DHCPPacket::OFFER
# end
$t.exit
# $t.exit
if packet.type==DHCPPacket::NACK
puts "Could not obtain IP"
exit 1
else
puts "Obtained IP #{packet.yiaddr}"
end
# option1=DHCPOption.new(DHCPOption::RequestedIP,NetUtils.decode_ip4(packet.yiaddr))
udp_socket.close
# option2=DHCPOption.new(DHCPOption::ServerIdentifier,NetUtils.decode_ip4(packet.siaddr))
ip=packet.yiaddr
# packet=DHCPPacket.new(nil,nil,MAC,DHCPPacket::REQUEST,[option1,option2])
ipv4_device=IPV4Device.new(eth_device,ip)
# send_packet(udp_socket,nil,10,packet)
eth_device.register_callback(EthernetFrame::ARP) do |frame,device|
packet=ARPPacket.new(frame.data)
if packet.is_request?
resp_packet=ARPPacket.new(nil,ARPPacket::REPLY,MAC,ip,packet.src_ip)
device.send_frame(EthernetFrame::ARP,packet.src_mac,resp_packet.bytes)
ipv4_device.map_arp(packet.src_ip,packet.src_mac)
else
ipv4_device.map_arp(packet.src_ip,packet.src_mac)
end
# packet=nil
# while true
# data,src_ip,src_port=udp_socket.read
# packet=DHCPPacket.new(data)
# break if packet.mac==MAC and packet.type==DHCPPacket::ACK or packet.type==DHCPPacket::NACK
# end
# $t.exit
# if packet.type==DHCPPacket::NACK
# puts "Could not obtain IP"
# exit 1
# else
# puts "Obtained IP #{packet.yiaddr}"
# end
# udp_socket.close
# ip=packet.yiaddr
# ipv4_device=IPV4Device.new(eth_device,ip)
ArpClient.initialize(eth_device, MAC, ip)
ipv4_device.register_callback(IPV4Packet::ICMP) do |packet, _device|
data = packet.data
data[0]=0
data[2]=0
data[3]=0
data[2],data[3] = NetUtils.compute_checksum(data)
ipv4_device.send_packet(IPV4Packet::ICMP, packet.src_ip, data)
end
udp_socket=MyUDPSocket.new(ipv4_device,7)
udp_socket = MyUDPSocket.new(udp_device, 7)
while true
msg,src_ip,src_port=udp_socket.read
udp_socket.send(msg,src_ip,src_port)
if ip == '10.0.0.6'
udp_socket.send("hi", '10.0.0.5', 7)
end
loop do
msg, src_ip, src_port = udp_socket.read
puts "Echo got message #{msg}"
udp_socket.send(msg, src_ip, src_port)
end

View File

@ -1,62 +1,50 @@
require "set"
require_relative "udp_datagram.rb"
require_relative "ipv4_packet.rb"
# frozen_string_literal: true
require 'set'
require_relative 'udp_datagram'
require_relative 'ipv4_packet'
require 'hexdump'
class MyUDPSocket
@@ports=Set.new()
@@ports = Set.new
def initialize(ipv4_device,port=nil,broad=false)
@dev=ipv4_device
@broad=broad
@packets=[]
def initialize(udp_device, port = nil)
@dev = udp_device
@packets = []
if port
@port=port
@port = port
else
@port=1025+rand(65536)
while @@ports.include? @port
@port=1025+rand(65536)
end
@port = rand(1025..66_560)
@port = rand(1025..66_560) while @@ports.include? @port
end
@@ports.add(@port)
@dev.register_callback(IPV4Packet::UDP) do |packet,device|
datagram=UDPDatagram.new(packet.data)
if datagram.dest_port==@port
#puts "Datagram from #{packet.src_ip}:#{datagram.src_port} to port #{datagram.dest_port} with data #{datagram.data.chomp}"
@packets.push({:src_ip=>packet.src_ip,:udp_datagram=>datagram})
end
@dev.register_callback(@port) do |src_ip, datagram|
@packets.push({ src_ip: src_ip, udp_datagram: datagram })
end
end
def connect(ip,port)
@bind_ip=ip
@bind_port=port
def connect(ip, port)
@bind_ip = ip
@bind_port = port
end
def send(msg,host=nil,port=nil)
if host
datagram=UDPDatagram.new(nil,@port,port,msg)
# puts "Datagram from port #{datagram.src_port} to #{host}:#{datagram.dest_port} with data #{datagram.data.chomp}"
@dev.send_packet(IPV4Packet::UDP,host,datagram.bytes,@broad)
else
datagram=UDPDatagram.new(nil,@port,@bind_port,msg)
# puts "Datagram from port #{datagram.src_port} to #{@bind_ip}:#{datagram.dest_port} with data #{datagram.data.chomp}"
@dev.send_packet(IPV4Packet::UDP,@bind_ip,datagram.bytes,@broad)
def send(msg, host = nil, port = nil)
@dev.send_datagram(msg, @port, host || @bind_ip, port || @bind_port)
end
def read
while @packets.empty?
end
packet = @packets.shift
[packet[:udp_datagram].data, packet[:src_ip], packet[:udp_datagram].src_port]
end
def read()
until @packets.length!=0
end
packet=@packets.shift
return [packet[:udp_datagram].data,packet[:src_ip],packet[:udp_datagram].src_port]
end
def close()
def close
@@ports.delete @port
@dev.delete_callback(@port)
end
end

View File

@ -1,5 +1,7 @@
require_relative "ethernet_frame.rb"
require_relative "ipv4_packet.rb"
# frozen_string_literal: true
require_relative 'ethernet_frame'
require_relative 'ipv4_packet'
class NetUtils
def self.format_mac(mac_bytes)
@ -9,58 +11,71 @@ class NetUtils
end
def self.format_ip4(ip_bytes)
ip_bytes.map do |byte|
byte.to_s
end.join('.')
ip_bytes.map(&:to_s).join('.')
end
def self.decode_ethertype(ethertype)
case ethertype
when EthernetFrame::ARP
return "ARP"
'ARP'
when EthernetFrame::IPV4
return "IPv4"
'IPv4'
when EthernetFrame::IPV6
return "IPv6"
'IPv6'
else
return "Unknown"
'Unknown'
end
end
def self.decode_ip4type(type)
case type
when IPV4Packet::UDP
return "UDP"
'UDP'
when IPV4Packet::ICMP
'ICMP'
else
return "Unknown"
'Unknown'
end
end
def self.decode_ip4(ip)
ip=ip.split(".")
ip[0]=ip[0].to_i
ip[1]=ip[1].to_i
ip[2]=ip[2].to_i
ip[3]=ip[3].to_i
return ip
ip = ip.split('.')
ip[0] = ip[0].to_i
ip[1] = ip[1].to_i
ip[2] = ip[2].to_i
ip[3] = ip[3].to_i
ip
end
def self.decode_mac(mac)
mac=mac.split(":")
mac[0]=mac[0].to_i(16)
mac[1]=mac[1].to_i(16)
mac[2]=mac[2].to_i(16)
mac[3]=mac[3].to_i(16)
mac[4]=mac[4].to_i(16)
mac[5]=mac[5].to_i(16)
return mac
mac = mac.split(':')
mac[0] = mac[0].to_i(16)
mac[1] = mac[1].to_i(16)
mac[2] = mac[2].to_i(16)
mac[3] = mac[3].to_i(16)
mac[4] = mac[4].to_i(16)
mac[5] = mac[5].to_i(16)
mac
end
def self.decode_int(bytes)
num=bytes[3]
num+=bytes[2]<<8
num+=bytes[1]<<16
num+=bytes[0]<<24
return num
num = bytes[3]
num += bytes[2] << 8
num += bytes[1] << 16
num += bytes[0] << 24
num
end
def self.compute_checksum(bytes)
i = 0
checksum = 0
(bytes.length / 2).times do
checksum += (bytes[i] << 8) + bytes[i + 1]
i += 2
end
checksum += bytes[-1] if bytes.length.odd?
checksum = (checksum & 0xFFFF) + (checksum >> 16) while (checksum >> 16) != 0
checksum = (~checksum) & 0xFFFF
[checksum >> 8, checksum & 0xFF]
end
end

View File

@ -1,28 +0,0 @@
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
struct sockaddr_ll {
unsigned short sll_family; /* Always AF_PACKET */
unsigned short sll_protocol; /* Physical-layer protocol */
int sll_ifindex; /* Interface number */
unsigned short sll_hatype; /* ARP hardware type */
unsigned char sll_pkttype; /* Packet type */
unsigned char sll_halen; /* Length of address */
unsigned char sll_addr[8]; /* Physical-layer address */
};

View File

@ -1,51 +1,51 @@
class UDPDatagram
# frozen_string_literal: true
class UDPDatagram
attr_reader :bytes
def initialize(bytes,src_port=nil,dst_port=nil,data=nil)
def initialize(bytes, src_port = nil, dst_port = nil, data = nil)
if !bytes
@bytes=[]
@bytes[0]=(src_port&0xFF00)>>8
@bytes[1]=src_port&0xFF
@bytes[2]=(dst_port&0xFF00)>>8
@bytes[3]=dst_port&0xFF
length=data.to_s.bytesize+8
@bytes[4]=(length&0xFF00)>>8
@bytes[5]=length&0xFF
@bytes[6]=0
@bytes[7]=0
@bytes+=data.to_s.unpack("C*")
@bytes = []
@bytes[0] = (src_port & 0xFF00) >> 8
@bytes[1] = src_port & 0xFF
@bytes[2] = (dst_port & 0xFF00) >> 8
@bytes[3] = dst_port & 0xFF
length = data.to_s.bytesize + 8
@bytes[4] = (length & 0xFF00) >> 8
@bytes[5] = length & 0xFF
@bytes[6] = 0
@bytes[7] = 0
@bytes += data.to_s.unpack('C*')
else
@bytes=bytes
@bytes = bytes
end
end
def src_port
src_port=@bytes[1]
src_port+=@bytes[0]<<8
return src_port
src_port = @bytes[1]
src_port += @bytes[0] << 8
src_port
end
def dest_port
dest_port=@bytes[3]
dest_port+=@bytes[2]<<8
return dest_port
dest_port = @bytes[3]
dest_port += @bytes[2] << 8
dest_port
end
def data
return @bytes[8,data_length].pack("C*")
@bytes[8, data_length].pack('C*')
end
def data_length()
return length-8
def data_length
length - 8
end
private
def length()
length=@bytes[5]
length+=@bytes[4]<<8
return length
def length
len = @bytes[5]
len += @bytes[4] << 8
len
end
end

56
udp_device.rb Normal file
View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
require_relative 'udp_datagram'
require_relative 'ipv4_packet'
require 'hexdump'
class UDPDevice
def initialize(dev)
@callbacks = {}
@dev = dev
@packets = []
dev.register_callback(IPV4Packet::UDP) do |packet, _device|
datagram = UDPDatagram.new(packet.data)
got_datagram(packet.src_ip, datagram)
end
end
def send_datagram(msg, src_port, host, port)
datagram = UDPDatagram.new(nil, src_port, port, msg)
puts "Sending datagram from port #{datagram.src_port} to #{host}:#{datagram.dest_port}"
# puts "Data:"
# print datagram.data.hexdump
# puts "Bytes:"
# puts datagram.bytes.pack("C*").hexdump
@dev.send_packet(IPV4Packet::UDP, host, datagram.bytes)
end
def register_callback(port, &callback)
raise Errno::EADDRINUSE, "bind(2) for \"#{dev.ip}\" port #{port}" if @callbacks[port]
@callbacks[port] = callback
end
def delete_callback(port)
@callbacks.delete port
end
private
def got_datagram(src_ip, datagram)
print "Received datagram from #{src_ip}:#{datagram.src_port} to port #{datagram.dest_port}"
if @callbacks[datagram.dest_port]
puts ''
# puts 'Data:'
# print datagram.data.hexdump
else
puts ' dropped'
end
# puts "Data:"
# puts datagram.data.hexdump
@callbacks[datagram.dest_port]&.call(src_ip, datagram)
end
end