cloudFPGA (cF) API  1.0
The documentation of the source code of cloudFPGA (cF)
tc_UdpRecv.py
Go to the documentation of this file.
1 #/*
2 # * Copyright 2016 -- 2021 IBM Corporation
3 # *
4 # * Licensed under the Apache License, Version 2.0 (the "License");
5 # * you may not use this file except in compliance with the License.
6 # * You may obtain a copy of the License at
7 # *
8 # * http://www.apache.org/licenses/LICENSE-2.0
9 # *
10 # * Unless required by applicable law or agreed to in writing, software
11 # * distributed under the License is distributed on an "AS IS" BASIS,
12 # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # * See the License for the specific language governing permissions and
14 # * limitations under the License.
15 # */
16 
17 # *****************************************************************************
18 # * @file : tc_UdpRecv.py
19 # * @brief : A single-threaded script to receive traffic on the UDP
20 # * connection of an FPGA module.
21 # *
22 # * System: : cloudFPGA
23 # * Component : cFp_BringUp/ROLE
24 # * Language : Python 3
25 # *
26 # *****************************************************************************
27 
28 # ### REQUIRED PYTHON PACKAGES ################################################
29 import argparse
30 import datetime
31 import errno
32 import socket
33 import struct
34 import netifaces as ni
35 import time
36 
37 from netifaces import AF_INET
38 
39 # ### REQUIRED TESTCASE MODULES ###############################################
40 from tc_utils import *
41 
42 
43 def udp_rx_loop(clientSock, serverSock, size, ip_da, udp_dp, count, verbose=False):
44  """UDP Rx Single-Thread Ramp.
45  Requests the FPGA to open a new active port and expects to receive 'count' datagrams
46  of 'size' bytes. Each datagram is made of the following repetitive pattern
47  '48692066726f6d200x464d4b553630210a' which decodes into the string "Hi from FMKU60\n".
48  :param clientSock The socket to send to.
49  :param serverSock The socket to receive from.
50  :param size The size of the expected segment.
51  :param ip_da The destination address of the host.
52  :param udp_dp The active destination port number that the FPGA is requested to open.
53  :param count The number of datagrams to receive.
54  :param verbose Enables verbosity.
55  :return None"""
56  if verbose:
57  print("[INFO] Requesting the FPGA to send %d datagrams of %d bytes.\n" % (count, size))
58  nrErr = 0
59  loop = 0
60  totalReceivedBytes = 0
61 
62  # Set the server socket non-blocking
63  # --------------------------------------
64  serverSock.setblocking(False)
65  serverSock.settimeout(5)
66 
67  # Request the test to generate a datagram of length='size' and to send it to
68  # socket={ip_da, tcp_dp}. This is done by sending 'ip_da', 'tcp_dp' and 'size'
69  # to the FPGA UDP_DP=8801 while turning the 'ip_da' into an unsigned int binary
70  # and 'tcp_dp' and 'size' into an unsigned short binary data.
71  reqMsgAsBytes = struct.pack(">IHH", ip_da, udp_dp, size)
72  if verbose:
73  print("\n\n[DEBUG] reqMsgAsBytes = %s" % reqMsgAsBytes)
74 
75  startTime = datetime.datetime.now()
76  while loop < count:
77  # SEND message length request to FPGA
78  # ------------------------------------
79  try:
80  clientSock.sendall(reqMsgAsBytes)
81  except socket.error as exc:
82  # Any exception
83  print("[EXCEPTION] Socket error while transmitting :: %s" % exc)
84  exit(1)
85  finally:
86  pass
87 
88  # RECEIVE message length bytes from FPGA
89  # ---------------------------------------
90  currRxByteCnt = 0
91  while currRxByteCnt < size:
92  try:
93  data = serverSock.recv(MTU)
94  except IOError as e:
95  # On non blocking connections - when there are no incoming data, error is going to be raised
96  # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
97  # We are going to check for both - if one of them - that's expected, means no incoming data,
98  # continue as normal. If we got different error code - something happened
99  if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
100  print("[ERROR] Socket reading error: {}".format(str(e)))
101  # We just did not receive anything
102  print("\t[INFO] So far we received %d bytes out of %d." % (currRxByteCnt, size))
103  count = loop
104  break
105  except socket.error as exc:
106  # Any other exception
107  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
108  exit(1)
109  else:
110  currRxByteCnt += len(data)
111  if verbose:
112  print("[INFO] Loop=%d | RxBytes=%d | RxData=%s\n" % (loop, len(data), data))
113  # [FIXME-TODO: assess the stream of received bytes]
114  finally:
115  pass
116  totalReceivedBytes += currRxByteCnt;
117  loop += 1
118  endTime = datetime.datetime.now()
119  elapseTime = endTime - startTime
120 
121  if totalReceivedBytes < 1000000:
122  print("[INFO] Received a total of %d bytes." % totalReceivedBytes)
123  elif totalReceivedBytes < 1000000000:
124  megaBytes = (totalReceivedBytes * 1.0) / (1024 * 1024 * 1.0)
125  print("[INFO] Received a total of %.1f MB." % megaBytes)
126  else:
127  gigaBytes = (totalReceivedBytes * 1.0) / (1024 * 1024 * 1024 * 1.0)
128  print("[INFO] Transferred a total of %.1f GB." % gigaBytes)
129 
130  bandwidth = (totalReceivedBytes * 8 * 1.0) / (elapseTime.total_seconds() * 1024 * 1024)
131  print("#####################################################")
132  if bandwidth < 1000:
133  print("#### UDP Rx DONE with bandwidth = %6.1f Mb/s " % bandwidth)
134  else:
135  bandwidth = bandwidth / 1000
136  print("#### UDP Rx DONE with bandwidth = %2.1f Gb/s " % bandwidth)
137  if (totalReceivedBytes != (size * count)):
138  print("#### [WARNING] UDP data loss = %.1f%%" % (1-(totalReceivedBytes)/(size*count)))
139  print("#####################################################")
140  print()
141 
142 
143 
148 
149 # STEP-1: Parse the command line strings into Python objects
150 # -----------------------------------------------------------------------------
151 parser = argparse.ArgumentParser(description='A script to receive UDP data from an FPGA module.')
152 parser.add_argument('-fi', '--fpga_ipv4', type=str, default='',
153  help='The IPv4 address of the FPGA (a.k.a image_ip / e.g. 10.12.200.163)')
154 parser.add_argument('-ii', '--inst_id', type=int, default=0,
155  help='The instance ID assigned by the cloudFPGA Resource Manager (e.g. 42)')
156 parser.add_argument('-lc', '--loop_count', type=int, default=10,
157  help='The number of test runs (default is 10)')
158 parser.add_argument('-mi', '--mngr_ipv4', type=str, default='10.12.0.132',
159  help='The IPv4 address of the cloudFPGA Resource Manager (default is 10.12.0.132)')
160 parser.add_argument('-mp', '--mngr_port', type=int, default=8080,
161  help='The TCP port of the cloudFPGA Resource Manager (default is 8080)')
162 parser.add_argument('-sd', '--seed', type=int, default=-1,
163  help='The initial number to seed the pseudorandom number generator.')
164 parser.add_argument('-sz', '--size', type=int, default=-1,
165  help='The size of the datagram to receive.')
166 parser.add_argument('-un', '--user_name', type=str, default='',
167  help='A user-name as used to log in ZYC2 (.e.g \'fab\')')
168 parser.add_argument('-up', '--user_passwd', type=str, default='',
169  help='The ZYC2 password attached to the user-name')
170 parser.add_argument('-v', '--verbose', action="store_true",
171  help='Enable verbosity')
172 
173 args = parser.parse_args()
174 
175 if args.user_name == '' or args.user_passwd == '':
176  print("\n[WARNING] You must provide a ZYC2 user name and the corresponding password for this script to execute.\n")
177  exit(1)
178 
179 # STEP-2a: Retrieve the IP address of the FPGA module (this will be the SERVER)
180 # -----------------------------------------------------------------------------
181 ipFpga = getFpgaIpv4(args)
182 
183 # STEP-2b: Retrieve the instance Id assigned by the cloudFPGA Resource Manager
184 # -----------------------------------------------------------------------------
185 instId = getInstanceId(args)
186 
187 # STEP-2c: Retrieve the IP address of the cF Resource Manager
188 # -----------------------------------------------------------------------------
189 ipResMngr = getResourceManagerIpv4(args)
190 
191 # STEP-3a: Set the UDP listen port of the FPGA server (this one is static)
192 # -----------------------------------------------------------------------------
193 portFpgaServer = XMIT_MODE_LSN_PORT # 8801
194 
195 # STEP-3b: Retrieve the TCP port of the cloudFPGA Resource Manager
196 # -----------------------------------------------------------------------------
197 portResMngr = getResourceManagerPort(args)
198 
199 # STEP-4: Trigger the FPGA role to restart (i.e. perform SW reset of the role)
200 # -----------------------------------------------------------------------------
201 restartApp(instId, ipResMngr, portResMngr, args.user_name, args.user_passwd)
202 
203 # STEP-5: Ping the FPGA
204 # -----------------------------------------------------------------------------
205 pingFpga(ipFpga)
206 
207 # STEP-6a: Set the FPGA socket association
208 # -----------------------------------------------------------------------------
209 fpgaServerAssociation = (str(ipFpga), portFpgaServer)
210 
211 # STEP-6b: Set the socket association for receiving from the FPGA client
212 # -----------------------------------------------------------------------------
213 dpHost = 2718 # Default UDP cF-Themisto ports are in range 2718-2750
214 fpgaClientAssociation = (str(ipFpga), dpHost)
215 
216 # STEP-7a: Create a UDP/IP client socket for sending to FPGA server
217 # -----------------------------------------------------------------------------
218 try:
219  udpClientSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
220 except Exception as exc:
221  print("[EXCEPTION] %s" % exc)
222  exit(1)
223 
224 # STEP-7b: Create a UDP/IP server socket for listening to FPGA client and set
225 # its OS buffer size to 64KB+
226 # -----------------------------------------------------------------------------
227 try:
228  udpServerSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
229 except Exception as exc:
230  print("[EXCEPTION] %s" % exc)
231  exit(1)
232 udpServerSock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 100*1024)
233 
234 # STEP-8a: Bind the client socket before connect (optional).
235 # This trick enables us to ask the kernel to select a specific source IP and
236 # source PORT by calling bind() before calling connect().
237 # -----------------------------------------------------------------------------
238 if 0:
239  try:
240  udpClientSock.bind(hostClientAssociation)
241  print('[INFO] Binding the socket address of the HOST to {%s, %d}' % hostClientAssociation)
242  except Exception as exc:
243  print("[EXCEPTION] %s" % exc)
244  exit(1)
245 
246 # STEP-8b: Bind the server socket before connect (compulsory).
247 # Issued here to associate the server socket with a specific remote IP address and UDP port.
248 # -----------------------------------------------------------------------------
249 hostname = socket.gethostname()
250 ipHostStr = socket.gethostbyname(hostname)
251 if ipHostStr.startswith('9.4.'):
252  # Search the IP address of the OpenVPN tunnel (FYI: the user-VPN always starts with 10.2.X.X)
253  for itf in ni.interfaces():
254  # DEBUG print("[INFO] Itf=%s " % itf)
255  if itf.startswith('tun'):
256  ip4Str = ni.ifaddresses(itf)[AF_INET][0]['addr']
257  if ip4Str.startswith('10.2.'):
258  ipHostStr = ip4Str
259  break
260  else:
261  ipHostStr = ""
262  if ipHostStr == "":
263  print("[ERROR] Could not find IPv4 address of the tunnel associated with the user-VPN.\n")
264  exit(1)
265 ipHost = int(ipaddress.IPv4Address(ipHostStr))
266 if args.verbose:
267  print("[INFO] Hostname = %s | IP is %s (0x%8.8X) \n" % (hostname, ipHostStr, ipHost))
268 
269 try:
270  udpServerSock.bind((ipHostStr, dpHost))
271  print('[INFO] Binding the socket address of the HOST to {%s, %d}' % (ipHostStr, dpHost))
272 except Exception as exc:
273  print("[EXCEPTION] %s" % exc)
274  exit(1)
275 
276 # STEP-9a: Connect the host client to the remote FPGA server.
277 # Info: Although UDP is connectionless, 'connect()' might still be called. This enables
278 # the OS kernel to set the default destination address for the send, whick makes it
279 # faster to send a message.
280 # -----------------------------------------------------------------------------
281 try:
282  udpClientSock.connect(fpgaServerAssociation)
283 except Exception as exc:
284  print("[EXCEPTION] %s" % exc)
285  exit(1)
286 else:
287  print('[INFO] Successful connection with socket address of FPGA at {%s, %d}' % fpgaServerAssociation)
288 
289 # STEP-9b: Enable the host server to to accept connections from remote FPGA client.
290 # -----------------------------------------------------------------------------
291 print('[INFO] Host server ready to start listening on socket address {%s, %d}' % fpgaClientAssociation)
292 
293 # STEP-10: Setup the test
294 # -------------------------------
295 print("[INFO] Testcase `%s` is run with:" % (os.path.basename(__file__)))
296 seed = args.seed
297 if seed == -1:
298  seed = random.randint(0, 100000)
299 random.seed(seed)
300 print("\t\t seed = %d" % seed)
301 
302 size = args.size
303 if size == -1:
304  size = random.randint(1, 0xFFFF)
305 elif size > 0xFFFF:
306  print('\nERROR: ')
307  print("[ERROR] This test limits the size of the received datagram to %d bytes.\n" % 0xFFFF)
308  exit(1)
309 print("\t\t size = %d" % size)
310 
311 count = args.loop_count
312 print("\t\t loop = %d" % count)
313 
314 # STEP-11: Run the test
315 # -------------------------------
316 print("[INFO] This testcase is sending traffic from FPGA-to-HOST.")
317 print("[INFO] This run is executed in single-threading mode.\n")
318 udp_rx_loop(udpClientSock, udpServerSock, size, ipHost, dpHost, count, args.verbose)
319 
320 # STEP-14: Close sockets
321 # -----------------------
322 time.sleep(2)
323 udpClientSock.close()
324 udpServerSock.close()
325 
326 
327 
def udp_rx_loop(clientSock, serverSock, size, ip_da, udp_dp, count, verbose=False)
Definition: tc_UdpRecv.py:43
def restartApp(instId, ipResMngr, portResMngr, user_name, user_passwd)
Definition: tc_utils.py:174
def getFpgaIpv4(args)
Definition: tc_utils.py:95
def getInstanceId(args)
Definition: tc_utils.py:153
def getResourceManagerIpv4(args)
Definition: tc_utils.py:124
def pingFpga(ipFpga)
Definition: tc_utils.py:199
def getResourceManagerPort(args)
Definition: tc_utils.py:142