36

My application is running on CentOS 5.5. I'm using raw socket to send data:

sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sd < 0) {
  // Error
}
const int opt_on = 1;
rc = setsockopt(m_SocketDescriptor, IPPROTO_IP, IP_HDRINCL, &opt_on, sizeof(opt_on));
if (rc < 0) {
  close(sd);
  // Error
}
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = my_ip_address;

if (sendto(m_SocketDescriptor, DataBuffer, (size_t)TotalSize, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0)  {
  close(sd);
  // Error
}

How can I bind this socket to specific network interface (say eth1)?

red0ct
  • 4,840
  • 3
  • 17
  • 44
Dima
  • 1,253
  • 3
  • 21
  • 31
  • 1
    Why would you want to do that? Your program will lose portability unless you are sure your machines will have interfaces named the predefined names. – Andrew Sledge Oct 22 '10 at 16:52
  • 11
    It is for embedded device, the portability is not needed. I have 6 Ethernet ports and I need to send data using specific interface – Dima Oct 22 '10 at 17:47

3 Answers3

43
const char *opt;
opt = "eth0";
const len = strnlen(opt, IFNAMSIZ);
if (len == IFNAMSIZ) {
    fprintf(stderr, "Too long iface name");
    return 1;
}
setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, opt, len);

First line: set up your variable

Second line: tell the program which interface to bind to

Lines 3-5: get length of interface name and check if it's size not too big.

Six line: set the socket options for socket sd, binding to the device opt.

setsockopt prototype:

int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

Also, make sure you include the if.h, socket.h and string.h header files

Ajith S
  • 2,907
  • 1
  • 18
  • 30
Andrew Sledge
  • 10,163
  • 2
  • 29
  • 30
  • 4
    Thanks, it working, but with one small modification: ifreq Interface; memset(&Interface, 0, sizeof(Interface)); strncpy(Interface.ifr_ifrn.ifrn_name, "eth1", IFNAMSIZ); if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &Interface, sizeof(Interface)) < 0) { close(sd); // Error } – Dima Oct 22 '10 at 20:14
  • SO_BINDTODEVICE only works if you run as root, right? (on Linux at least) – sep332 Nov 27 '12 at 21:29
  • Strange it seems to be requiring ifreq structure, when socket(7) that the passed option is variable-length Zstring... – Pawel Veselov Jan 14 '13 at 07:53
  • 1
    The first element of "struct ifreq" is the char[] name of the interface. You also still specify IFNAMSIZ. So this should be identical (just as appending any random buffer to "eth0" should work) – gsk May 23 '16 at 20:56
  • 3
    According to [this other SO answer](https://stackoverflow.com/a/9921602/919747), and my experience on CentOS 7, this does not work for a raw socket. Instead, you need to use `bind()`, not the `setsockopt()` approach shown here. – Jason R Feb 08 '19 at 16:20
  • 1
    @JasonR: Your comment just cut short hours of frustration for myself and my coworker. (We are finally revving from CentOS 6 to 7 and have been banging our heads over why some legacy code isn't working.) Thank you! – Nemo Mar 01 '19 at 18:09
22

As mentioned earlier, the correct thing to do is use the struct ifreq to specify the interface name. Here is my code sample.

#define SERVERPORT 5555
...
struct ifreq ifr;


/* Create the socket */
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) 
{
    printf("Error in socket() creation - %s", strerror(errno));
}

/* Bind to eth1 interface only - this is a private VLAN */
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth1");
if ((rc = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr))) < 0)
{
    perror("Server-setsockopt() error for SO_BINDTODEVICE");
    printf("%s\n", strerror(errno));
    close(sd);
    exit(-1);
}

/* bind to an address */
memset(&serveraddr, 0x00, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVERPORT);
serveraddr.sin_addr.s_addr = inet_addr("9.1.2.3");

int rc = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

I would also like to add that from a security perspective, while it is good to bind the socket to an interface, it does not make sense to use INADDR_ANY as the listening IP address. Doing so would make the port appear open in netstat on all network interfaces.

Proto Recv-Q Send-Q Local Address    Foreign Address    State     User Inode      PID/Program name
tcp   0      0      0.0.0.0:5555     0.0.0.0:*          LISTEN    0    210898     26996/myserver  

Instead, I specified an IP address specific to the interface being used (a private VLAN). This fixed the netstat output too:

Proto Recv-Q Send-Q Local Address    Foreign Address    State     User Inode      PID/Program name
tcp   0      0      9.1.2.3:5555     0.0.0.0:*          LISTEN    0    210898     26996/myserver  
viv
  • 353
  • 6
  • 10
  • 1
    Where is it specified that you use `struct ifreq` ? My man page says "the passed option is a variable-length null-terminated interface name string" – Bryan Feb 18 '16 at 14:15
  • 4
    The problem you suggest (INADDR_ANY for SOCK_STREAM sockets) doesn't occur for the original poster, who was using SOCK_RAW. – Jon Watte May 01 '16 at 17:49
  • @Bryan Both methods should work, at the very least on Linux. According to netdevice(7), the `ifr_name` string is the first member of `struct ifreq`, and therefore a pointer to `struct ifreq` or `char` work the same. – Will Eccles Dec 18 '20 at 16:22
  • This is really the correct answer... – 71GA Jan 16 '23 at 14:27
2

Bind socket to specific interface IP address

int bind_using_iface_ip(int fd, char *ipaddr, uint16_t port)
{
    struct sockaddr_in localaddr = {0};
    localaddr.sin_family    = AF_INET;
    localaddr.sin_port  = htons(port);
    localaddr.sin_addr.s_addr = inet_addr(ipaddr);
    return bind(fd, (struct sockaddr*) &localaddr, sizeof(struct sockaddr_in));
}

Bind socket to specific interface name

int bind_using_iface_name(int fd, char *iface_name)
{
    return setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface_name, strlen(iface_name))
}

In bind_using_iface_ip, to bind to any port 0 should be passed. And also if the fd is raw socket then need to pass port as 0. This bind mechanism is common for all kind of sockets like raw, dgram and stream.

rashok
  • 12,790
  • 16
  • 88
  • 100