/* * Ethernet Switch IGMP Snooper * Copyright (C) 2014 ASUSTeK Inc. * All Rights Reserved. * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program 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 Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snooper.h" #ifdef DEBUG_SWITCH #define log_switch(fmt, args...) log_debug("%s::" fmt, "switch", ##args) #else #define log_switch(...) do {} while (0) #endif #ifdef TEST #define logger(level, fmt, args...) printf(fmt, ##args) #endif #define REG_MII_PAGE 0x10 /* MII Page register */ #define REG_MII_ADDR 0x11 /* MII Address register */ #define REG_MII_DATA0 0x18 /* MII Data register 0 */ #define REG_MII_PAGE_ENABLE 1 #define REG_MII_ADDR_WRITE 1 #define REG_MII_ADDR_READ 2 #define ROBO_PHY_ADDR 0x1e /* Robo Switch PHY Address */ #define PAGE_CTRL 0x00 /* Control registers */ #define PAGE_VTBL 0x05 /* ARL Access Registers */ #define PAGE_VLAN 0x34 /* VLAN Registers */ #define REG_CTRL_MCAST 0x21 /* Multicast Control */ #define REG_VTBL_CTRL 0x00 /* ARL Read/Write Control */ #define REG_VTBL_MINDX 0x02 /* MAC Address Index */ #define REG_VTBL_VINDX 0x08 /* VID Table Address Index */ #define REG_VTBL_ACCESS 0x80 /* VLAN Table Access */ #define REG_VTBL_INDX 0x81 /* VLAN Table Address Index */ #define REG_VTBL_ENTRY 0x83 /* VLAN Table Entry */ #define REG_VLAN_ACCESS 0x06 /* VLAN Table Access register */ #define REG_VLAN_READ 0x0c /* VLAN Read register */ enum { SWITCH_UNKNOWN, SWITCH_BCM5325, SWITCH_BCM53115, SWITCH_BCM53125, SWITCH_BCM5301x, } model; static int fd; static struct ifreq ifr; static int vlan; static int cpu_portmap; static int robo_ioctl(int write, int page, int reg, uint16_t *value, int count) { static const int __ioctl_args[2] = { SIOCGETCROBORD, SIOCSETCROBOWR }; int ret, vecarg[5]; uint16_t *valp; memset(&vecarg, 0, sizeof(vecarg)); vecarg[0] = (page << 16) | reg; if (model == SWITCH_BCM5301x) { vecarg[1] = count * 2; valp = (uint16_t *) &vecarg[2]; } else valp = (uint16_t *) &vecarg[1]; if (write) memcpy(valp, value, count * 2); ifr.ifr_data = (caddr_t) vecarg; ret = ioctl(fd, __ioctl_args[write], (caddr_t) &ifr); if (!write) memcpy(value, valp, count * 2); return ret; } static int phy_ioctl(int write, int phy, int reg, uint16_t *value) { static const int __ioctl_args[2] = { SIOCGETCPHYRD2, SIOCSETCPHYWR2 }; int ret, vecarg[2]; if (model == SWITCH_BCM5301x) return robo_ioctl(write, 0x10 + phy, reg, value, 1); memset(&vecarg, 0, sizeof(vecarg)); vecarg[0] = (phy << 16) | reg; if (write) vecarg[1] = *value; ifr.ifr_data = (caddr_t) vecarg; ret = ioctl(fd, __ioctl_args[write], (caddr_t) &ifr); if (!write) *value = vecarg[1]; return ret; } static int phy_select(int page, int reg, int op) { uint16_t value; int i = 3; value = (page << 8) | REG_MII_PAGE_ENABLE; phy_ioctl(1, ROBO_PHY_ADDR, REG_MII_PAGE, &value); value = (reg << 8) | op; phy_ioctl(1, ROBO_PHY_ADDR, REG_MII_ADDR, &value); while (i--) { if (phy_ioctl(0, ROBO_PHY_ADDR, REG_MII_ADDR, &value) == 0 && (value & 3) == 0) return 0; } return -1; } static int robo_read(int page, int reg, uint16_t *value, int count) { int i, ret; if (model == SWITCH_BCM5301x) return robo_ioctl(0, page, reg, value, count); ret = 0; for (i = 0; i < count; i++) { ret |= phy_select(page, reg, REG_MII_ADDR_READ); phy_ioctl(0, ROBO_PHY_ADDR, REG_MII_DATA0 + i, &value[i]); } return ret; } static inline uint16_t robo_read16(int page, int reg) { uint16_t value = 0; robo_read(page, reg, &value, 1); return value; } static inline uint32_t robo_read32(int page, int reg) { uint32_t value = 0; robo_read(page, reg, (uint16_t *) &value, 2); return value; } static int robo_write(int page, int reg, uint16_t *value, int count) { int i; if (model == SWITCH_BCM5301x) return robo_ioctl(1, page, reg, value, count); for (i = 0; i < count; i++) phy_ioctl(1, ROBO_PHY_ADDR, REG_MII_DATA0 + i, &value[i]); return phy_select(page, reg, REG_MII_ADDR_WRITE); } static inline int robo_write16(int page, int reg, uint16_t value) { return robo_write(page, reg, &value, 1); } static inline int robo_write32(int page, int reg, uint32_t value) { return robo_write(page, reg, (uint16_t *) &value, 2); } static int get_model(void) { et_var_t var; int ret, devid = 0; memset(&var, 0, sizeof(var)); var.set = 0; var.cmd = IOV_ET_ROBO_DEVID; var.buf = &devid; var.len = sizeof(devid); ifr.ifr_data = (caddr_t) &var; ret = ioctl(fd, SIOCSETGETVAR, (caddr_t)&ifr); if (ret < 0) devid = robo_read16(0x02, 0x30); if (devid == 0x25) return SWITCH_BCM5325; else if (devid == 0x3115) return SWITCH_BCM53115; else if (devid == 0x3125) return SWITCH_BCM53125; else if ((devid & 0xfffffff0) == 0x53010) return SWITCH_BCM5301x; return SWITCH_UNKNOWN; } static int read_entry(uint16_t *hwaddr, int vlan, uint16_t entry[6], int *index) { int i, valid, portmap, vid; vlan &= (model == SWITCH_BCM5325) ? 0x0f : 0x0fff; robo_write(PAGE_VTBL, REG_VTBL_MINDX, hwaddr, 3); robo_write16(PAGE_VTBL, REG_VTBL_VINDX, vlan); robo_read(PAGE_VTBL, REG_VTBL_MINDX, &entry[0], 4); robo_read(PAGE_VTBL, REG_VTBL_VINDX, &entry[4], 2); robo_write16(PAGE_VTBL, REG_VTBL_CTRL, 0x81); for (i = 0; i < (model == SWITCH_BCM5325 ? 2 : 4); i++) { switch (model) { case SWITCH_BCM5325: robo_read(PAGE_VTBL, 0x10 + 0x08 * i, &entry[0], 4); robo_read(PAGE_VTBL, 0x30 + 0x02 * i, &entry[4], 1); valid = entry[3] & 0x8000; portmap = entry[3] & 0x7f; vid = entry[4] & 0x0f; break; case SWITCH_BCM53115: case SWITCH_BCM53125: case SWITCH_BCM5301x: robo_read(PAGE_VTBL, 0x10 + 0x10 * i, &entry[0], 4); robo_read(PAGE_VTBL, 0x18 + 0x10 * i, &entry[4], 2); valid = entry[5] & 0x1; portmap = entry[4] & 0x1ff; vid = entry[3] & 0x0fff; break; default: return -1; } if (valid && memcmp(entry, hwaddr, ETHER_ADDR_LEN) == 0 && vid == vlan) { *index = i; return portmap; } else if (!valid) *index = i; } memset(entry, 0, sizeof(entry)); return -1; } static int write_entry(uint16_t hwaddr[3], int vlan, uint16_t entry[6], int index, int portmap) { memcpy(entry, hwaddr, ETHER_ADDR_LEN); switch (model) { case SWITCH_BCM5325: entry[4] = vlan & 0x0f; if (portmap >= 0) { entry[3] &= ~0x7f; entry[3] |= 0xc000 | (portmap & 0x7f); } else entry[3] &= ~0x8000; portmap = entry[3] & 0x7f; robo_write(PAGE_VTBL, 0x10 + 0x08 * index, &entry[0], 4); robo_write(PAGE_VTBL, 0x30 + 0x02 * index, &entry[4], 1); break; case SWITCH_BCM53115: case SWITCH_BCM53125: case SWITCH_BCM5301x: entry[3] = vlan & 0x0fff; if (portmap >= 0) { entry[4] &= ~0x1ff; entry[4] |= 0x8000 | (portmap & 0x1ff); entry[5] |= 0x01; } else entry[5] &= ~0x01; portmap = entry[4] & 0x1ff; robo_write(PAGE_VTBL, 0x10 + 0x10 * index, &entry[0], 4); robo_write(PAGE_VTBL, 0x18 + 0x10 * index, &entry[4], 2); break; default: return -1; } robo_write16(PAGE_VTBL, REG_VTBL_CTRL, 0x80); return portmap; } static int read_portmap(int vlan) { uint32_t entry; int i, portmap = 0; switch (model) { case SWITCH_BCM5325: for (i = vlan; i < vlan + 16; i++) { robo_write16(PAGE_VLAN, REG_VLAN_ACCESS, 0x2000 | (vlan & 0x0ff0) | (i & 0x0f)); entry = robo_read32(PAGE_VLAN, REG_VLAN_READ); if (((entry & 0x1000000)) && ((entry & 0x0fff000) >> 12) == (vlan & 0x0fff)) { portmap = entry & 0x2f; break; } } break; case SWITCH_BCM53115: case SWITCH_BCM53125: case SWITCH_BCM5301x: robo_write16(PAGE_VTBL, REG_VTBL_INDX, vlan & 0x0fff); robo_write16(PAGE_VTBL, REG_VTBL_ACCESS, 0x81); entry = robo_read32(PAGE_VTBL, REG_VTBL_ENTRY); portmap = entry & 0x1ff; break; default: return -1; } return portmap; } int switch_init(char *ifname) { struct vlan_ioctl_args ifv; int portmap; #ifdef SOCK_CLOEXEC fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); if (fd < 0) { log_error("socket: %s", strerror(errno)); return -1; } #else int value; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { log_error("socket: %s", strerror(errno)); return -1; } value = fcntl(fd, F_GETFD, 0); if (value < 0 || fcntl(fd, F_SETFD, value | FD_CLOEXEC) < 0) { log_error("fcntl::FD_CLOEXEC: %s", strerror(errno)); goto error; } value = fcntl(fd, F_GETFL, 0); if (value < 0 || fcntl(fd, F_SETFL, value | O_NONBLOCK) < 0) { log_error("fcntl::O_NONBLOCK: %s", strerror(errno)); goto error; } #endif memset(&ifr, 0, sizeof(ifr)); memset(&ifv, 0, sizeof(ifv)); strncpy(ifv.device1, ifname, sizeof(ifv.device1)); ifv.cmd = GET_VLAN_REALDEV_NAME_CMD; if (ioctl(fd, SIOCGIFVLAN, &ifv) >= 0) { strncpy(ifr.ifr_name, ifv.u.device2, sizeof(ifr.ifr_name)); ifv.cmd = GET_VLAN_VID_CMD; if (ioctl(fd, SIOCGIFVLAN, &ifv) >= 0) vlan = ifv.u.VID; } else if (sscanf(ifname, "vlan%u", &vlan) == 1) { strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name)); } else if (sscanf(ifname, "%16[^.].%u", ifr.ifr_name, &vlan) != 2) { strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); vlan = 0; } model = get_model(); switch (model) { case SWITCH_BCM5325: cpu_portmap = (1 << 5); break; case SWITCH_BCM53115: case SWITCH_BCM53125: cpu_portmap = (1 << 5) | (1 << 8); break; case SWITCH_BCM5301x: cpu_portmap = (1 << 5) | (1 << 7) | (1 << 8); break; default: log_error("unsupported switch: %s", strerror(errno)); goto error; } portmap = read_portmap(vlan); log_switch("%-5s map@vlan%u = " FMT_PORTS, "read", vlan, ARG_PORTS(portmap)); if (portmap & cpu_portmap) cpu_portmap &= portmap; log_switch("%-5s cpu@vlan%u = " FMT_PORTS, "init", vlan, ARG_PORTS(cpu_portmap)); if (model == SWITCH_BCM5325) robo_write16(PAGE_CTRL, REG_CTRL_MCAST, 1); return 0; error: if (fd >= 0) close(fd); return -1; } void switch_done(void) { if (model == SWITCH_BCM5325) robo_write16(PAGE_CTRL, REG_CTRL_MCAST, 0); if (fd >= 0) close(fd); } int switch_get_port(unsigned char *haddr) { uint16_t hwaddr[3], entry[6]; int value, index = 0; hwaddr[2] = (haddr[0] << 8) | haddr[1]; hwaddr[1] = (haddr[2] << 8) | haddr[3]; hwaddr[0] = (haddr[4] << 8) | haddr[5]; value = read_entry(hwaddr, vlan, entry, &index); if (model == SWITCH_BCM5325 && value == 8) value = 5; log_switch("%-5s [" FMT_EA "] = 0x%x", "read", ARG_EA(haddr), value); return value; } int switch_add_portmap(unsigned char *maddr, int portmap) { uint16_t hwaddr[3], entry[6]; int value, index = 0; if ((maddr[0] & 0x01) == 0) return -1; hwaddr[2] = (maddr[0] << 8) | maddr[1]; hwaddr[1] = (maddr[2] << 8) | maddr[3]; hwaddr[0] = (maddr[4] << 8) | maddr[5]; value = read_entry(hwaddr, vlan, entry, &index); log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "read", ARG_EA(maddr), ARG_PORTS(value)); if (value < 0) value = 0; portmap = (value | portmap) | cpu_portmap; if (portmap != value) { log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "write", ARG_EA(maddr), ARG_PORTS(portmap)); write_entry(hwaddr, vlan, entry, index, portmap); } return portmap; } int switch_del_portmap(unsigned char *maddr, int portmap) { uint16_t hwaddr[3], entry[6]; int value, index = 0; if ((maddr[0] & 0x01) == 0) return -1; hwaddr[2] = (maddr[0] << 8) | maddr[1]; hwaddr[1] = (maddr[2] << 8) | maddr[3]; hwaddr[0] = (maddr[4] << 8) | maddr[5]; value = read_entry(hwaddr, vlan, entry, &index); log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "read", ARG_EA(maddr), ARG_PORTS(value)); if (value < 0) value = 0; portmap = (value & ~portmap) | cpu_portmap; if (portmap != value) { log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "write", ARG_EA(maddr), ARG_PORTS(portmap)); write_entry(hwaddr, vlan, entry, index, portmap); } return portmap; } int switch_clr_portmap(unsigned char *maddr) { uint16_t hwaddr[3], entry[6]; int value, index = 0; if ((maddr[0] & 0x01) == 0) return -1; hwaddr[2] = (maddr[0] << 8) | maddr[1]; hwaddr[1] = (maddr[2] << 8) | maddr[3]; hwaddr[0] = (maddr[4] << 8) | maddr[5]; value = read_entry(hwaddr, vlan, entry, &index); log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "read", ARG_EA(maddr), ARG_PORTS(value)); if (value >= 0) { log_switch("%-5s [" FMT_EA "] = " FMT_PORTS, "clear", ARG_EA(maddr), ARG_PORTS(value)); write_entry(hwaddr, vlan, entry, index, -1); } return value; } #ifdef TEST int main(int argc, char *argv[]) { uint16_t value[4] = { 0 }; int i, write, page, reg, count; if (!argv[1] || !argv[2] || !argv[3]) return 0; write = (strncmp(argv[1], "robowr", 6) == 0 ? 1 : strncmp(argv[1], "robord", 6) == 0 ? 0 : -1); if (write < 0 || (write && !argv[4])) return 0; count = atoi(argv[1] + 6); count = count ? count / 16 : 0; count = count ? : 1; if (switch_init("eth0") < 0) { perror("switch"); return -1; } page = strtoul(argv[2], NULL, 0); reg = strtoul(argv[3], NULL, 0); if (write) { unsigned long long val = strtoull(argv[4], NULL, 0); memcpy(value, &val, count * 2); robo_write(page, reg, value, count); } robo_read(page, reg, value, count); printf("page %02x reg %02x = ", page, reg); for (i = 0; i < count * 2; i++) printf("%02x", ((unsigned char *) value)[i]); printf("\n"); switch_done(); return 0; } #endif