The following scenario fails:
Attached is sample app that recreates this problem (rename it to .cpp)
udp_socket_example.txt
Should be compiled with following command:
"mbed compile -t GCC_ARM -m K64F --source ."
@trianglee
@sg-
@geky
Hi Jenia, thanks for reporting this issue.
The NSAPI_ERROR_PARAMETER is actually the expected behavior. The recvfrom function modifies the SocketAddress in place, if a recvfrom call fails the SocketAddress is set to null (represented as “0.0.0.0”). The following sendto call fails with NSAPI_ERROR_PARAMETER because the address “0.0.0.0” is an invalid IP address.
The address argument to the recvfrom function is useful for UDP servers, but usually unnecessary for applications where the server address is known beforehand. In this case, a NULL address can be used instead since it is safely ignored:
//some message is sent on UDP socket
sentBytes = _socket->sendto(*_socketAddress, messageOut, messageSize);
// message is received on UDP socket
recievedBytes = _socket->recvfrom(NULL, messageIn, messageBufferSize);
if (recievedBytes == NSAPI_ERROR_WOULD_BLOCK){
//some message is sent on UDP socket
sentBytes = _socket->sendto(*_socketAddress, messageOut, messageSize);
}
Let me know if you find an issue with this.
@jenia81 I think I have found the culprit here. There is nothing wrong with APIs. I itested with the code provided by you with a simple python UDP server running on my home machine. The only issue I found was that the ARM machine (office laptop) firewall was blocking my traffic. So I ran the UDP server on my personal machine and made sure that the socket was opened.
I would also suggest you to use some other port than 5683 (which is a coap port). Use something like 6005. Also, for this test call sendto() first and then call recvfrom() so as to get a response from our dummy server. I had added some code to enable traces , you can remove it if you wish. I tried with both removing the CLIENT from mbed_app.json and keeping it, reponse is just the same.
So I am pretty sure that the issue is with your setup rather than mbed-os/client or any other networking APIs.
How to reproduce what I did:
1) A dummy UDP server
from socket import *
address('0.0.0.0', 6005)
server_socket = socket(AF_INET, SOCK_DGRAM)
server_socket.bind(address)
while(1):
print "Listening"
recv_data, addr = server_socket.recvfrom(2048)
print recv_data
if recv_data == "PING"
print "Responding"
server_socket.sendto("Got it", addr)
2) Your client code
/******YOUR CODE******/
#include "mbed.h"
#include "rtos.h"
#include "EthernetInterface.h"
#include <UDPSocket.h>
#include "SocketAddress.h"
EthernetInterface gEth;
#define SERVER_ADDRESS "192.168.1.100" //My machine's address
#define SERVER_PORT 6005
#include "mbed-trace/mbed_trace.h"
Serial output(USBTX, USBRX);
void trace_printer(const char* str) {
printf("%s", str);
}
static bool DeviceIpAddrGet()
{
int retries = 5;
while (retries--) {
const char *ip = gEth.get_ip_address();
if (ip != NULL) {
printf("Device IP address: %s\r\n", ip);
return true;
}
else {
wait(0.2);
}
}
printf("Failed retrieving device IP address\r\n");
return false; /* failed to retrieve a valid IP address */
}
int main(int argc, char *argv[])
{
mbed_trace_init();
mbed_trace_print_function_set(trace_printer);
output.baud(115200);
char messageOut[] = "PING";
char messageIn[100];
int rc = gEth.connect();
if (rc != 0) {
printf("Failed connecting to ethernet interface!\r\n");
return EXIT_FAILURE;
}
// Get device IP address - this should yield a factory IP address
DeviceIpAddrGet();
UDPSocket *_socket;
SocketAddress *_socketAddress;
int sentBytes;
int recievedBytes;
_socketAddress = new SocketAddress((NetworkInterface*)&gEth, SERVER_ADDRESS, SERVER_PORT);
_socket = new UDPSocket((NetworkInterface*)&gEth);
_socket->set_blocking(true);
_socket->set_timeout(3000);
sentBytes = _socket->sendto(*_socketAddress, messageOut, strlen(messageOut));
if (sentBytes == NSAPI_ERROR_WOULD_BLOCK) {
printf("Timeout error %i \r\n", sentBytes);
}
else if (sentBytes < 0) {
printf("Failed sending data to the socket %i \r\n", sentBytes);
}
else if ((unsigned)sentBytes != strlen(messageOut)) {
printf("Error data size was send , packet size %i, sent bytes messageSize %i \r\n", strlen(messageOut), sentBytes);
}
recievedBytes = _socket->recvfrom(_socketAddress, messageIn, sizeof(messageIn));
if (recievedBytes == NSAPI_ERROR_WOULD_BLOCK){
printf("Failed to receive data from the socket %i \r\n", recievedBytes); //should be warn level
}
else if (recievedBytes < 0) {
printf("Failed to receive data from the socket %i \r\n", recievedBytes);
}
return 0;
}
@geky
Using NULL parameter in recvfrom() function indeed solved the problem.
By the way, the same code worked with previous mbed-os I worked with (with socketAddress parameter).
I have another question though, I want to check that the received packet was sent from the address that I expect (the one that I send data to).
Is there any API you expose that can tell me from which IP the received packed had arrived?
@hasnainvirk the issue was that if recvfrom() fails because there is no data received (the error would be then NSAPI_ERROR_WOULD_BLOCK), then next sendto fails too.
Glad it worked for you.
Unfortunately, in previous versions of mbed-os the state of the address after a failed recvfrom was undefined. Most network interfaces didn’t bother to modify the address until an actual packet had arrived, so the code would usually work. Imagine what would happen if I sent a random packet to the port you happened to be listening on, the original address would be lost. The recent changes made the behavior of a failed recvfrom more consistent.
As for checking that checking that the received packet is from the correct source, your best bet is to create a temporary SocketAddress:
//some message is sent on UDP socket
sentBytes = _socket->sendto(*_socketAddress, messageOut, messageSize);
// message is received on UDP socket
SocketAddress sourceAddress;
recievedBytes = _socket->recvfrom(&sourceAddress, messageIn, messageBufferSize);
if (recievedBytes >= 0) {
// successfully received a message
if (strcmp(_socketAddress.get_ip_address(), &sourceAddress.get_ip_address()) == 0) {
// _socketAddress and sourceAddress are the same
sentBytes = _socket->sendto(*_socketAddress, messageOut, messageSize);
}
}
You were right that the recvfrom address parameter is how you get the packet’s source address, however there needs to be a temporary to avoid overwriting the _socketAddress.
@jenia81 Yup I understood that. That's why if you notice, I changed the pattern of calls in your code and mentioned it in the comment that this is an example of showing you that it works.
@jenia81 Can you please close this issue then if you are happy with the discussion ?
@geky I've added the code you suggested and it works now.
Closing the issue.
Thanks!