Can't Parse DHCP packets with Ryu's get_protocol(dhcp.dhcp) - parsing

I'm using the Ryu SDN controller with an Open vSwitch on mininet using OpenFlow 1.3 to parse DHCP packets. Following online examples and the Ryu resources, I've implemented a DHCP packet parser. However, it does not work as I expected it to, and I'm wondering if anyone has any insight as to why my first solution does not work?
An example of a code snippet for parsing a DHCP packet is below:
from ryu.lib.packet import dhcp
...
...
#set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
pkt = packet.Packet(msg.data)
dhcpPacket = pkt.get_protocol(dhcp.dhcp)
My code follows a similar vein:
from ryu.lib.packet import dhcp
...
...
#set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
pkt = {}
pkt['msg'] = ev.msg
pkt['dp'] = pkt['msg'].datapath
pkt['pkt'] = packet.Packet(pkt['msg'].data)
pkt['dhcp'] = pkt['pkt'].get_protocol(dhcp.dhcp)
This seems reasonable as I am following this exact sequence with other protocols like ARP, ICMP, IP, etc. Examples below.
pkt['arp'] = pkt['pkt'].get_protocol(arp.arp)
pkt['ip'] = pkt['pkt'].get_protocol(ipv4.ipv4)
pkt['icmp'] = pkt['pkt'].get_protocol(icmp.icmp)
The only problem is that the three parsers I list above actually return data, while the get_protocol for DHCP consistently returns None. I have already tested this by sending DHCP packets through my switch.
What does work is the following code snippet where I identify packet lists having more than three values. I save the value at index three and set that as my DHCP packet. In the DHCP packet, I concentrate of parsing the string at index 2. That contains the data I'm interested in.
# pkt['dhcp'] = pkt['pkt'].get_protocol(dhcp.dhcp)
# Check if pkt['pkt]] > 3 elements, if so, parse DHCP string
#Standard pkt['dhcp'] = (None, None, String)
if len(pkt['pkt']) > 3:
pkt['dhcp'] = dhcp.dhcp.parser(pkt['pkt'][3])
pkt['op'] = hex(ord(dhcp_p[2][0]))
pkt['htype'] = hex(ord(dhcp_p[2][1]))
pkt['hlen'] = hex(ord(dhcp_p[2][2]))
pkt['hops'] = hex(ord(dhcp_p[2][3]))
def parseDHCP(pkt_d,start,stop):
s_value = ''
stop += 1
for val in range(start,stop):
s_value += str(hex(ord(pkt_d[val])))
return s_value
pkt['xid'] = parseDHCP(dhcp_p[2],4,7)
pkt['secs'] = parseDHCP(dhcp_p[2],8,9)
pkt['flags'] = parseDHCP(dhcp_p[2],10,11)
pkt['ciaddr'] = parseDHCP(dhcp_p[2],12,15)
pkt['yiaddr'] = parseDHCP(dhcp_p[2],16,19)
pkt['siaddr'] = parseDHCP(dhcp_p[2],20,23)
pkt['giaddr'] = parseDHCP(dhcp_p[2],24,27)
pkt['chaddr'] = parseDHCP(dhcp_p[2],28,33)
pkt['pad'] = parseDHCP(dhcp_p[2],34,43)
A print out of these values looks like so:
0x1
0x1
0x6
0x0
0x440x30x980x11
0x00x0
0x00x0
0x00x00x00x0
0x00x00x00x0
0x00x00x00x0
0x00x00x00x0
0x7e0x1d0xcc0xe70xee0x4f
0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x0
The above code allows me to observe the contents of DHCP packets, but I'm really trying to figure out why I'm not achieving similar results using the pkt['pkt'].get_protocol(dhcp.dhcp) method?

Ok, I found the problem. In lines 200 – 218 of dhcp.py there is a try except statement. I pulled the cls._parser out of the try to see the error it throws and I get this:
Ryuretic_coupler: Exception occurred during handler processing. Backtrace
from offending handler [initial_event] servicing event [EventOFPPacketIn] follows.
Traceback (most recent call last):
File "/home/ubuntu/ryu/ryu/base/app_manager.py", line 290, in _event_loop
handler(ev)
File "/home/ubuntu/ryu/ryu/app/Ryuretic/Ryuretic.py", line 72, in initial_event
pkt = parsPkt.handle_pkt(ev)
File "/home/ubuntu/ryu/ryu/app/Ryuretic/Pkt_Parse13.py", line 81, in handle_pkt
dhcp_p = pkt['dhcp'] = dhcp.dhcp.parser(pkt['pkt'][3])
File "/home/ubuntu/ryu/ryu/lib/packet/dhcp.py", line 212, in parser
return cls._parser(buf)
File "/home/ubuntu/ryu/ryu/lib/packet/dhcp.py", line 192, in _parser
) = struct.unpack_from(unpack_str, buf)
error: unpack_from requires a buffer of at least 233 bytes
So, dhcp.py is not receiving the 233 bytes that it requires. Unfortunately, I am using an Open vSwitch on the VM provided by SDN hub, and 128 bytes seems to be a limit. So, I have a conflict with Ryu's dhcp.py file. My solution was to modify dhcp.py. How that was done follows.
Before modifying the code, I recommend you first update your Ryu controller. The procedures are as follows:
Step 1: If you are using a VM. Take a stapshot or clone it now.
Step 2: Update Ryu
cd ryu
git pull
If you were running an older version like I was, then the following error may occur when you next attempt to run your Ryu controller:
Traceback (most recent call last):
File "./bin/ryu-manager", line 18, in <module>
from ryu.cmd.manager import main
File "/home/ubuntu/ryu/ryu/cmd/manager.py", line 31, in <module>
from ryu.base.app_manager import AppManager
File "/home/ubuntu/ryu/ryu/base/app_manager.py", line 37, in <module>
from ryu.controller.controller import Datapath
File "/home/ubuntu/ryu/ryu/controller/controller.py", line 74, in <module>
help='Maximum number of unreplied echo requests before datapath is disconnected.')
File "/usr/local/lib/python2.7/dist-packages/oslo_config/cfg.py", line 1033, in __init__
super(IntOpt, self).__init__(name, type=types.Integer(), **kwargs)
TypeError: __init__() got an unexpected keyword argument 'min'
Step 3: Update your oslo_config file
sudo pip install oslo.config --upgrade
The TypeError from step 2 should now be resolved. (Hopefully, you cloned your VM just in case.)
Step 3: Modify Ryu's dhcp.py file
Ryu's dhcp.py file (located at /ryu/ryu/lib/packet) expects to receive a buffer of more than 235 bytes. Otherwise, it throws and error and returns nothing to the controller. Since my buffer only receives a buffer size of about 81 bytes. I modified the Ryu dhcp.py file as follows.
The error occurs because dhcp.py specifies a string format of '!BBBBIHH4s4s4s4s16s64s128s'. In #1, I created a second option. Do so, allows me to insert a few if statements to handle the packet differently if a packet arrives that is smaller than 100 bytes. Likewise, if the packet is greater than 235 bytes, then dhcp.py's handles the packet normally and returns the extra values.
class dhcp(packet_base.PacketBase):
"""DHCP (RFC 2131) header encoder/decoder class.
....deleted....
"""
_MIN_LEN = 236
_HLEN_UNPACK_STR = '!BBB'
_HLEN_UNPACK_LEN = struct.calcsize(_HLEN_UNPACK_STR)
_DHCP_UNPACK_STR = '!BIHH4s4s4s4s%ds%ds64s128s'
##################################################
#1(mod) Created second option for unpacking string
_DHCP_UNPACK_STR2 = '!BIHH4s4s4s4s%ds%ds40s'
_DHCP_PACK_STR = '!BBBBIHH4s4s4s4s16s64s128s'
#################################################
_DHCP_CHADDR_LEN = 16
_HARDWARE_TYPE_ETHERNET = 1
_class_prefixes = ['options']
_TYPE = {
'ascii': [
'ciaddr', 'yiaddr', 'siaddr', 'giaddr', 'chaddr', 'sname'
]
}
def __init__(self, op, chaddr, options, htype=_HARDWARE_TYPE_ETHERNET,
hlen=0, hops=0, xid=None, secs=0, flags=0,
ciaddr='0.0.0.0', yiaddr='0.0.0.0', siaddr='0.0.0.0',
giaddr='0.0.0.0', sname='', boot_file=b''):
super(dhcp, self).__init__()
#...Deleted No Changes made to init.
#classmethod
def _parser(cls, buf):
(op, htype, hlen) = struct.unpack_from(cls._HLEN_UNPACK_STR, buf)
buf = buf[cls._HLEN_UNPACK_LEN:]
####################################################
#2(mod) provided option for smaller packet sizes
if len(buf) < 100:
unpack_str = cls._DHCP_UNPACK_STR2 % (hlen,
(cls._DHCP_CHADDR_LEN - hlen))
else:
unpack_str = cls._DHCP_UNPACK_STR % (hlen,
(cls._DHCP_CHADDR_LEN - hlen))
#####################################################
min_len = struct.calcsize(unpack_str)
######################################################
#3(mod) provided option for smaller packets, set bootfile to b''
if min_len > 233:
(hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr,
chaddr, dummy, sname, boot_file
) = struct.unpack_from(unpack_str, buf)
else:
boot_file=b''
(hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr,
chaddr, dummy, sname, boot_file
) = struct.unpack_from(unpack_str, buf)+(boot_file,)
########################################################
length = min_len
########################################################
# (mod) provided option for smaller packet sizes, no parse_opt
if len(buf) > 233:
parse_opt = options.parser(buf[min_len:])
length += parse_opt.options_len
else:
parse_opt = None
length = min_len
#########################################################
return (cls(op, addrconv.mac.bin_to_text(chaddr), parse_opt,
htype, hlen, hops, xid, secs, flags,
addrconv.ipv4.bin_to_text(ciaddr),
addrconv.ipv4.bin_to_text(yiaddr),
addrconv.ipv4.bin_to_text(siaddr),
addrconv.ipv4.bin_to_text(giaddr),
sname.decode('ascii'), boot_file),
None, buf[length:])
The complete file will soon be on https://github.com/Ryuretic/RyureticLabs/tree/master/ryu/ryu/app/Ryuretic/Support_Files.
If you make these adjustements to the dhcp.py file, then you will gain access to the following header fields:
============== ====================
Attribute Description
============== ====================
op Message op code / message type.\
1 = BOOTREQUEST, 2 = BOOTREPLY
htype Hardware address type (e.g. '1' = 10mb ethernet).
hlen Hardware address length (e.g. '6' = 10mb ethernet).
hops Client sets to zero, optionally used by relay agent\
when booting via a relay agent.
xid Transaction ID, a random number chosen by the client,\
used by the client and serverto associate messages\
and responses between a client and a server.
secs Filled in by client, seconds elapsed since client\
began address acquisition or renewal process.
flags Flags.
ciaddr Client IP address; only filled in if client is in\
BOUND, RENEW or REBINDING state and can respond\
to ARP requests.
yiaddr 'your' (client) IP address.
siaddr IP address of next server to use in bootstrap;\
returned in DHCPOFFER, DHserver.
giaddr Relay agent IP address, used in booting via a\
relay agent.
chaddr Client hardware address.
sname(partial) Optional server host name, null terminated string.

Related

Frame.msg not working with db_decode from cantools

I want to use the can_msgs/Frame.msg for decoding can messages using db.decode_message(Frame.id, Frame.data) but it is giving error
I want to try and write a new Frame.msg format but will it help?
def callback(Frame):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", Frame.data)
Temp = db.decode_message(Frame.id, Frame.data)
temp.data = Temp
pub.publish(temp)
I want to print the message in the dbc format that the cantools package helps decoding.
Error:
File "safa.py", line 42, in callback
temp = db.decode_message(Frame.id, Frame.data)
File "build/bdist.linux-x86_64/egg/cantools/database/can/database.py", line 379, in decode_message
message = self._name_to_message[frame_id_or_name]
KeyError: 10
Looking at the cantools documentation for db.decode_message, the KeyError is a bad description/error msg for the following.
db is a database (class cantools.database.can.Database), which stores all the weird encodings we may choose to use with cantools. To decode a message, it must already have an encoding stored within it, using one of the db.add_* methods with the class.
Edit:
The definition of the can_msgs/Frame is
std_msgs/Header header
uint32 id
bool is_rtr
bool is_extended
bool is_error
uint8 dlc
uint8[8] data
The data term requires an array/list of 8 uint8 values, not a string. Any CANbus data encoding must finish in this form.
Additionally, ROS provides an interface to the CANbus already: socketcan_bridge can be called in a launch file with your node at the same time.
Before you can decode messages you should setup the database e.g by loading a dbc file:
try:
dbc_file_object = open(dbc_file, 'r')
except IOError as e:
rospy.loginfo('Unable to open file {}'.format(e))
sys.exit(-1)
self.db = cantools.db.load(dbc_file_object)

How can I send the output of buttons via MQTT to a broker?

I've only been programming Python for two days. I want to send the output of buttons via MQTT to a broker (and then of course to a subscriber). I have programmed the buttons so far and would like to start the publisher-part now.
import tkinter as tk
from tkinter import ttk
from tkinter import Button
import paho.mqtt.client as mqtt
root = tk.Tk()
root.attributes('-fullscreen', True)
root.bind('<Escape>',lambda e: root.destroy())
a=0
def close_window():
root.destroy()
def callback():
print ("1")
a=1
def callback1():
print ("2")
a=2
def callback2():
print ("3")
a=3
s=ttk.Style()
s.configure('my.TButton', font=('Helvetica', '70'))
bx = ttk.Button(root, text = "x", style='my.TButton', command = close_window)
bx.pack(fill=tk.BOTH, anchor=tk.N, ipady=60)
c = Button(root, text="", height=15)
c.pack(fill=tk.BOTH, anchor=tk.N,)
b1 = ttk.Button(root, text="1", style='my.TButton', command=callback)
b1.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
b2 = ttk.Button(root, text="2", style='my.TButton', command=callback1)
b2.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
b3 = ttk.Button(root, text="3", style='my.TButton', command=callback2)
b3.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
broker_address="xxxx"
client1 = mqtt.Client("Publisher1")
client1.connect("xxxx")
client1.publish("Button/Number", a, qos=2)
root.mainloop()
I want the variable "a" to change with each button stroke, and then be sent 1 time. I have now added the basic publisher script but get many different error messages like:
ConnectionRefusedError: [Errno 61] Connection refused (Btw, I want to use my Laptop as a broker, I took the local IP address)
and:
PermissionError: [Errno 13] Permission denied (same story)
I am aware that the Publisher Part will not work, but I don't know what I am missing or what I need to add.

send xml data via socket with message (VLI) Ruby

Ruby novice. First time post so excuse any communication protocol inadequacies :)
This site has been a great help and a "HUGE!!!" shoutout of thanks to all.
I need to connect my rails app to an electricity providers api so I can vend electricity to my web customers. I'm needing some help to simply get an initial request sent to the API
IP: 41.204.194.188
Port: 8945
First block: What is a message variable length indicator (VLI)?
"2 bytes precede every message sent to/from BizSwitch. The 2 bytes are referred to as a variable length indicator. Bytes 1-2 indicate the number of bytes in the message (excluding the first 2 bytes). The 2 bytes represent a 16bit unsigned integer in network byte order. Note that if a compressed message is being sent, the message will have to first be compressed, in order to determine its length, before being sent."
Ignore compression.
link to api doc: https://dl.dropboxusercontent.com/u/3815995/Ipay-prepaidElecTransactionSpec.pdf
Simple Vend Request example:
<ipayMsg client="ipay" term="1" seqNum="0" time="2002-05-16 10:55:30 +0200">
<elecMsg ver="2.37">
<vendReq>
<ref>136105500001</ref>
<amt cur="ZAR">11400</amt>
<numTokens>1</numTokens>
<meter>A12C3456789</meter>
<payType>cash</payType>
</vendReq >
</elecMsg>
</ipayMsg>
Simple Vend Response example
<ipayMsg client="ipay" term="1" seqNum="0" time="2002-05-16 10:55:35 +0200">
<elecMsg ver="2.37"
<vendRes> <ref>136105500001</ref>
<res code="elec000">OK</res>
<util addr="Megawatt Park, Contact Centre tel 086-003-7566" taxRef="4740101508" distId="6004708001509">Eskom Online</util>
<stdToken units="346.34" rctNum="12345678" amt="10000" tax="1400">12345678901234567890</stdToken>
<rtlrMsg>060000 Warning: This meter is not configured for FBE.</rtlrMsg>
<customerMsg>Meter not registered for Free Basic Electricity. Please apply at your local office.</customerMsg>
</vendRes>
</elecMsg>
</ipayMsg>
I've got this far and I seem connected but how do I actually send and receive responses? I've tried googling for help but yet to find how to send the XML packet and then receive the response.
#!/usr/bin/env ruby
require 'socket'
begin
socket = TCPSocket.new('41.204.194.188', 8945)
rescue => e
puts "error: #{e}"
else
puts "connected"
end
socket.close
Would appreciate any assistance or a nudge in the right direction.
Kind regards,
Jamie
Great I figured it out. Main issue regarding communication to the socket was sending a message variable length indicator. This stackoverflow question put me on the write path "Ruby - How to represent message length as 2 binary bytes"
Step 1: determine the length of my xml message length = message.size
The first field in the header must be the message length which is defined as a 2 binary byte message length in network byte order.
Step 2: message_variable_length_indicator = [length].pack("n")
Step 3: Connect to socket streamSock = TCPSocket::new('41.204.194.188', 8945)
Step 4: streamSock.write(message_variable_length_indicator)
Step 5: streamSock.write(message)
Step 6: get a response str = streamSock.recvfrom(1000)
Now to deal with timeout but at least I'm connecting :)
I have done this in PHP may be you will get idea from below code its working fine for my request.
function sendSocketRequest($XmlString, $Socket_Request) {
if (!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Couldn't create socket: [$errorcode] $errormsg \n");
}
if (!socket_connect($sock, $Socket_Request['HostName'], $Socket_Request['Port'])) {
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
die("Could not connect: [$errorcode] $errormsg \n");
}
$status = socket_write($sock, pack_int32be(strlen($XmlString)), 4);
$status = socket_write($sock, $XmlString, strlen($XmlString));
$response = socket_read($sock, $this->_socketReadLength);
socket_close($sock);
return substr($response, 2);
}
function pack_int32be($i) {
if ($i < -2147483648 || $i > 2147483647) {
die("Out of bounds");
}
return pack('C4', ($i >> 24) & 0xFF, ($i >> 16) & 0xFF, ($i >> 8) & 0xFF, ($i >> 0) & 0xFF
);
}
$socketResponse = sendSocketRequest($yourXMLString, array('HostName'=>'<HostName>','Port'=>'<Port>');

DBSeqRecord cannot access annotations, features using BioSQL under BioPython

Running on Ubuntu 12.10 with Python 2.7, latest BioPython and BioSQL.
I have successfully established the MySQL-based BioSQL server, and I can load sequences into the system properly (or they seem to be proper--tables are populated correctly in MySQL and things are generally error-free).
However--when I retrieve via 'lookup,' I can only access the id, name, and description for the DBSeqRecords. Annotations and features are supposed to be called on demand, but this crashes things. For example:
File "/usr/lib/pymodules/python2.7/Bio/SeqRecord.py", line 595, in __str__
lines.append("Number of features: %i" % len(self.features))
File "/usr/lib/pymodules/python2.7/BioSQL/BioSeq.py", line 516, in __get_features
self._primary_id)
File "/usr/lib/pymodules/python2.7/BioSQL/BioSeq.py", line 280, in _retrieve_features
feature.location = SeqFeature.FeatureLocation(start, end)
File "/usr/lib/pymodules/python2.7/Bio/SeqFeature.py", line 561, in __init__
raise TypeError(start)
TypeError: 0
Any idea what is happening here?
I encountered the same error just this week.
A feature's start and end positions are retrieved from MySQL as type long (not int) but SeqFeature.py expects int only for start and end when instantiating a FeatureLocation. I don't know what has changed in mysql server, MySQLdb, BioSeqDatabase or SeqFeature to have provoked the problem.
Hopefully one of the biopython developers will be able to provide a long term solution but these are suggestions for temporary fixes:
try to get to the MySQLdb connection used by BioSeqDatabase and change the conversion behaviour with the conv keyword parameter of its connect function (I haven't been able to do this)
hack BioSeqDatabase so that start_pos and end_pos are converted to int after the values are fetched from MySQL (I haven't done this)
hack SeqFeature.py to allow long type for start and end when instantiating a FeatureLocation object (this is what I have done). Change this:
if isinstance(start, AbstractPosition):
self._start = start
elif isinstance(start, int):
self._start = ExactPosition(start)
else:
raise TypeError(start)
if isinstance(end, AbstractPosition):
self._end = end
elif isinstance(end, int):
self._end = ExactPosition(end)
else:
raise TypeError(end)
to this:
if isinstance(start, AbstractPosition):
self._start = start
elif isinstance(start, int) or isinstance(start, long):
self._start = ExactPosition(start)
else:
raise TypeError(start)
if isinstance(end, AbstractPosition):
self._end = end
elif isinstance(end, int) or isinstance(end, long):
self._end = ExactPosition(end)
else:
raise TypeError(end)

How to receive UDP data in Vala?

another Vala problem occured: I try to send and receive data via UDP. The sending works and via Wireshark I can see that the server sends the expected result. Problem is: My program doesn't get the data.
I checked and I can see that, when a socket has been created to send the UDP data, the specific port stays open, which is confirmed by Wireshark because my PC doesn't send any of those ICMP messages back to the server.
What I got so far:
try
{
SocketClient mySocket = new SocketClient();
mySocket.protocol = SocketProtocol.UDP;
mySocket.type = SocketType.DATAGRAM;
var conn = mySocket.connect (new InetSocketAddress(addr,targetPort));
conn.output_stream.write(themessage_in_a_uint8_array);
DataInputStream response = new DataInputStream (conn.input_stream);
string resp ="";
char myChar;
try
{
do
{
myChar = (char)response.read_byte();
print ("Response" + myChar.to_string());
}while(true);
}
catch(Error e)
{
print(e.message);
}
}
catch(Error e)
{print(e.message);}
What currently happens: The message is send, the string 'Response' is printed once into the console and after that it just loops.
If I check response.get_available() it returns 0.
I can check with lsof | grep used_portnumber and sure enough, the used socket stays open. What am I doing wrong?
I am not sure but this is what I suspect:
UDP is a datagram protocol (data is explicitly chopped into data). Server have sent one datagram to client. Now in BSD Sockets (and after it everywhere) if the underlaying socket have datagram type then read reads the full packet. If the buffer have insufficient length the message is truncated.
The solution is read in one byte. For example
uint8[] buffer = new uint8[1 << 16]; // Maximum UDP length - we don't loose anything
unowned string locale;
bool need_convert = GLib.get_charset (out locale);
do {
ssize_t len = response.read (buffer);
string text;
if (need_convert) {
text = GLib.convert ((string)buffer, len, locale, "UTF-8");
} else {
text = (string)buffer;
}
stdout.print("Response " + text);
} while (true);
Edit I have change the code to print UTF-8 text - without assuming current locale is "UTF-8"-based.
PS 1 This is my guess as it is one gotcha of BSD Sockets (also Winsockets and everything that builds on this) that come to my mind. Please be graceful if the question will be more specific (i.e. it is not the answer to question).
PS 2 In general I would recommend against mixing bytes and chars. While in ASCII-compatible encodings (ISO, UTF-8) sending ASCII subset of chars is safe it will bite when attempt on CJK encodings or if sender will send 'ą' by UTF-8 and sender will treat it as ISO-8859-2 (where this character have different encoding). I assume it is for the toy-examples only. If not you may want to read What Every Programmer Absolutely, Positively Needs To Know About Encodings And Character Sets To Work With Text.

Resources