Heowyn
Heowyn

Reputation: 131

Windows UDP sockets: recvfrom() fails with error 10054


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:

(*) 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.

Upvotes: 13

Views: 16446

Answers (2)

kuga
kuga

Reputation: 1765

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.

Upvotes: 2

simmerlee
simmerlee

Reputation: 160

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

Upvotes: 15

Related Questions