Files
s390-tools/ip_watcher/xcec-bridge.c
Jan Höppner c675096899 ip_watcher: Copy device name correctly
DEV_NAME_SIZE is defined as IFNAMSIZ. IFNAMSIZ is the maximum buffer size
for an interface name, including its terminating null byte. [1] The
buffers dev_name and ifr_name are both defined with a size of
DEV_NAME_SIZE and IFNAMSIZ respectively.
Given these facts, only a size of 'IFNAMSIZ - 1' should be copied and
the destination string should then be null-terminated properly.

Use util_strlcpy() to correctly copy the strings and rid of the followin
GCC8 compile warnings:

xcec-bridge.c: In function ‘open_incoming_socket’:
xcec-bridge.c:94:2: warning: ‘strncpy’ specified bound 16 equals
destination size [-Wstringop-tru ncation]
..strncpy(if_req.ifr_name,dev_name,DEV_NAME_LEN);
..^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
xcec-bridge.c: In function ‘read_sys’:
xcec-bridge.c:284:4: warning: ‘strncpy’ output may be truncated copying
16 bytes from a string of length 255 [-Wstringop-truncation]
....strncpy(is->dev_name, if_name, DEV_NAME_LEN);
....^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[1]: https://www.gnu.org/software/libc/manual/html_node/Interface-Naming.html

Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-11-16 15:01:42 +01:00

588 lines
14 KiB
C

/*
* xcec-bridge - HiperSockets Network Concentrator
*
* Parameters:
* also_unicast - uni-, multi-, broadcast is bridged
* <no parameters> - multi-, broadcast is bridged
* only broadcast - only broadcast is bridged
*
* Copyright IBM Corp. 2003, 2017
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <dirent.h>
#include <errno.h>
#include <features.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
#include "lib/util_libc.h"
#include "lib/zt_common.h"
/* a signal causes the interfaces to be re-checked */
#define LOGGING_FACILITY LOG_LOCAL0
#define UPDATE_SIGNAL SIGUSR1
#define DEV_NAME_SIZE IFNAMSIZ
#define BUFFER_LEN 65536
int so_sndbuf=(8*1024*1024);
int do_unicast_bridging=0;
int do_multicast_bridging=1;
int do_broadcast_bridging=0;
struct int_sock {
#define I_S_FEATURE_PASSTHROUGH 0x01
int features;
int i_fd;
int o_fd;
char dev_name[DEV_NAME_SIZE];
int mtu_warning;
struct int_sock *next;
};
fd_set work_fd_set; /* used in and changed by select */
struct set {
fd_set fds;
int highest_fd;
struct int_sock *i_s_list;
};
struct set select_set;
volatile int update_interface_trigger=0;
int open_incoming_socket(char *dev_name)
{
int fd,retval;
struct sockaddr_ll sock_addr;
struct ifreq if_req;
struct packet_mreq mc_req;
/* we want to receive everything. we filter out stuff that we
* don't forward by ourselves */
fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
if (fd==-1) {
syslog(LOG_ERR,"can't open raw packet socket, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
return -1;
}
util_strlcpy(if_req.ifr_name, dev_name, DEV_NAME_SIZE);
retval=ioctl(fd,SIOCGIFINDEX,&if_req);
if (retval==-1) {
syslog(LOG_ERR,"can't ioctl on raw packet socket, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
sock_addr.sll_protocol=htons(ETH_P_ALL);
sock_addr.sll_ifindex=if_req.ifr_ifindex;
sock_addr.sll_family=AF_PACKET;
retval=bind(fd,(struct sockaddr *)&sock_addr,
sizeof(struct sockaddr_ll));
if (retval==-1) {
syslog(LOG_ERR,"can't bind packet raw packet socket to " \
"interface %s -- it will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
mc_req.mr_ifindex=if_req.ifr_ifindex;
mc_req.mr_type=PACKET_MR_ALLMULTI;
mc_req.mr_alen=0;
retval=setsockopt(fd,SOL_SOCKET,PACKET_ADD_MEMBERSHIP,
&mc_req,sizeof(struct packet_mreq));
if (retval==-1) {
syslog(LOG_ERR,"can't set socket options to join all " \
"multicast groups -- multicast may not be " \
"forwarded from %s: %s",dev_name,strerror(errno));
}
return fd;
}
int open_outgoing_socket(char *dev_name)
{
int fd,retval;
int val;
fd=socket(PF_INET,SOCK_RAW,IPPROTO_RAW);
if (fd==-1) {
syslog(LOG_ERR,"can't open raw inet socket, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
return -1;
}
/* IP_HDRINCL should be set by the stack already, we'll set
* it nevertheless */
val=1;
retval=setsockopt(fd,SOL_IP,IP_HDRINCL,&val,sizeof(int));
if (retval==-1) {
syslog(LOG_ERR,"can't set IP_HDRINCL on raw inet socket, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
/* we bind the socket to the device */
retval=setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,
dev_name,strlen(dev_name)+1);
if (retval==-1) {
syslog(LOG_ERR,"can't bind raw inet socket to device, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
/* get max socket buffer */
retval=setsockopt(fd,SOL_SOCKET,SO_SNDBUF,&so_sndbuf,sizeof(int));
if (retval==-1) {
syslog(LOG_ERR,"can't set socket buffer size, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
/* and enable broadcast on the socket */
val=1;
retval=setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(int));
if (retval==-1) {
syslog(LOG_ERR,"can't enable broadcast on raw inet socket, " \
"interface %s will not be used: %s",
dev_name,strerror(errno));
close(fd);
return -1;
}
return fd;
}
int interface_in_list(struct int_sock *item,struct int_sock *list)
{
for (;list;list=list->next) {
if (!strncmp(item->dev_name,list->dev_name,DEV_NAME_SIZE)) {
return 1;
}
}
return 0;
}
int read_sys(struct int_sock **nlist)
{
DIR *qdir;
FILE *qfile;
struct dirent *qde;
char fname[256];
char *tmp;
char if_name[256];
char rtr[256];
struct int_sock *is = NULL;
int i;
qdir = opendir("/sys/devices/qeth");
if (!qdir) {
syslog(LOG_ERR,"failed to open directory /sys/devices/qeth");
return errno;
}
while ((qde = readdir(qdir))) {
if ((qde->d_type == DT_DIR) && (qde->d_name[0] != '.')) {
strcpy(fname, "/sys/devices/qeth/");
strcat(fname, qde->d_name);
strcat(fname, "/if_name");
qfile = fopen(fname, "r");
if (!qfile) {
continue;
}
tmp = if_name;
while ((i = fgetc(qfile)) != EOF) {
if ((char)i == '\n') {
*tmp = 0;
break;
} else {
*tmp = (char)i;
}
tmp++;
}
*tmp = 0;
fclose(qfile);
strcpy(fname, "/sys/devices/qeth/");
strcat(fname, qde->d_name);
strcat(fname, "/route4");
qfile = fopen(fname, "r");
if (!qfile) {
continue;
}
tmp = rtr;
while ((i = fgetc(qfile)) != EOF) {
if ((char)i == '\n') {
*tmp = 0;
break;
} else {
*tmp = (char)i;
}
tmp++;
}
*tmp = 0;
fclose(qfile);
if (!strstr(rtr, "multicast") &&
!strstr(rtr, "connector"))
continue;
is = malloc(sizeof(struct int_sock));
if (!is) {
syslog(LOG_ERR,"no memory while reading from "\
"/sys/devices/qeth some interface"\
"might not be used");
continue;
}
/* as soon as we have one interface echoeing back
* broadcasts to us, we don't bridge broadcast
* traffic
*/
if (!(strstr(rtr, "connector+") ||
strstr(rtr, "multicast router+")))
do_broadcast_bridging=0;
is->mtu_warning=0;
util_strlcpy(is->dev_name, if_name, DEV_NAME_SIZE);
if (!strncmp(if_name,"hsi",3)) {
is->features=I_S_FEATURE_PASSTHROUGH;
}
is->next = *nlist;
*nlist = is;
}
}
closedir(qdir);
return 0;
}
void update_interfaces()
{
struct int_sock *new_list=NULL;
struct int_sock *i=NULL,*j,*prev;
struct int_sock *new_int=NULL;
int i_fd,o_fd;
/* if all interfaces are '+'-interfaces, we bridge broadcast */
do_broadcast_bridging=1;
update_interface_trigger=0;
syslog(LOG_DEBUG,"rechecking interfaces");
if (read_sys(&new_list))
return;
for (i=select_set.i_s_list;i;i=i->next) {
if (!interface_in_list(i,new_list)) {
/* remove interface i */
j=select_set.i_s_list;
prev=NULL;
while (j) {
if (!strncmp(j->dev_name,i->dev_name,
DEV_NAME_SIZE)) {
if (!prev) {
select_set.i_s_list=j->next;
} else {
prev->next=j->next;
}
prev=j;
j=j->next;
free(j);
} else {
j=j->next;
}
}
/* and close the socket */
close(i->i_fd);
close(i->o_fd);
syslog(LOG_INFO,"removed interface %s",i->dev_name);
}
}
for (i=new_list;i;i=i->next) {
if (!interface_in_list(i,select_set.i_s_list)) {
/* add interface i */
new_int=malloc(sizeof(struct int_sock));
if (!new_int) {
syslog(LOG_ERR,"can't add interface %s -- " \
"no memory",i->dev_name);
continue;
}
i_fd=open_incoming_socket(i->dev_name);
if (i_fd==-1) {
free(new_int);
continue;
}
o_fd=open_outgoing_socket(i->dev_name);
if (o_fd==-1) {
close(i_fd);
free(new_int);
continue;
}
util_strlcpy(new_int->dev_name, i->dev_name, DEV_NAME_SIZE);
new_int->i_fd=i_fd;
new_int->o_fd=o_fd;
new_int->features=i->features;
new_int->next=select_set.i_s_list;
select_set.i_s_list=new_int;
syslog(LOG_INFO,"added interface %s",i->dev_name);
}
}
/* kill temporary new_list */
while (new_list) {
i=new_list->next;
free(new_list);
new_list=i;
}
/* prepare the fd_set for select */
FD_ZERO(&select_set.fds);
for (i=select_set.i_s_list;i;i=i->next) {
FD_SET(i->i_fd,&select_set.fds);
select_set.highest_fd=(i->i_fd>select_set.highest_fd)?
i->i_fd:select_set.highest_fd;
}
}
void process_packet(struct int_sock *i_s)
{
int retval;
char buffer[BUFFER_LEN];
int buffer_len;
struct int_sock *i_s_item;
struct sockaddr_ll s_ll;
struct sockaddr_in s_in;
socklen_t sll_len;
sll_len=(socklen_t)sizeof(struct sockaddr_ll);
buffer_len=recvfrom(i_s->i_fd,buffer,BUFFER_LEN,0,
(struct sockaddr *)&s_ll,&sll_len);
if (buffer_len==-1) {
syslog(LOG_WARNING,"recvfrom failed on %s: %s\n",
i_s->dev_name,strerror(errno));
return;
}
/* nothing read */
if (buffer_len==0)
return;
/* no packets that came from our own stack... that could lead to
* traffic loops */
if (s_ll.sll_pkttype==PACKET_OUTGOING)
return;
/* only do unicast bridging when required */
if ( (s_ll.sll_pkttype==PACKET_HOST) &&
(!do_unicast_bridging) )
return;
/* only do multicast bridging when required */
if ( (s_ll.sll_pkttype==PACKET_MULTICAST) &&
(!do_multicast_bridging) )
return;
/* broadcast is critical, see comment above */
if (!do_broadcast_bridging) {
if (s_ll.sll_pkttype==PACKET_BROADCAST)
return;
}
/* only do v4 at this time */
if (s_ll.sll_protocol!=ETH_P_IP)
return;
/* forward buffer to each interface ... */
for (i_s_item=select_set.i_s_list;i_s_item;i_s_item=i_s_item->next) {
/* ... but i_s */
if (i_s_item==i_s) continue;
s_ll.sll_ifindex=0;
s_in.sin_family=AF_INET;
s_in.sin_port=0;
if (s_ll.sll_pkttype==PACKET_BROADCAST) {
s_in.sin_addr.s_addr=INADDR_BROADCAST;
} else {
memcpy(&s_in.sin_addr, &buffer[16 + ETH_HLEN], 4);
}
retval=sendto(i_s_item->o_fd, buffer + ETH_HLEN,
buffer_len - ETH_HLEN, 0,
(struct sockaddr *)&s_in,
sizeof(struct sockaddr_in));
if (retval==-1) {
if ( (errno==EMSGSIZE) && (!i_s_item->mtu_warning) ) {
syslog(LOG_WARNING,"MTU of %s too small " \
"to forward packet with size of %i" \
" -- won't show warning again.",
i_s_item->dev_name,buffer_len);
i_s_item->mtu_warning=1;
} else {
syslog(LOG_WARNING,"sendto failed on %s: " \
"%s\n",i_s_item->dev_name,
strerror(errno));
}
} else if (retval != (buffer_len - ETH_HLEN)) {
syslog(LOG_WARNING,"sendto sent only %i instead " \
"of %i bytes on %s\n",
retval,buffer_len,i_s->dev_name);
}
}
}
void action_handler(int UNUSED(s))
{
update_interface_trigger=1;
syslog(LOG_DEBUG,"signal caught");
/* select will return, interfaces will be re-checked */
}
int main(int argc,char *argv[]) {
struct int_sock *i_s;
int retval,r;
struct sigaction s_a;
if ( (argc>1) && (!strncmp(argv[1],"also_unicast",12)) ) {
do_unicast_bridging=1;
} else
if ( (argc>1) && (!strncmp(argv[1],"only_broadcast",14)) ) {
do_multicast_bridging=0;
}
openlog("xcec-bridge",LOG_NDELAY,LOGGING_FACILITY);
FD_ZERO(&select_set.fds);
select_set.i_s_list=NULL;
select_set.highest_fd=0;
s_a.sa_handler=action_handler;
if (sigemptyset(&s_a.sa_mask)) {
syslog(LOG_ERR,"problem in sigemptyset: %s -- exiting",
strerror(errno));
return 1;
}
s_a.sa_flags=0;
retval=sigaction(UPDATE_SIGNAL,&s_a,NULL);
if (sigemptyset(&s_a.sa_mask)) {
syslog(LOG_ERR,"problem in sigemptyset: %s -- exiting",
strerror(errno));
return 1;
}
if (sigaddset(&s_a.sa_mask,UPDATE_SIGNAL)) {
syslog(LOG_ERR,"problem in sigaddset: %s -- exiting %s",
argv[0],strerror(errno));
return 1;
}
r=sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL);
if (r) {
syslog(LOG_ERR,"sigprocmask: %s",strerror(errno));
}
syslog(LOG_INFO,"*** started ***");
update_interfaces();
while (1) {
r=sigprocmask(SIG_UNBLOCK,&s_a.sa_mask,NULL);
if (r) {
/* while blocked: */
update_interfaces();
syslog(LOG_INFO,"sigprocmask (unblock): %s",
strerror(errno));
/* try until sigprocmask is not interrupted by a
* signal */
while (sigprocmask(SIG_UNBLOCK,&s_a.sa_mask,NULL)) ;
}
memcpy(&work_fd_set,&select_set.fds,sizeof(fd_set));
retval=select(select_set.highest_fd+1,&work_fd_set,
NULL,NULL,NULL);
r=sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL);
if (r) {
syslog(LOG_INFO,"sigprocmask (block): %s",
strerror(errno));
/* try until sigprocmask is not interrupted by a
* signal */
while (sigprocmask(SIG_BLOCK,&s_a.sa_mask,NULL)) ;
/* when blocked: */
update_interfaces();
}
/* a signal came in after we unblocked
* or before we blocked? we may process one packet before
* the list gets updated, but we do this check here never-
* theless in order to not catch a signal during some
* system call in update_interfaces
*/
if (update_interface_trigger) {
update_interfaces();
}
if (retval==-1) {
if (errno==EINTR) {
update_interfaces();
} else if (errno) {
syslog(LOG_WARNING,"select returned with %s",
strerror(errno));
}
continue; /* fds are undefined -> no packets came
in at this time */
}
/* check all fds regardless of retval */
for (i_s=select_set.i_s_list;i_s;i_s=i_s->next) {
if (FD_ISSET(i_s->i_fd,&work_fd_set)) {
process_packet(i_s);
}
}
}
/* cleanup... no. */
}