4

I have this c executable file that is responsible for processing packets. I am trying to use setsockopt to bind an interface to my socket sockfd

#define MULTICAST

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <net/if.h>

#define TS_PACKET_SIZE 188

long long int usecDiff(struct timeval* time_stop, struct timeval* time_start)
{
    long long int temp = 0;
    long long int utemp = 0;

    if (time_stop && time_start) {
        if (time_stop->tv_usec >= time_start->tv_usec) {
            utemp = time_stop->tv_usec - time_start->tv_usec;    
            temp = time_stop->tv_sec - time_start->tv_sec;
        } else {
            utemp = time_stop->tv_usec + 1000000 - time_start->tv_usec;       
            temp = time_stop->tv_sec - 1 - time_start->tv_sec;
        }
        if (temp >= 0 && utemp >= 0) {
            temp = (temp * 1000000) + utemp;
            } else {
            fprintf(stderr, "start time %ld.%ld is after stop time %ld.%ld\n", time_start->tv_sec, time_start->tv_usec, time_stop->tv_sec, time_stop->tv_usec);
            temp = -1;
        }
    } else {
        fprintf(stderr, "memory is garbaged?\n");
        temp = -1;
    }
        return temp;
}


int main (int argc, char *argv[]) {

    int sockfd;
    int len;
    int sent;
    int transport_fd;
    struct sockaddr_in addr;
    unsigned long int packet_size;   
    char* tsfile;
    unsigned char* send_buf;
    unsigned int bitrate;
    unsigned long long int packet_time;
    unsigned long long int real_time;    
    struct timeval time_start;
    struct timeval time_stop;
    struct timespec nano_sleep_packet;
    char *opt; // "eth0" or "eth1"

    memset(&addr, 0, sizeof(addr));
    memset(&time_start, 0, sizeof(time_start));
    memset(&time_stop, 0, sizeof(time_stop));
    memset(&nano_sleep_packet, 0, sizeof(nano_sleep_packet));

    if(argc < 6 ) {
    fprintf(stderr, "Usage: %s file.ts ipaddr port bitrate [ts_packet_per_ip_packet]\n", argv[0]);
    fprintf(stderr, "ts_packet_per_ip_packet default is 7\n");
    fprintf(stderr, "bit rate refers to transport stream bit rate\n");
    fprintf(stderr, "zero bitrate is 100.000.000 bps\n");
    fprintf(stderr, "This is the modified file\n");
    return 0;
    } else {
    tsfile = argv[1];
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[2]);
    addr.sin_port = htons(atoi(argv[3]));
    bitrate = atoi(argv[4]);
    opt = argv[5];
    if (bitrate <= 0) {
        bitrate = 100000000;
    }
    //To be modified : original arguments are 5 and have to be increased by one
    if (argc >= 7) {
        packet_size = strtoul(argv[5], 0, 0) * TS_PACKET_SIZE;
    } else {
        packet_size = 7 * TS_PACKET_SIZE;
    }
    }

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0) {
    perror("socket(): error ");
    return 0;
    } 
    struct ifreq ifr;

    memset(&ifr, 0, sizeof(ifr));
    //snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), opt); //
    setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, opt, strlen(opt)); // 6 is the size of MAC address in bytes
    ioctl(sockfd, SIOCGIFINDEX, &ifr);
    if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
    perror("Server-setsockopt() error for SO_BINDTODEVICE");
    close(sockfd);
    return 0;
    }

    transport_fd = open(tsfile, O_RDONLY);
    if(transport_fd < 0) {
    fprintf(stderr, "can't open file %s\n", tsfile);
    close(sockfd);
    return 0;
    } 

    int completed = 0;
    send_buf = malloc(packet_size);
    packet_time = 0;
    real_time = 0;

    gettimeofday(&time_start, 0);
    while (!completed) {

        gettimeofday(&time_stop, 0);
        real_time = usecDiff(&time_stop, &time_start);
        if (real_time * bitrate > packet_time * 1000000) { /* theorical bits against sent bits */
        len = read(transport_fd, send_buf, packet_size);
        if(len < 0) {
            fprintf(stderr, "ts file read error \n");
            completed = 1;
        } else if (len == 0) {
            fprintf(stderr, "ts sent done\n");
                completed = 1;
        } else {
            sent = sendto(sockfd, send_buf, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
            if(sent <= 0) {
            perror("send(): error ");
            completed = 1;
            } else {
            packet_time += packet_size * 8;
            }
        }
        } else {
            nanosleep(&nano_sleep_packet, 0);
        }
    }

    close(transport_fd);
    close(sockfd);
    free(send_buf);
    return 0;    
}

I call this file using subprocess.Popen() in a Python script as follows

self.proc.append(subprocess.Popen(["tsudpsend", "/dev/stdin", out_address, str(port), "10000000", "eth0"], stdin=self.proc[-1].stdout, stderr=open("/home/cayman/log.txt", "w")))

When I check the pipe output I found the following error:

Server-setsockopt() error for SO_BINDTODEVICE: Operation not permitted

I spent some time looking into this thread and I saw few suggestions but in vain. Can anyone please guide me how to resolve this. Thanks.

Community
  • 1
  • 1
  • 1
    What is the idea behind this: `printf(ifr.ifr_name, sizeof(ifr.ifr_name), opt);` It looks completely wrong. – alk Dec 05 '16 at 18:32
  • 4
    The question you linked mentions that `SO_BINDTODEVICE` may only be used by root. Are you running as non-root? – dbush Dec 05 '16 at 18:36
  • What's up with calling `setsockopt()` twice, with different arguments? – John Bollinger Dec 05 '16 at 18:38
  • 1
    Also this `... SO_BINDTODEVICE, opt, 6);` probably should be `... SO_BINDTODEVICE, opt, strlen(opt);` You aren't passing in the MAC address but the device name (`"eth0"` or alike). – alk Dec 05 '16 at 18:39
  • @dbush I am not running it in root as there are too many dependencies of the software that are appending directories from the regular user. I don't think I would be able to run the python script calling the executable c file as a root user –  Dec 05 '16 at 18:46
  • @alk I tried also setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, opt, strlen(opt) but no change –  Dec 05 '16 at 18:48
  • You do pass "eth0" or alike on the command line, don't you? Still it clearly seems to be rights related: "*Operation not permitted*" – alk Dec 05 '16 at 18:49
  • @JohnBollinger...I saw many other people doing so...One time for setting options and the other time for error checking. Again, that's what others are doing and I'm trying to follow the convention –  Dec 05 '16 at 18:49
  • @alk that's right...eth0 is passed from the Python script to the c file. That's exactly the requirement I'm trying to implement. I want to let users select an interface from a front-end (eth0 or eth1) and pass it to the packet-processing in the back-end and provide some operations. –  Dec 05 '16 at 18:53
  • Just for debugging: Which error do you get (if any) when running this as root? – alk Dec 05 '16 at 18:57
  • @alk I get an exception related to other dependencies of the software. The software I am developing is assumed to run in a non-root mode. I get this error: OSError: [Errno 2] No such file or directory: '/root/.config/providius/bmg' –  Dec 05 '16 at 19:01
  • Also you want to add error checking and logging to the 1st call to `setsockopt` as well. If this 1st call succeeds, comment out the 2nd. – alk Dec 05 '16 at 19:03

2 Answers2

4

The issue with SO_BINDTODEVICE is that you must be root to use it. If I run your code as root, I do not get that error.

What you need to do instead is get the list of interfaces via getifaddrs and loop through the interface list until the name matches the one you want. Then you pull out the IPv4 address in question and use that to populate a sockaddr_in in a call to bind.

struct sockaddr_in bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr);
bind_addr.sin_family = AF_INET;
bind_addr.sin_port = htons(atoi(argv[3]));

...

struct ifaddrs *ifa, *ifa_tmp;
if (getifaddrs(&ifa) == -1) {
    perror("getifaddrs failed");
    exit(1);
}
ifa_tmp = ifa;
while (ifa_tmp) {
    if (!strcmp(ifa_tmp->ifa_name, opt) && 
            ifa_tmp->ifa_addr && (ifa_tmp->ifa_addr->sa_family == AF_INET)) {
        printf("addr = %s\n", 
            inet_ntoa(((struct sockaddr_in *)ifa_tmp->ifa_addr)->sin_addr);
        bind_addr.sin_addr.s_addr = 
            ((struct sockaddr_in *)ifa_tmp->ifa_addr)->sin_addr.s_addr;
        break;
    }
    ifa_tmp = ifa_tmp->ifa_next;
}
freeifaddrs(ifa);

if (bind_addr.sin_addr.s_addr == 0) {
    printf("interface not found\n");
    exit(1);
}

...

if (bind(sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0) {
    perror("bind failed");
    exit(1);
}

...
dbush
  • 205,898
  • 23
  • 218
  • 273
1

To be precise, you need the CAP_NET_RAW capability.