/* * teamd_common.c - Common teamd functions * Copyright (C) 2012-2015 Jiri Pirko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "teamd.h" static struct sock_filter bad_flt[] = { BPF_STMT(BPF_LD + BPF_B + BPF_ABS, -1), BPF_STMT(BPF_RET + BPF_K, 0), }; static const struct sock_fprog bad_fprog = { .len = ARRAY_SIZE(bad_flt), .filter = bad_flt, }; static int attach_filter(int sock, const struct sock_fprog *pref_fprog, const struct sock_fprog *alt_fprog) { int ret; const struct sock_fprog *fprog; if (!pref_fprog) return 0; /* Now we are in tough situation. Older kernels (<3.8) does not * support SKF_AD_VLAN_TAG_PRESENT and SKF_AD_VLAN_TAG. But the kernel * check if these are supported was added after that: * aa1113d9f85da59dcbdd32aeb5d71da566e46def * But it was added close enough. So try to attach obviously bad * filter and assume that is it does not fail, kernel does not support * accessing skb->vlan_tci getting and use alternative filter instead. */ ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bad_fprog, sizeof(bad_fprog)); if (ret == -1) { if (errno != EINVAL) return -errno; fprog = pref_fprog; } else if (alt_fprog) { teamd_log_warn("Kernel does not support accessing skb->vlan_tci from BPF,\n" "falling back to alternative filter. Expect vlan-tagged ARPs\n" "to be accounted on non-tagged link monitor and vice versa."); fprog = alt_fprog; } else { return 0; } ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, fprog, sizeof(*fprog)); if (ret == -1) return -errno; return 0; } int teamd_packet_sock_open_type(int type, int *sock_p, const uint32_t ifindex, const unsigned short family, const struct sock_fprog *fprog, const struct sock_fprog *alt_fprog) { struct sockaddr_ll ll_my; int sock; int ret; int err; sock = socket(PF_PACKET, type, 0); if (sock == -1) { teamd_log_err("Failed to create packet socket."); return -errno; } err = attach_filter(sock, fprog, alt_fprog); if (err) { teamd_log_err("Failed to attach filter."); goto close_sock; } memset(&ll_my, 0, sizeof(ll_my)); ll_my.sll_family = AF_PACKET; ll_my.sll_ifindex = ifindex; ll_my.sll_protocol = family; ret = bind(sock, (struct sockaddr *) &ll_my, sizeof(ll_my)); if (ret == -1) { teamd_log_err("Failed to bind socket."); err = -errno; goto close_sock; } *sock_p = sock; return 0; close_sock: close(sock); return err; } int teamd_packet_sock_open(int *sock_p, const uint32_t ifindex, const unsigned short family, const struct sock_fprog *fprog, const struct sock_fprog *alt_fprog) { return teamd_packet_sock_open_type(SOCK_DGRAM, sock_p, ifindex, family, fprog, alt_fprog); } int teamd_getsockname_hwaddr(int sock, struct sockaddr_ll *addr, size_t expected_len) { socklen_t addr_len; int ret; addr_len = sizeof(*addr); ret = getsockname(sock, (struct sockaddr *) addr, &addr_len); if (ret == -1) { teamd_log_err("Failed to getsockname."); return -errno; } if (expected_len && addr->sll_halen != expected_len) { teamd_log_err("Unexpected length of hw address."); return -ENOTSUP; } return 0; } int teamd_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { ssize_t ret; resend: ret = sendto(sockfd, buf, len, flags, dest_addr, addrlen); if (ret == -1) { switch(errno) { case EINTR: goto resend; case ENETDOWN: case ENETUNREACH: case EADDRNOTAVAIL: case ENXIO: return 0; default: teamd_log_err("sendto failed."); return -errno; } } return 0; } int teamd_send(int sockfd, const void *buf, size_t len, int flags) { ssize_t ret; resend: ret = send(sockfd, buf, len, flags); if (ret == -1) { switch(errno) { case EINTR: goto resend; case ENETDOWN: case ENETUNREACH: case EADDRNOTAVAIL: case ENXIO: return 0; default: teamd_log_err("send failed."); return -errno; } } return 0; } int teamd_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t addrlen) { size_t ret; socklen_t tmp_addrlen = addrlen; rerecv: ret = recvfrom(sockfd, buf, len, flags, src_addr, &tmp_addrlen); if (ret == -1) { switch(errno) { case EINTR: goto rerecv; case ENETDOWN: return 0; default: teamd_log_err("recvfrom failed."); return -errno; } } return ret; }