10
votes


Hello everyone.
I'm trying to use Windows sockets to send and receive UDP packets (in C++).
It worked well until three days ago, when the program stopped behaving properly.
To summarize the situation:

  • When calling WSAPoll() on my socket, it always returns my socket updated with EVERY revents possible (corresponding to every events I gave the pollfd), even if there is no server launched.
  • When calling recvfrom() and no server is launched, it returns SOCKET_ERROR with error code 10054(*).
  • When calling recvfrom() and a server is launched, it works properly - blocks until it receives something.
  • The behavior is the same whether I try to connect to localhost or to a distant host.

(*) I investigated this error. In UDP, it means that there is an ICMP problem. ("On a UDP-datagram socket this error indicates a previous send operation resulted in an ICMP Port Unreachable message.").
I indeed call sendto() before recvfrom(), so the problem's not here.
I tried to put down my firewall to see if it changed anything, but it didn't. I also tried to put down every network flowing through my PC. In this state I managed to get the program to work for a few minutes, but when I enabled the networks it stopped working again. I tried to repeat the process but it would not work anymore.
I tried compiling with both visual studio (2015) and MinGW.
I tried on another computer too (under Windows 7, mine has Windows 8.1), to no avail.

Here is a simple test file which does not work on my computer.

#undef _WIN32_WINNT
#define _WIN32_WINNT 0x501
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <vector>
#include <iostream>

int main() {
  int clientSock;
  char buf[100];
  int serverPort;

  /* Initializing WSA */
  WSADATA wsaData;
  WSAStartup(MAKEWORD(2, 2), &wsaData);

  /* I create my socket */
  struct addrinfo specs;
  struct addrinfo *addr = new addrinfo;
  ZeroMemory(&specs, sizeof(specs));
  specs.ai_family = AF_INET;
  specs.ai_socktype = SOCK_DGRAM;
  specs.ai_flags = 0;
  getaddrinfo("127.0.0.1", "2324", &specs, &addr);

  clientSock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);

  /* I get the server's address */
  struct sockaddr_in serverAddr;
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  serverAddr.sin_port = htons(2324);
  int len = sizeof(struct sockaddr);

  /* I'll poll & recvfrom on my socket */
  std::vector<pollfd> fds;
  pollfd fd;
  fd.fd = clientSock;
  fd.events = POLLRDNORM;
  fd.revents = -1;
  fds.push_back(fd);

  while(1) {
    memset(buf,0,sizeof(buf));
    printf("\nClient--->: ");
    gets(buf);
    /* It's UDP, so it doesn't matter if there is someone to receive the packet */
    sendto(clientSock, buf, strlen(buf), 0, (sockaddr*)&serverAddr ,len);

    memset(buf,0,sizeof(buf));
    int ret;
    /* Always returns "1" */
    if ((ret = WSAPoll(fds.data(), 1, 0)) > 0) {
      std::cout << ret;
      /* Always returns "-1" */
      std::cout << recvfrom(clientSock,buf,sizeof(buf),0, (sockaddr*)&serverAddr,&len) << std::endl;
      printf("\n--->From the server: ");
      printf("%s",buf);
    }
  }

  closesocket(clientSock);
  WSACleanup();

  return 0;
}

Two questions:

  1. Why does WSAPoll() always returns an updated socket, even if there wasn't any interaction with it ?
  2. Why does recvfrom() return this error and how can I fix it ? I suppose it comes from my computer. I tried allowing ICMP through my firewall but it didn't change anything, maybe I did something wrong ?

Edit: I fixed my main program (not shown here because it is way too large) by just ignoring any "error 10054" I received. Now it works the same way it does on Unix.
Still, it is not really a solution (ignoring an error code... meh) and if anyone knows why I get the "ICMP Port Unreachable" error when calling sendto(), I'd be glad to hear about it.

2
"I indeed call sendto() before recvfrom(), so the problem's not there" doesn't make sense. You called sendto(), an ICMP UNREACH was return, and you detected it in the following recvfrom().user207421
Yes but it could have been a bind problem, as if I do not call sendto() before there won't be any implicit bind (you talked about it in the other comment). It is the reason I precised this.Heowyn
But you did call sendto(). You said so. And even if you hadn't, calling recvfrom() would also have done an implicit bind. Unclear what on earth you're talking about here.user207421
Well, when searching for answers to my problem, I found a topic where people said I HAD to call sendto() before recvfrom() or it would not work. I did not know recvfrom() did an implicit bind too. My bad.Heowyn
It is perfectly reasonable to ignore any UDP errors received since they're not reliable anyway and it's often close to impossible to figure out what they mean specifically. If you aren't getting data you should be getting, you have a problem. Otherwise, it's all good.David Schwartz

2 Answers

5
votes

In Windows, if host A use UDP socket and call sendto() to send something to host B, but B doesn't bind any port so that B doesn't receive the message, and then host A call recvfrom() to receive some message, recvfrom() will failed, and WSAGetLastError() will return 10054.

It's a bug of Windows. If UDP socket recv a ICMP(port unreachable) message after send a message, this error will be stored, and next time call recvfrom() will return this error.

There are 2 ways to solve this problem:

  1. Make sure host B has already bound the port you want to send to.
  2. Disable this error by using following code:
#include <Winsock2.h>
#include <Mstcpip.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)

BOOL bNewBehavior = FALSE;
DWORD dwBytesReturned = 0;
WSAIoctl(iSock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL);

Reference: http://www.cnblogs.com/cnpirate/p/4059137.html

0
votes

I have stripped down the Authors code and included the fix of simmerlee. This provides an simpler way to reproduce the error:

#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)

void testCase(bool fixed)
{
    int clientSock;
    char rcvBuf[100];

    // create socket
    clientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if(fixed)
    {
        BOOL bNewBehavior = FALSE;
        DWORD dwBytesReturned = 0;
        WSAIoctl(clientSock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL);
    }

    // bind socket
    struct sockaddr_in clientAddr;
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientAddr.sin_port = htons(61234);
    int sizeClientAddr = sizeof(clientAddr);
    bind(clientSock, (sockaddr*) &clientAddr, sizeClientAddr);

    struct sockaddr_in serverAddr = clientAddr;
    serverAddr.sin_port = htons(2324); // change port where nobody listens
    int sizeServerAddr = sizeof(struct sockaddr);

    int lasterror = 0;
    int status = 0;

    // send where nobody is listening
    printf("Send to nowhere--->:\n");
    /* It's UDP, so it doesn't matter if there is someone to receive the packet */
    status =sendto(clientSock, "Message", 7, 0, (sockaddr*)&serverAddr, sizeServerAddr);
    lasterror = WSAGetLastError(); 
    printf("sendto return %d (lasterror %d)\n", status, lasterror);

    // recvfrom with "failing" sendto before. 
    // fixed: This should block.
    // unfixed: WSAGetLastError is 10054
    memset(rcvBuf, 0, sizeof(rcvBuf));
    status = recvfrom(clientSock, rcvBuf, sizeof(rcvBuf), 0, (sockaddr*)&serverAddr, &sizeServerAddr);
    lasterror = WSAGetLastError(); 
    printf("recvfrom return %d (lasterror %d)\n", status, lasterror);
    printf("--->From the server: -%s-\n", rcvBuf);

    closesocket(clientSock);
}

int _tmain(int argc, _TCHAR* argv[])
{
    /* Initializing WSA */
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    printf("##### UNFIXED\n");
    testCase(false);
    printf("##### FIXED\n");
    testCase(true);

    WSACleanup();

    // pause
    char buf[100];
    gets(buf);
    return 0;
}

This should return:

##### UNFIXED
Send to nowhere--->:
sendto return 7 (lasterror 0)
recvfrom return -1 (lasterror 10054)
--->From the server: --
##### FIXED
Send to nowhere--->:
sendto return 7 (lasterror 0)

and then block.