views:

117

answers:

4

This code is identical to the original udp async echo server, but with a different socket.

The response is transmitted and showing in wireshark, but then an ICMP Port Unreachable error is sent back to the server. I'm trying to understand why because everything looks correct.

You can copy this code directly into a source file e.g. server.cpp. and then compile with

gcc server.cpp -lboost_system

Run it with a command like: ./a.out 35200

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>

using boost::asio::ip::udp;
class server
{
public:
  server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      socket_(io_service, udp::endpoint(udp::v4(), port)),
      socket2_(io_service, udp::endpoint(udp::v4(),0))
  {
    socket_.async_receive_from(
        boost::asio::buffer(data_, max_length), sender_endpoint_,
        boost::bind(&server::handle_receive_from, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_receive_from(const boost::system::error_code& error,
      size_t bytes_recvd)
  {
    if (!error && bytes_recvd > 0)
    {
        // use a different socket... random source port.
        socket2_.async_send_to(
            boost::asio::buffer(data_, bytes_recvd), sender_endpoint_,
            boost::bind(&server::handle_send_to, this,
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      socket_.async_receive_from(
          boost::asio::buffer(data_, max_length), sender_endpoint_,
          boost::bind(&server::handle_receive_from, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
  }

  void handle_send_to(const boost::system::error_code& /*error*/,
      size_t /*bytes_sent*/)
  {
    // error_code shows success when checked here.  But wireshark shows
    // an ICMP response with destination unreachable, port unreachable when run on
    // localhost.  Haven't tried it across a network.

    socket_.async_receive_from(
        boost::asio::buffer(data_, max_length), sender_endpoint_,
        boost::bind(&server::handle_receive_from, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

private:
  boost::asio::io_service& io_service_;
  udp::socket socket_;
  udp::socket socket2_;
  udp::endpoint sender_endpoint_;
  enum { max_length = 1024 };
  char data_[max_length];
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_udp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, atoi(argv[1]));

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

The reason I need this is because I have multiple threads receiving data from an input queue that is fed with a UDP server. Now I want those threads to be able to send responses directly but I can't get it working.

If I use the original socket (i.e. socket_) in the async_send_to call then it works.

Ok... here is the test client that doesn't work with the code above (but works with the original version from the asio examples).

#!/usr/bin/python

import socket, sys, time, struct

textport = "35200"
host = "localhost"

if len(sys.argv) > 1:
    host = sys.argv[1]

print "Sending Data"

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
port = int(textport)
s.connect((host, port))

s.sendall("Hello World")
#s.shutdown(1)

print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    buf = s.recv(1200)
    if not len(buf):
        break
    print "Received: %s" % buf

It's got me baffled. But at least I can use the C++ UDP client and it works.

+3  A: 

You shouldn't pend an asynchronous send and then close the socket. The destructor for socket runs at the end of the block, closing the socket, which prevents the send from ever occurring.

Ben Voigt
Thanks for pointing that out. I have amended the code which still doesn't work. Is ASIO broken?
Matt H
BTW same result if using synchronous send_to call.
Matt H
A: 

Edit

Your python client code looks suspicious, I don't think you should be doing a connect or a send using a UDP socket. Try this:

#!/usr/bin/python

import socket, sys, time, struct

port = 10000
host = "localhost"
addr = (host,port)

if len(sys.argv) > 1:
    host = sys.argv[1]

print "Sending Data"

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.sendto("Hello World",addr)

print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    data,addr = s.recvfrom(1200)
    if not data:
        break
    print "Received: %s" % data

it works for me using your server.cpp

macmini:stackoverflow samm$ ./client.py 
Sending Data
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
Received: Hello World

original answer below.

host unreachable is what I would expect if the client that sent the message does not have the sender_endpoint_ port open. When I compiled your server.cc and use the Boost.Asio blocking udp echo client example, it works just fine

macmini:stackoverflow samm$ g++ server.cpp -lboost_system -o server
macmini:stackoverflow samm$ g++ client.cpp -lboost_system -o client
macmini:stackoverflow samm$ ./server 10000

in another shell

macmini:stackoverflow samm$ ./client 127.0.0.1 10000
Enter message: hello
Reply is: hello
macmini:stackoverflow samm$ ./client 127.0.0.1 10000
Enter message: goodbye
Reply is: goodbye
macmini:stackoverflow samm$ 
Sam Miller
hmmm, thats odd. I tried it with the UDP client also. I think it might be something to do with the python test script I'm using.
Matt H
A: 

Ok, a completely different possibility.

Are you running netfilter? Do you have a conntrack rule?

A reply from the same port would match CONNECTED in the conntrack module, while a reply from a new port would appear to be a new connection. If incoming UDP packets which don't match CONNECTED have a REJECT action, it would explain the behavior, as well as why the exact same code could work for Sam.

Ben Voigt
A: 

Here we go. I'm answering my own question again. The problem relates to my python code which was a sample I grabbed from someone else.

This version works a whole heap better and reads the result correctly. And, is using the correct API sendto recvfrom which is what you would normally use with udp packets.

#!/usr/bin/python

import socket, sys, time, struct

textport = "35200"
host = "localhost"

if len(sys.argv) > 1:
    host = sys.argv[1]

print "Sending Data"

port = int(textport)
addr = (host, port)
buf = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.sendto("hello World", addr)

print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    data,addr = s.recvfrom(buf)
    if not data:
        print "Client has exited!"
        break
    else:
        print "\nReceived: '", data,"'"

# Close socket
s.close()

The other thing is, that the as Ben has pointed out in his answer that at one point I was creating a socket that was later being deleted as the function went out of scope and it still had pending I/O. I have decided that there is little benefit in my case to use asynchronous I/O as it unnecessarily complicates the code and won't affect performance much.

Matt H