Gene Vincent Gene Vincent - 3 years ago 253
C++ Question

Invalid argument error for sendmsg() on some FreeBSD systems

I have code to send a UDP packet from a specific source IP (see below).
This worky nicely on all system I tried so far, including FreeBSD.

Unfortunately on a client system sendmsg() fails with "invalid argument" error and I'm unable to figure out why.

The FreeBSD versions are the same, tests on all system use the same kind of IPv4 addresses for source and destination.

I did a ktrace, but only shows part of the paramers used (the sockaddr_in6), but those seem fine. Valgrind also didn't complain (on my system).

How do I find this ? Is there a tool that displays the full msghdr struct for sendmsg() calls ?

Update: Please focus on the tools or techniques I could use. You can look at the code snippet, but it won't compile without the surounding code.

ssize_t UDPSendWithSourceIP(int fd, void * data, size_t len, const sockaddr_in6 & toAddress)
{
struct sockaddr_in6 dest = toAddress;

// set source address
PIPSocket::Address src = RasServer::Instance()->GetLocalAddress(toIP);

struct msghdr msgh = { };
struct cmsghdr *cmsg;
struct iovec iov = { };
char cbuf[256];
memset(&cbuf, 0, sizeof(cbuf));

// Set up iov and msgh structures
memset(&msgh, 0, sizeof(struct msghdr));
iov.iov_base = data;
iov.iov_len = len;
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
msgh.msg_name = (struct sockaddr*)&dest;
// must pass short len when sending to IPv4 address on Solaris 11, OpenBSD and NetBSD
// sizeof(dest) is OK on Linux and FreeBSD
size_t addr_len = sizeof(sockaddr_in);
if (toIP.GetVersion() == 6)
addr_len = sizeof(sockaddr_in6);
msgh.msg_namelen = addr_len;

if ((((struct sockaddr*)&dest)->sa_family == AF_INET6)) {
struct in6_pktinfo *pkt;

msgh.msg_control = cbuf;
msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt));

cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt));

pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg);
memset(pkt, 0, sizeof(*pkt));
pkt->ipi6_addr = src;
msgh.msg_controllen = cmsg->cmsg_len;
} else
{
#ifdef IP_SENDSRCADDR // FreeBSD
struct in_addr *in;

msgh.msg_control = cbuf;
msgh.msg_controllen = CMSG_SPACE(sizeof(*in));

cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_SENDSRCADDR;
cmsg->cmsg_len = CMSG_LEN(sizeof(*in));

in = (struct in_addr *) CMSG_DATA(cmsg);
*in = src;
#endif // IP_SENDSRCADDR
}

ssize_t bytesSent = sendmsg(fd, &msgh, 0);
if (bytesSent < 0) {
cerr << "RTP\tSend error " << strerror(errno) << endl;
}

return bytesSent;
}

Answer Source

It turns out FreeBSD is very picky when it allows the use of IP_SENDSRCADDR on a UDP socket. If the socket is bound to INADDR_ANY my code works fine. If the socket is bound to a single IP, then sendmsg() returns EINVAL (invalid argument).

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download