├── .gitignore ├── CMakeLists.txt ├── relayd.h ├── dhcp.c ├── route.c └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | CMakeCache.txt 3 | CMakeFiles 4 | *.cmake 5 | install_manifest.txt 6 | relayd 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | PROJECT(relayd C) 4 | ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3) 5 | 6 | IF(APPLE) 7 | INCLUDE_DIRECTORIES(/opt/local/include) 8 | LINK_DIRECTORIES(/opt/local/lib) 9 | ENDIF() 10 | 11 | ADD_EXECUTABLE(relayd main.c dhcp.c route.c) 12 | TARGET_LINK_LIBRARIES(relayd ubox) 13 | 14 | SET(CMAKE_INSTALL_PREFIX /usr) 15 | 16 | INSTALL(TARGETS relayd 17 | RUNTIME DESTINATION sbin 18 | ) 19 | -------------------------------------------------------------------------------- /relayd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Felix Fietkau 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 16 | * 17 | */ 18 | #ifndef __RELAYD_H 19 | #define __RELAYD_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #define DEBUG 38 | #ifdef DEBUG 39 | #define DPRINTF(level, ...) if (debug >= level) fprintf(stderr, __VA_ARGS__); 40 | #else 41 | #define DPRINTF(...) do {} while(0) 42 | #endif 43 | 44 | #ifndef __packed 45 | #define __packed __attribute__((packed)) 46 | #endif 47 | 48 | #define __uc(c) ((unsigned char *)(c)) 49 | 50 | #define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" 51 | #define MAC_BUF(_c) __uc(_c)[0], __uc(_c)[1], __uc(_c)[2], __uc(_c)[3], __uc(_c)[4], __uc(_c)[5] 52 | 53 | #define IP_FMT "%d.%d.%d.%d" 54 | #define IP_BUF(_c) __uc(_c)[0], __uc(_c)[1], __uc(_c)[2], __uc(_c)[3] 55 | 56 | #define DUMMY_IP ((uint8_t *) "\x01\x01\x01\x01") 57 | 58 | #define DHCP_FLAG_BROADCAST (1 << 15) 59 | 60 | struct relayd_interface { 61 | struct list_head list; 62 | struct uloop_fd fd; 63 | struct uloop_fd bcast_fd; 64 | struct sockaddr_ll sll; 65 | struct sockaddr_ll bcast_sll; 66 | char ifname[IFNAMSIZ]; 67 | struct list_head hosts; 68 | uint8_t src_ip[4]; 69 | bool managed; 70 | int rt_table; 71 | }; 72 | 73 | struct relayd_host { 74 | struct list_head list; 75 | struct list_head routes; 76 | struct relayd_interface *rif; 77 | uint8_t lladdr[ETH_ALEN]; 78 | uint8_t ipaddr[4]; 79 | struct uloop_timeout timeout; 80 | int cleanup_pending; 81 | }; 82 | 83 | struct relayd_route { 84 | struct list_head list; 85 | uint8_t dest[4]; 86 | uint8_t mask; 87 | }; 88 | 89 | struct arp_packet { 90 | struct ether_header eth; 91 | struct ether_arp arp; 92 | } __packed; 93 | 94 | struct rtnl_req { 95 | struct nlmsghdr nl; 96 | struct rtmsg rt; 97 | } __packed; 98 | 99 | extern struct list_head interfaces; 100 | extern int debug; 101 | extern int route_table; 102 | extern uint8_t local_addr[4]; 103 | extern int local_route_table; 104 | 105 | void rtnl_route_set(struct relayd_host *host, struct relayd_route *route, bool add); 106 | 107 | static inline void relayd_add_route(struct relayd_host *host, struct relayd_route *route) 108 | { 109 | rtnl_route_set(host, route, true); 110 | } 111 | 112 | static inline void relayd_del_route(struct relayd_host *host, struct relayd_route *route) 113 | { 114 | rtnl_route_set(host, route, false); 115 | } 116 | 117 | void relayd_add_interface_routes(struct relayd_interface *rif); 118 | void relayd_del_interface_routes(struct relayd_interface *rif); 119 | 120 | int relayd_rtnl_init(void); 121 | void relayd_rtnl_done(void); 122 | 123 | struct relayd_host *relayd_refresh_host(struct relayd_interface *rif, 124 | const uint8_t *lladdr, 125 | const uint8_t *ipaddr); 126 | void relayd_add_host_route(struct relayd_host *host, const uint8_t *ipaddr, uint8_t mask); 127 | void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, uint8_t mask, int timeout); 128 | 129 | void relayd_forward_bcast_packet(struct relayd_interface *from_rif, void *packet, int len); 130 | bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int len, bool forward, bool parse); 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /dhcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Felix Fietkau 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 16 | */ 17 | 18 | #define _GNU_SOURCE 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "relayd.h" 27 | 28 | struct ip_packet { 29 | struct ether_header eth; 30 | struct iphdr iph; 31 | } __packed; 32 | 33 | 34 | enum { 35 | DHCP_OPTION_ROUTER = 0x03, 36 | DHCP_OPTION_ROUTES = 0x79, 37 | DHCP_OPTION_END = 0xff, 38 | }; 39 | 40 | struct dhcp_option { 41 | uint8_t code; 42 | uint8_t len; 43 | uint8_t data[]; 44 | }; 45 | 46 | struct dhcp_header { 47 | uint8_t op, htype, hlen, hops; 48 | uint32_t xit; 49 | uint16_t secs, flags; 50 | struct in_addr ciaddr, yiaddr, siaddr, giaddr; 51 | unsigned char chaddr[16]; 52 | unsigned char sname[64]; 53 | unsigned char file[128]; 54 | uint32_t cookie; 55 | uint8_t option_data[]; 56 | } __packed; 57 | 58 | static uint16_t 59 | chksum(uint16_t sum, const uint8_t *data, uint16_t len) 60 | { 61 | const uint8_t *last; 62 | uint16_t t; 63 | 64 | last = data + len - 1; 65 | 66 | while(data < last) { 67 | t = (data[0] << 8) + data[1]; 68 | sum += t; 69 | if(sum < t) 70 | sum++; 71 | data += 2; 72 | } 73 | 74 | if(data == last) { 75 | t = (data[0] << 8) + 0; 76 | sum += t; 77 | if(sum < t) 78 | sum++; 79 | } 80 | 81 | return sum; 82 | } 83 | 84 | static void 85 | parse_dhcp_options(struct relayd_host *host, struct dhcp_header *dhcp, int len) 86 | { 87 | uint8_t *end = (uint8_t *) dhcp + len; 88 | struct dhcp_option *opt = (void *)dhcp->option_data; 89 | static const uint8_t dest[4] = { 0, 0, 0, 0 }; 90 | 91 | while((uint8_t *) opt + sizeof(*opt) < end) { 92 | if ((uint8_t *) opt + opt->len > end || 93 | (uint8_t *) opt + sizeof(*opt) > end ) 94 | break; 95 | 96 | opt = (void *) &opt->data[opt->len]; 97 | if ((uint8_t *) opt + sizeof(*opt) > end ) 98 | break; 99 | switch(opt->code) { 100 | case DHCP_OPTION_ROUTER: 101 | DPRINTF(2, "Found a DHCP router option, len=%d\n", opt->len); 102 | if (!memcmp(opt->data, host->ipaddr, 4)) 103 | relayd_add_host_route(host, dest, 0); 104 | else 105 | relayd_add_pending_route(opt->data, dest, 0, 10000); 106 | break; 107 | case DHCP_OPTION_ROUTES: 108 | DPRINTF(2, "Found a DHCP static routes option, len=%d\n", opt->len); 109 | break; 110 | case DHCP_OPTION_END: 111 | opt = (void *) end; 112 | continue; 113 | default: 114 | DPRINTF(3, "Skipping unknown DHCP option %02x\n", opt->code); 115 | continue; 116 | } 117 | 118 | } 119 | } 120 | 121 | bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int len, bool forward, bool parse) 122 | { 123 | struct ip_packet *pkt = data; 124 | struct udphdr *udp; 125 | struct dhcp_header *dhcp; 126 | struct relayd_host *host; 127 | int udplen; 128 | uint16_t sum; 129 | 130 | if (pkt->eth.ether_type != htons(ETH_P_IP)) 131 | return false; 132 | 133 | if (pkt->iph.version != 4) 134 | return false; 135 | 136 | if (pkt->iph.protocol != IPPROTO_UDP) 137 | return false; 138 | 139 | udp = (void *) ((char *) &pkt->iph + (pkt->iph.ihl << 2)); 140 | dhcp = (void *) (udp + 1); 141 | 142 | if ((uint8_t *)udp + sizeof(*udp) > (uint8_t *)data + len || 143 | (uint8_t *)dhcp + sizeof(*dhcp) > (uint8_t *)data + len) 144 | return false; 145 | 146 | udplen = ntohs(udp->len); 147 | if (udplen > len - ((char *) udp - (char *) data)) 148 | return false; 149 | 150 | if (udp->dest != htons(67) && udp->source != htons(67)) 151 | return false; 152 | 153 | if (dhcp->op != 1 && dhcp->op != 2) 154 | return false; 155 | 156 | if (!forward) 157 | return true; 158 | 159 | if (dhcp->op == 2) { 160 | host = relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) &pkt->iph.saddr); 161 | if (host && parse) 162 | parse_dhcp_options(host, dhcp, udplen - sizeof(struct udphdr)); 163 | } 164 | 165 | DPRINTF(2, "%s: handling DHCP %s\n", rif->ifname, (dhcp->op == 1 ? "request" : "response")); 166 | 167 | dhcp->flags |= htons(DHCP_FLAG_BROADCAST); 168 | 169 | udp->check = 0; 170 | sum = udplen + IPPROTO_UDP; 171 | sum = chksum(sum, (void *) &pkt->iph.saddr, 8); 172 | sum = chksum(sum, (void *) udp, udplen); 173 | if (sum == 0) 174 | sum = 0xffff; 175 | 176 | udp->check = htons(~sum); 177 | 178 | relayd_forward_bcast_packet(rif, data, len); 179 | 180 | return true; 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /route.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Felix Fietkau 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "relayd.h" 29 | 30 | #define NLMSG_ALIGNTO 4U 31 | #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) 32 | 33 | static struct uloop_fd rtnl_sock; 34 | static unsigned int rtnl_seq, rtnl_dump_seq; 35 | int route_table = 16800; 36 | 37 | static void rtnl_flush(void) 38 | { 39 | ssize_t ret; 40 | int fd; 41 | 42 | fd = open("/proc/sys/net/ipv4/route/flush", O_WRONLY); 43 | if (fd < 0) 44 | return; 45 | 46 | ret = write(fd, "-1", 2); 47 | if (ret != 2) 48 | perror("write"); 49 | close(fd); 50 | } 51 | 52 | enum { 53 | RULE_F_ADD = (1 << 0), 54 | RULE_F_DEFGW_WORKAROUND = (1 << 1), 55 | }; 56 | 57 | static int get_route_table(struct relayd_interface *rif) 58 | { 59 | if (rif) 60 | return rif->rt_table; 61 | else 62 | return local_route_table; 63 | } 64 | 65 | static void 66 | rtnl_rule_request(struct relayd_interface *rif, int flags) 67 | { 68 | struct { 69 | struct nlmsghdr nl; 70 | struct rtmsg rt; 71 | struct { 72 | struct rtattr rta; 73 | int table; 74 | } __packed table; 75 | struct { 76 | struct rtattr rta; 77 | int prio; 78 | } __packed prio; 79 | struct { 80 | struct rtattr rta; 81 | char ifname[IFNAMSIZ + 1]; 82 | } __packed dev; 83 | } __packed req = { 84 | .rt = { 85 | .rtm_family = AF_INET, 86 | .rtm_table = RT_TABLE_UNSPEC, 87 | .rtm_scope = RT_SCOPE_UNIVERSE, 88 | .rtm_protocol = RTPROT_BOOT, 89 | }, 90 | .prio = { 91 | .rta.rta_type = FRA_PRIORITY, 92 | .rta.rta_len = sizeof(req.prio), 93 | .prio = 2, 94 | }, 95 | .table.rta = { 96 | .rta_type = FRA_TABLE, 97 | .rta_len = sizeof(req.table), 98 | }, 99 | }; 100 | const char *ifname = "lo"; 101 | int padding = sizeof(req.dev.ifname); 102 | 103 | if (rif) 104 | ifname = rif->ifname; 105 | 106 | if (!(flags & RULE_F_DEFGW_WORKAROUND)) { 107 | int len = strlen(ifname) + 1; 108 | req.dev.rta.rta_type = FRA_IFNAME; 109 | padding -= NLMSG_ALIGN(len); 110 | strcpy(req.dev.ifname, ifname); 111 | req.dev.rta.rta_len = sizeof(req.dev.rta) + len; 112 | } else { 113 | padding = sizeof(req.dev); 114 | req.prio.prio--; 115 | } 116 | req.table.table = get_route_table(rif); 117 | req.nl.nlmsg_len = sizeof(req) - padding; 118 | 119 | req.nl.nlmsg_flags = NLM_F_REQUEST; 120 | if (flags & RULE_F_ADD) { 121 | req.nl.nlmsg_type = RTM_NEWRULE; 122 | req.nl.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; 123 | 124 | req.rt.rtm_type = RTN_UNICAST; 125 | } else { 126 | req.nl.nlmsg_type = RTM_DELRULE; 127 | req.rt.rtm_type = RTN_UNSPEC; 128 | } 129 | 130 | send(rtnl_sock.fd, &req, req.nl.nlmsg_len, 0); 131 | rtnl_flush(); 132 | } 133 | 134 | struct rtnl_addr { 135 | struct rtattr rta; 136 | uint8_t ipaddr[4]; 137 | } __packed; 138 | 139 | static struct rtnl_addr * 140 | rtnl_add_addr(struct rtnl_addr *addr, int *len, int type, const uint8_t *ipaddr) 141 | { 142 | addr->rta.rta_type = type; 143 | memcpy(addr->ipaddr, ipaddr, 4); 144 | *len += sizeof(*addr); 145 | return addr + 1; 146 | } 147 | 148 | static void 149 | rtnl_route_request(struct relayd_interface *rif, struct relayd_host *host, 150 | struct relayd_route *route, bool add) 151 | { 152 | static struct { 153 | struct nlmsghdr nl; 154 | struct rtmsg rt; 155 | struct { 156 | struct rtattr rta; 157 | int table; 158 | } __packed table; 159 | struct { 160 | struct rtattr rta; 161 | int ifindex; 162 | } __packed dev; 163 | struct rtnl_addr addr[3]; 164 | } __packed req = { 165 | .rt = { 166 | .rtm_family = AF_INET, 167 | .rtm_dst_len = 32, 168 | .rtm_table = RT_TABLE_MAIN, 169 | }, 170 | .table.rta = { 171 | .rta_type = RTA_TABLE, 172 | .rta_len = sizeof(req.table), 173 | }, 174 | .dev.rta = { 175 | .rta_type = RTA_OIF, 176 | .rta_len = sizeof(req.dev), 177 | }, 178 | .addr[0].rta.rta_len = sizeof(struct rtnl_addr), 179 | .addr[1].rta.rta_len = sizeof(struct rtnl_addr), 180 | .addr[2].rta.rta_len = sizeof(struct rtnl_addr), 181 | }; 182 | int pktlen = sizeof(req) - sizeof(req.addr); 183 | struct rtnl_addr *addr = &req.addr[0]; 184 | const char *ifname = "loopback"; 185 | 186 | req.dev.ifindex = host->rif->sll.sll_ifindex; 187 | req.table.table = get_route_table(rif); 188 | 189 | req.nl.nlmsg_flags = NLM_F_REQUEST; 190 | if (add) { 191 | req.nl.nlmsg_type = RTM_NEWROUTE; 192 | req.nl.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; 193 | 194 | req.rt.rtm_protocol = RTPROT_BOOT; 195 | if (route) { 196 | req.rt.rtm_scope = RT_SCOPE_UNIVERSE; 197 | } else { 198 | req.rt.rtm_scope = RT_SCOPE_LINK; 199 | } 200 | req.rt.rtm_type = RTN_UNICAST; 201 | } else { 202 | req.nl.nlmsg_type = RTM_DELROUTE; 203 | req.rt.rtm_scope = RT_SCOPE_NOWHERE; 204 | } 205 | 206 | if (rif) 207 | ifname = rif->ifname; 208 | 209 | if (route) { 210 | DPRINTF(2, "%s: add route to "IP_FMT"/%d via "IP_FMT" (%s)\n", ifname, 211 | IP_BUF(route->dest), route->mask, IP_BUF(host->ipaddr), 212 | host->rif->ifname); 213 | 214 | req.rt.rtm_dst_len = route->mask; 215 | if (route->mask) 216 | addr = rtnl_add_addr(addr, &pktlen, RTA_DST, route->dest); 217 | addr = rtnl_add_addr(addr, &pktlen, RTA_GATEWAY, host->ipaddr); 218 | } else { 219 | DPRINTF(2, "%s: add host route to "IP_FMT" (%s)\n", ifname, 220 | IP_BUF(host->ipaddr), host->rif->ifname); 221 | addr = rtnl_add_addr(addr, &pktlen, RTA_DST, host->ipaddr); 222 | req.rt.rtm_dst_len = 32; 223 | } 224 | 225 | /* local route */ 226 | if (!rif) 227 | addr = rtnl_add_addr(addr, &pktlen, RTA_PREFSRC, local_addr); 228 | 229 | req.nl.nlmsg_len = pktlen; 230 | if (route) 231 | rtnl_rule_request(rif, RULE_F_DEFGW_WORKAROUND | RULE_F_ADD); 232 | send(rtnl_sock.fd, &req, pktlen, 0); 233 | if (route) 234 | rtnl_rule_request(rif, RULE_F_DEFGW_WORKAROUND); 235 | rtnl_flush(); 236 | } 237 | 238 | void 239 | rtnl_route_set(struct relayd_host *host, struct relayd_route *route, bool add) 240 | { 241 | struct relayd_interface *rif; 242 | 243 | list_for_each_entry(rif, &interfaces, list) { 244 | if (rif == host->rif) 245 | continue; 246 | 247 | rtnl_route_request(rif, host, route, add); 248 | } 249 | if (local_route_table) 250 | rtnl_route_request(NULL, host, route, add); 251 | } 252 | 253 | void relayd_add_interface_routes(struct relayd_interface *rif) 254 | { 255 | rif->rt_table = route_table++; 256 | rtnl_rule_request(rif, RULE_F_ADD); 257 | } 258 | 259 | void relayd_del_interface_routes(struct relayd_interface *rif) 260 | { 261 | rtnl_rule_request(rif, 0); 262 | } 263 | 264 | #ifndef NDA_RTA 265 | #define NDA_RTA(r) \ 266 | ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) 267 | #endif 268 | 269 | static void rtnl_parse_newneigh(struct nlmsghdr *h) 270 | { 271 | struct relayd_interface *rif = NULL; 272 | struct ndmsg *r = NLMSG_DATA(h); 273 | const uint8_t *lladdr = NULL; 274 | const uint8_t *ipaddr = NULL; 275 | struct rtattr *rta; 276 | int len; 277 | 278 | if (r->ndm_family != AF_INET) 279 | return; 280 | 281 | list_for_each_entry(rif, &interfaces, list) { 282 | if (rif->sll.sll_ifindex == r->ndm_ifindex) 283 | goto found_interface; 284 | } 285 | return; 286 | 287 | found_interface: 288 | len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*r)); 289 | for (rta = NDA_RTA(r); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { 290 | switch(rta->rta_type) { 291 | case NDA_LLADDR: 292 | lladdr = RTA_DATA(rta); 293 | break; 294 | case NDA_DST: 295 | ipaddr = RTA_DATA(rta); 296 | break; 297 | default: 298 | break; 299 | } 300 | } 301 | 302 | if (!lladdr || !ipaddr || (r->ndm_state & (NUD_INCOMPLETE|NUD_FAILED))) 303 | return; 304 | 305 | if (!memcmp(lladdr, "\x00\x00\x00\x00\x00\x00", ETH_ALEN)) 306 | return; 307 | 308 | DPRINTF(1, "%s: Found ARP cache entry for host "IP_FMT" ("MAC_FMT")\n", 309 | rif->ifname, IP_BUF(ipaddr), MAC_BUF(lladdr)); 310 | relayd_refresh_host(rif, lladdr, ipaddr); 311 | } 312 | 313 | static void rtnl_parse_packet(void *data, int len) 314 | { 315 | struct nlmsghdr *h; 316 | 317 | for (h = data; NLMSG_OK(h, len); h = NLMSG_NEXT(h, len)) { 318 | if (h->nlmsg_type == NLMSG_DONE || 319 | h->nlmsg_type == NLMSG_ERROR) 320 | return; 321 | 322 | if (h->nlmsg_seq != rtnl_dump_seq) 323 | continue; 324 | 325 | if (h->nlmsg_type == RTM_NEWNEIGH) 326 | rtnl_parse_newneigh(h); 327 | } 328 | } 329 | 330 | static void rtnl_cb(struct uloop_fd *fd, unsigned int events) 331 | { 332 | struct sockaddr_nl nladdr; 333 | static uint8_t buf[16384]; 334 | struct iovec iov = { 335 | .iov_base = buf, 336 | .iov_len = sizeof(buf), 337 | }; 338 | struct msghdr msg = { 339 | .msg_name = &nladdr, 340 | .msg_namelen = sizeof(nladdr), 341 | .msg_iov = &iov, 342 | .msg_iovlen = 1, 343 | }; 344 | 345 | do { 346 | int len; 347 | 348 | len = recvmsg(rtnl_sock.fd, &msg, 0); 349 | if (len < 0) { 350 | if (errno == EINTR) 351 | continue; 352 | 353 | return; 354 | } 355 | 356 | if (!len) 357 | break; 358 | 359 | if (nladdr.nl_pid != 0) 360 | continue; 361 | 362 | rtnl_parse_packet(buf, len); 363 | } while (1); 364 | } 365 | 366 | static void rtnl_dump_request(int nlmsg_type) 367 | { 368 | static struct { 369 | struct nlmsghdr nlh; 370 | struct rtgenmsg g; 371 | } req = { 372 | .nlh = { 373 | .nlmsg_len = sizeof(req), 374 | .nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST, 375 | .nlmsg_pid = 0, 376 | }, 377 | .g.rtgen_family = AF_INET, 378 | }; 379 | req.nlh.nlmsg_type = nlmsg_type; 380 | req.nlh.nlmsg_seq = rtnl_seq; 381 | send(rtnl_sock.fd, &req, sizeof(req), 0); 382 | rtnl_seq++; 383 | } 384 | 385 | int relayd_rtnl_init(void) 386 | { 387 | struct sockaddr_nl snl_local = {}; 388 | 389 | rtnl_sock.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 390 | if (rtnl_sock.fd < 0) { 391 | perror("socket(AF_NETLINK)"); 392 | return -1; 393 | } 394 | 395 | snl_local.nl_family = AF_NETLINK; 396 | 397 | if (bind(rtnl_sock.fd, (struct sockaddr *) &snl_local, sizeof(struct sockaddr_nl)) < 0) { 398 | perror("bind"); 399 | close(rtnl_sock.fd); 400 | return -1; 401 | } 402 | 403 | rtnl_sock.cb = rtnl_cb; 404 | uloop_fd_add(&rtnl_sock, ULOOP_READ | ULOOP_EDGE_TRIGGER); 405 | 406 | rtnl_seq = time(NULL); 407 | rtnl_dump_seq = rtnl_seq; 408 | rtnl_dump_request(RTM_GETNEIGH); 409 | rtnl_rule_request(NULL, RULE_F_ADD); 410 | 411 | return 0; 412 | } 413 | 414 | void relayd_rtnl_done(void) 415 | { 416 | rtnl_rule_request(NULL, 0); 417 | uloop_fd_delete(&rtnl_sock); 418 | close(rtnl_sock.fd); 419 | } 420 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Felix Fietkau 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License v2 as published by 6 | * the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 16 | * 17 | */ 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "relayd.h" 33 | 34 | static LIST_HEAD(pending_routes); 35 | LIST_HEAD(interfaces); 36 | int debug; 37 | 38 | static int host_timeout; 39 | static int host_ping_tries; 40 | static int inet_sock; 41 | static int forward_bcast; 42 | static int forward_dhcp; 43 | static int parse_dhcp; 44 | 45 | uint8_t local_addr[4]; 46 | int local_route_table; 47 | 48 | struct relayd_pending_route { 49 | struct relayd_route rt; 50 | struct uloop_timeout timeout; 51 | uint8_t gateway[4]; 52 | }; 53 | 54 | static struct relayd_host *find_host_by_ipaddr(struct relayd_interface *rif, const uint8_t *ipaddr) 55 | { 56 | struct relayd_host *host; 57 | 58 | if (!rif) { 59 | list_for_each_entry(rif, &interfaces, list) { 60 | host = find_host_by_ipaddr(rif, ipaddr); 61 | if (!host) 62 | continue; 63 | 64 | return host; 65 | } 66 | return NULL; 67 | } 68 | 69 | list_for_each_entry(host, &rif->hosts, list) { 70 | if (memcmp(ipaddr, host->ipaddr, sizeof(host->ipaddr)) != 0) 71 | continue; 72 | 73 | return host; 74 | } 75 | return NULL; 76 | } 77 | 78 | static void add_arp(struct relayd_host *host) 79 | { 80 | struct sockaddr_in *sin; 81 | struct arpreq arp; 82 | 83 | strncpy(arp.arp_dev, host->rif->ifname, sizeof(arp.arp_dev)); 84 | arp.arp_flags = ATF_COM; 85 | 86 | arp.arp_ha.sa_family = ARPHRD_ETHER; 87 | memcpy(arp.arp_ha.sa_data, host->lladdr, ETH_ALEN); 88 | 89 | sin = (struct sockaddr_in *) &arp.arp_pa; 90 | sin->sin_family = AF_INET; 91 | memcpy(&sin->sin_addr, host->ipaddr, sizeof(host->ipaddr)); 92 | 93 | ioctl(inet_sock, SIOCSARP, &arp); 94 | } 95 | 96 | static void timeout_host_route(struct uloop_timeout *timeout) 97 | { 98 | struct relayd_pending_route *rt; 99 | 100 | rt = container_of(timeout, struct relayd_pending_route, timeout); 101 | list_del(&rt->rt.list); 102 | free(rt); 103 | } 104 | 105 | void relayd_add_host_route(struct relayd_host *host, const uint8_t *dest, uint8_t mask) 106 | { 107 | struct relayd_route *rt; 108 | 109 | list_for_each_entry(rt, &host->routes, list) { 110 | if (!memcmp(rt->dest, dest, sizeof(rt->dest)) && rt->mask == mask) 111 | return; 112 | } 113 | 114 | rt = calloc(1, sizeof(*rt)); 115 | if (!rt) 116 | return; 117 | 118 | list_add(&rt->list, &host->routes); 119 | memcpy(rt->dest, dest, sizeof(rt->dest)); 120 | rt->mask = mask; 121 | relayd_add_route(host, rt); 122 | } 123 | 124 | static void del_host(struct relayd_host *host) 125 | { 126 | struct relayd_route *route, *tmp; 127 | 128 | DPRINTF(1, "%s: deleting host "IP_FMT" ("MAC_FMT")\n", host->rif->ifname, 129 | IP_BUF(host->ipaddr), MAC_BUF(host->lladdr)); 130 | 131 | list_for_each_entry_safe(route, tmp, &host->routes, list) { 132 | relayd_del_route(host, route); 133 | list_del(&route->list); 134 | free(route); 135 | } 136 | if (host->rif->managed) 137 | relayd_del_route(host, NULL); 138 | uloop_timeout_cancel(&host->timeout); 139 | list_del(&host->list); 140 | free(host); 141 | } 142 | 143 | static void fill_arp_packet(struct arp_packet *pkt, struct relayd_interface *rif, 144 | const uint8_t spa[4], const uint8_t tpa[4]) 145 | { 146 | memset(pkt, 0, sizeof(*pkt)); 147 | 148 | pkt->eth.ether_type = htons(ETHERTYPE_ARP); 149 | memcpy(pkt->eth.ether_shost, rif->sll.sll_addr, ETH_ALEN); 150 | 151 | memcpy(pkt->arp.arp_sha, rif->sll.sll_addr, ETH_ALEN); 152 | memcpy(pkt->arp.arp_spa, spa, 4); 153 | memcpy(pkt->arp.arp_tpa, tpa, 4); 154 | 155 | pkt->arp.arp_hrd = htons(ARPHRD_ETHER); 156 | pkt->arp.arp_pro = htons(ETH_P_IP); 157 | pkt->arp.arp_hln = ETH_ALEN; 158 | pkt->arp.arp_pln = 4; 159 | } 160 | 161 | static void send_arp_request(struct relayd_interface *rif, const uint8_t *ipaddr) 162 | { 163 | struct arp_packet pkt; 164 | 165 | fill_arp_packet(&pkt, rif, rif->src_ip, ipaddr); 166 | 167 | pkt.arp.arp_op = htons(ARPOP_REQUEST); 168 | memcpy(pkt.arp.arp_spa, rif->src_ip, sizeof(pkt.arp.arp_spa)); 169 | memset(pkt.arp.arp_tha, 0, ETH_ALEN); 170 | memset(pkt.eth.ether_dhost, 0xff, ETH_ALEN); 171 | 172 | DPRINTF(2, "%s: sending ARP who-has "IP_FMT", tell "IP_FMT" ("MAC_FMT")\n", 173 | rif->ifname, IP_BUF(pkt.arp.arp_tpa), 174 | IP_BUF(pkt.arp.arp_spa), MAC_BUF(pkt.eth.ether_shost)); 175 | 176 | sendto(rif->fd.fd, &pkt, sizeof(pkt), 0, 177 | (struct sockaddr *) &rif->sll, sizeof(rif->sll)); 178 | } 179 | 180 | void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, uint8_t mask, int timeout) 181 | { 182 | struct relayd_pending_route *rt; 183 | struct relayd_interface *rif; 184 | struct relayd_host *host; 185 | 186 | host = find_host_by_ipaddr(NULL, gateway); 187 | if (host) { 188 | relayd_add_host_route(host, dest, mask); 189 | return; 190 | } 191 | 192 | rt = calloc(1, sizeof(*rt)); 193 | if (!rt) 194 | return; 195 | 196 | memcpy(rt->gateway, gateway, sizeof(rt->gateway)); 197 | memcpy(rt->rt.dest, dest, sizeof(rt->rt.dest)); 198 | rt->rt.mask = mask; 199 | list_add(&rt->rt.list, &pending_routes); 200 | if (timeout <= 0) 201 | return; 202 | 203 | rt->timeout.cb = timeout_host_route; 204 | uloop_timeout_set(&rt->timeout, 10000); 205 | list_for_each_entry(rif, &interfaces, list) { 206 | send_arp_request(rif, gateway); 207 | } 208 | } 209 | 210 | static void send_arp_reply(struct relayd_interface *rif, const uint8_t spa[4], 211 | const uint8_t tha[ETH_ALEN], const uint8_t tpa[4]) 212 | { 213 | struct arp_packet pkt; 214 | 215 | fill_arp_packet(&pkt, rif, spa, tpa); 216 | 217 | if (tha) { 218 | pkt.arp.arp_op = htons(ARPOP_REPLY); 219 | memcpy(pkt.eth.ether_dhost, tha, ETH_ALEN); 220 | memcpy(pkt.arp.arp_tha, tha, ETH_ALEN); 221 | 222 | DPRINTF(2, "%s: sending ARP reply to "IP_FMT", "IP_FMT" is at ("MAC_FMT")\n", 223 | rif->ifname, IP_BUF(pkt.arp.arp_tpa), 224 | IP_BUF(pkt.arp.arp_spa), MAC_BUF(pkt.eth.ether_shost)); 225 | } else { 226 | pkt.arp.arp_op = htons(ARPOP_REQUEST); 227 | memset(pkt.eth.ether_dhost, 0xff, ETH_ALEN); 228 | memset(pkt.arp.arp_tha, 0xff, ETH_ALEN); 229 | 230 | DPRINTF(2, "%s: sending gratuitous ARP: "IP_FMT" is at ("MAC_FMT")\n", 231 | rif->ifname, IP_BUF(pkt.arp.arp_tpa), 232 | MAC_BUF(pkt.eth.ether_shost)); 233 | } 234 | 235 | sendto(rif->fd.fd, &pkt, sizeof(pkt), 0, 236 | (struct sockaddr *) &rif->sll, sizeof(rif->sll)); 237 | 238 | if (tha) 239 | return; 240 | 241 | /* 242 | * Gratuitous ARP comes in two flavours, request and reply. 243 | * Some operating systems only accept request, some only reply. 244 | * Let's just send both... 245 | */ 246 | pkt.arp.arp_op = htons(ARPOP_REPLY); 247 | 248 | sendto(rif->fd.fd, &pkt, sizeof(pkt), 0, 249 | (struct sockaddr *) &rif->sll, sizeof(rif->sll)); 250 | 251 | } 252 | 253 | static void host_entry_timeout(struct uloop_timeout *timeout) 254 | { 255 | struct relayd_host *host = container_of(timeout, struct relayd_host, timeout); 256 | struct relayd_interface *rif; 257 | 258 | /* 259 | * When a host is behind a managed interface, we must not expire its host 260 | * entry prematurely, as this will cause routes to the node to expire, 261 | * leading to loss of connectivity from the other side. 262 | * When the timeout is reached, try pinging the host a few times before 263 | * giving up on it. 264 | */ 265 | if (host->rif->managed && host->cleanup_pending < host_ping_tries) { 266 | list_for_each_entry(rif, &interfaces, list) { 267 | send_arp_request(rif, host->ipaddr); 268 | } 269 | host->cleanup_pending++; 270 | uloop_timeout_set(&host->timeout, 1000); 271 | return; 272 | } 273 | del_host(host); 274 | } 275 | 276 | static struct relayd_host *add_host(struct relayd_interface *rif, const uint8_t *lladdr, const uint8_t *ipaddr) 277 | { 278 | struct relayd_host *host; 279 | struct relayd_pending_route *route, *rtmp; 280 | 281 | DPRINTF(1, "%s: adding host "IP_FMT" ("MAC_FMT")\n", rif->ifname, 282 | IP_BUF(ipaddr), MAC_BUF(lladdr)); 283 | 284 | host = calloc(1, sizeof(*host)); 285 | INIT_LIST_HEAD(&host->routes); 286 | host->rif = rif; 287 | memcpy(host->ipaddr, ipaddr, sizeof(host->ipaddr)); 288 | memcpy(host->lladdr, lladdr, sizeof(host->lladdr)); 289 | list_add(&host->list, &rif->hosts); 290 | host->timeout.cb = host_entry_timeout; 291 | uloop_timeout_set(&host->timeout, host_timeout * 1000); 292 | 293 | add_arp(host); 294 | if (rif->managed) 295 | relayd_add_route(host, NULL); 296 | 297 | list_for_each_entry_safe(route, rtmp, &pending_routes, rt.list) { 298 | if (memcmp(route->gateway, ipaddr, 4) != 0) 299 | continue; 300 | 301 | relayd_add_host_route(host, route->rt.dest, route->rt.mask); 302 | if (!route->timeout.pending) 303 | continue; 304 | 305 | uloop_timeout_cancel(&route->timeout); 306 | list_del(&route->rt.list); 307 | free(route); 308 | } 309 | 310 | return host; 311 | } 312 | 313 | static void send_gratuitous_arp(struct relayd_interface *rif, const uint8_t *spa) 314 | { 315 | struct relayd_interface *to_rif; 316 | 317 | list_for_each_entry(to_rif, &interfaces, list) { 318 | if (rif == to_rif) 319 | continue; 320 | 321 | send_arp_reply(to_rif, spa, NULL, spa); 322 | } 323 | } 324 | 325 | 326 | struct relayd_host *relayd_refresh_host(struct relayd_interface *rif, const uint8_t *lladdr, const uint8_t *ipaddr) 327 | { 328 | struct relayd_host *host; 329 | 330 | host = find_host_by_ipaddr(rif, ipaddr); 331 | if (!host) { 332 | host = find_host_by_ipaddr(NULL, ipaddr); 333 | 334 | /* 335 | * When we suddenly see the host appearing on a different interface, 336 | * reduce the timeout to make the old entry expire faster, in case the 337 | * host has moved. 338 | * If the old entry is behind a managed interface, it will be pinged 339 | * before we expire it 340 | */ 341 | if (host && !host->cleanup_pending) { 342 | uloop_timeout_set(&host->timeout, 1); 343 | return NULL; 344 | } 345 | 346 | host = add_host(rif, lladdr, ipaddr); 347 | } else { 348 | host->cleanup_pending = false; 349 | uloop_timeout_set(&host->timeout, host_timeout * 1000); 350 | send_gratuitous_arp(rif, ipaddr); 351 | } 352 | 353 | return host; 354 | } 355 | 356 | static void relay_arp_request(struct relayd_interface *from_rif, struct arp_packet *pkt) 357 | { 358 | struct relayd_interface *rif; 359 | struct arp_packet reqpkt; 360 | 361 | memcpy(&reqpkt, pkt, sizeof(reqpkt)); 362 | list_for_each_entry(rif, &interfaces, list) { 363 | if (rif == from_rif) 364 | continue; 365 | 366 | memcpy(reqpkt.eth.ether_shost, rif->sll.sll_addr, ETH_ALEN); 367 | memset(reqpkt.eth.ether_dhost, 0xff, ETH_ALEN); 368 | memcpy(reqpkt.arp.arp_sha, rif->sll.sll_addr, ETH_ALEN); 369 | memset(reqpkt.arp.arp_tha, 0, ETH_ALEN); 370 | 371 | DPRINTF(2, "%s: sending ARP who-has "IP_FMT", tell "IP_FMT" ("MAC_FMT")\n", 372 | rif->ifname, IP_BUF(reqpkt.arp.arp_tpa), 373 | IP_BUF(reqpkt.arp.arp_spa), MAC_BUF(reqpkt.eth.ether_shost)); 374 | 375 | sendto(rif->fd.fd, &reqpkt, sizeof(reqpkt), 0, 376 | (struct sockaddr *) &rif->sll, sizeof(rif->sll)); 377 | } 378 | } 379 | 380 | static void recv_arp_request(struct relayd_interface *rif, struct arp_packet *pkt) 381 | { 382 | struct relayd_host *host; 383 | 384 | DPRINTF(2, "%s: ARP who-has "IP_FMT", tell "IP_FMT" ("MAC_FMT")\n", 385 | rif->ifname, 386 | IP_BUF(pkt->arp.arp_tpa), 387 | IP_BUF(pkt->arp.arp_spa), 388 | MAC_BUF(pkt->eth.ether_shost)); 389 | 390 | if (!memcmp(pkt->arp.arp_spa, "\x00\x00\x00\x00", 4)) 391 | return; 392 | 393 | host = find_host_by_ipaddr(NULL, pkt->arp.arp_spa); 394 | if (!host || host->rif != rif) 395 | relayd_refresh_host(rif, pkt->eth.ether_shost, pkt->arp.arp_spa); 396 | 397 | if (local_route_table && !memcmp(pkt->arp.arp_tpa, local_addr, sizeof(local_addr))) { 398 | send_arp_reply(rif, local_addr, pkt->arp.arp_sha, pkt->arp.arp_spa); 399 | return; 400 | } 401 | 402 | host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa); 403 | 404 | /* 405 | * If a host is being pinged because of a timeout, do not use the cached 406 | * entry here. That way we can avoid giving out stale data in case the node 407 | * has moved. We shouldn't relay requests here either, as we might miss our 408 | * chance to create a host route. 409 | */ 410 | if (host && host->cleanup_pending) 411 | return; 412 | 413 | relay_arp_request(rif, pkt); 414 | } 415 | 416 | static void recv_arp_reply(struct relayd_interface *rif, struct arp_packet *pkt) 417 | { 418 | struct relayd_host *host; 419 | 420 | DPRINTF(2, "%s: received ARP reply for "IP_FMT" from "MAC_FMT", deliver to "IP_FMT"\n", 421 | rif->ifname, 422 | IP_BUF(pkt->arp.arp_spa), 423 | MAC_BUF(pkt->eth.ether_shost), 424 | IP_BUF(pkt->arp.arp_tpa)); 425 | 426 | if (memcmp(pkt->arp.arp_sha, rif->sll.sll_addr, ETH_ALEN) != 0) 427 | relayd_refresh_host(rif, pkt->arp.arp_sha, pkt->arp.arp_spa); 428 | 429 | host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa); 430 | if (!host) 431 | return; 432 | 433 | if (host->rif == rif) 434 | return; 435 | 436 | send_arp_reply(host->rif, pkt->arp.arp_spa, host->lladdr, host->ipaddr); 437 | } 438 | 439 | static void recv_packet(struct uloop_fd *fd, unsigned int events) 440 | { 441 | struct relayd_interface *rif = container_of(fd, struct relayd_interface, fd); 442 | struct arp_packet *pkt; 443 | static char pktbuf[4096]; 444 | int pktlen; 445 | 446 | do { 447 | if (rif->fd.error) 448 | uloop_end(); 449 | 450 | pktlen = recv(rif->fd.fd, pktbuf, sizeof(pktbuf), 0); 451 | if (pktlen < 0) { 452 | if (errno == EINTR) 453 | continue; 454 | 455 | break; 456 | } 457 | 458 | if (!pktlen) 459 | break; 460 | 461 | pkt = (void *)pktbuf; 462 | if (pkt->arp.arp_op == htons(ARPOP_REPLY)) 463 | recv_arp_reply(rif, pkt); 464 | else if (pkt->arp.arp_op == htons(ARPOP_REQUEST)) 465 | recv_arp_request(rif, pkt); 466 | else 467 | DPRINTF(1, "received unknown packet type: %04x\n", ntohs(pkt->arp.arp_op)); 468 | 469 | } while (1); 470 | } 471 | 472 | void relayd_forward_bcast_packet(struct relayd_interface *from_rif, void *packet, int len) 473 | { 474 | struct relayd_interface *rif; 475 | struct ether_header *eth = packet; 476 | 477 | list_for_each_entry(rif, &interfaces, list) { 478 | if (rif == from_rif) 479 | continue; 480 | 481 | DPRINTF(3, "%s: forwarding broadcast packet to %s\n", from_rif->ifname, rif->ifname); 482 | memcpy(eth->ether_shost, rif->sll.sll_addr, ETH_ALEN); 483 | send(rif->bcast_fd.fd, packet, len, 0); 484 | } 485 | } 486 | 487 | static void recv_bcast_packet(struct uloop_fd *fd, unsigned int events) 488 | { 489 | struct relayd_interface *rif = container_of(fd, struct relayd_interface, bcast_fd); 490 | static char pktbuf[4096]; 491 | int pktlen; 492 | 493 | do { 494 | if (rif->fd.error) 495 | uloop_end(); 496 | 497 | pktlen = recv(rif->bcast_fd.fd, pktbuf, sizeof(pktbuf), 0); 498 | if (pktlen < 0) { 499 | if (errno == EINTR) 500 | continue; 501 | 502 | break; 503 | } 504 | 505 | if (!pktlen) 506 | break; 507 | 508 | if (!forward_bcast && !forward_dhcp) 509 | continue; 510 | 511 | if (relayd_handle_dhcp_packet(rif, pktbuf, pktlen, forward_dhcp, parse_dhcp)) 512 | continue; 513 | 514 | if (forward_bcast) 515 | relayd_forward_bcast_packet(rif, pktbuf, pktlen); 516 | } while (1); 517 | } 518 | 519 | 520 | static int init_interface(struct relayd_interface *rif) 521 | { 522 | struct sockaddr_ll *sll = &rif->sll; 523 | struct sockaddr_in *sin; 524 | struct ifreq ifr; 525 | int fd = rif->fd.fd; 526 | #ifdef PACKET_RECV_TYPE 527 | unsigned int pkt_type; 528 | #endif 529 | 530 | fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); 531 | if (fd < 0) 532 | return -1; 533 | 534 | rif->fd.fd = fd; 535 | 536 | memset(&ifr, 0, sizeof(ifr)); 537 | strcpy(ifr.ifr_name, rif->ifname); 538 | 539 | if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { 540 | perror("ioctl(SIOCGIFHWADDR)"); 541 | return -1; 542 | } 543 | 544 | memcpy(sll->sll_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); 545 | sll->sll_family = AF_PACKET; 546 | sll->sll_protocol = htons(ETH_P_ARP); 547 | sll->sll_pkttype = PACKET_BROADCAST; 548 | sll->sll_hatype = ARPHRD_ETHER; 549 | sll->sll_halen = ETH_ALEN; 550 | 551 | if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) { 552 | perror("ioctl(SIOCGIFINDEX)"); 553 | return -1; 554 | } 555 | 556 | sll->sll_ifindex = ifr.ifr_ifindex; 557 | 558 | if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { 559 | memcpy(rif->src_ip, DUMMY_IP, sizeof(rif->src_ip)); 560 | } else { 561 | sin = (struct sockaddr_in *) &ifr.ifr_addr; 562 | memcpy(rif->src_ip, &sin->sin_addr.s_addr, sizeof(rif->src_ip)); 563 | } 564 | 565 | if (bind(fd, (struct sockaddr *)sll, sizeof(struct sockaddr_ll)) < 0) { 566 | perror("bind(ETH_P_ARP)"); 567 | return -1; 568 | } 569 | 570 | rif->fd.cb = recv_packet; 571 | uloop_fd_add(&rif->fd, ULOOP_READ | ULOOP_EDGE_TRIGGER); 572 | 573 | if (!forward_bcast && !forward_dhcp) 574 | return 0; 575 | 576 | fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); 577 | if (fd < 0) 578 | return 0; 579 | 580 | rif->bcast_fd.fd = fd; 581 | rif->bcast_fd.cb = recv_bcast_packet; 582 | 583 | memcpy(&rif->bcast_sll, &rif->sll, sizeof(rif->bcast_sll)); 584 | sll = &rif->bcast_sll; 585 | sll->sll_protocol = htons(ETH_P_IP); 586 | 587 | if (bind(fd, (struct sockaddr *)sll, sizeof(struct sockaddr_ll)) < 0) { 588 | perror("bind(ETH_P_IP)"); 589 | return 0; 590 | } 591 | 592 | #ifdef PACKET_RECV_TYPE 593 | pkt_type = (1 << PACKET_BROADCAST) | (1 << PACKET_MULTICAST); 594 | setsockopt(fd, SOL_PACKET, PACKET_RECV_TYPE, &pkt_type, sizeof(pkt_type)); 595 | #endif 596 | 597 | uloop_fd_add(&rif->bcast_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER); 598 | relayd_add_interface_routes(rif); 599 | return 0; 600 | } 601 | 602 | static void ping_static_routes(void) 603 | { 604 | struct relayd_pending_route *rt; 605 | struct relayd_interface *rif; 606 | 607 | list_for_each_entry(rt, &pending_routes, rt.list) 608 | list_for_each_entry(rif, &interfaces, list) 609 | send_arp_request(rif, rt->gateway); 610 | } 611 | 612 | static int init_interfaces(void) 613 | { 614 | struct relayd_interface *rif; 615 | int ret; 616 | 617 | list_for_each_entry(rif, &interfaces, list) { 618 | ret = init_interface(rif); 619 | if (ret < 0) 620 | return ret; 621 | } 622 | 623 | return 0; 624 | } 625 | 626 | static void cleanup_hosts(void) 627 | { 628 | struct relayd_interface *rif; 629 | struct relayd_host *host, *tmp; 630 | 631 | list_for_each_entry(rif, &interfaces, list) { 632 | list_for_each_entry_safe(host, tmp, &rif->hosts, list) { 633 | del_host(host); 634 | } 635 | } 636 | } 637 | 638 | static void free_interfaces(void) 639 | { 640 | struct relayd_interface *rif, *rtmp; 641 | 642 | list_for_each_entry_safe(rif, rtmp, &interfaces, list) { 643 | relayd_del_interface_routes(rif); 644 | list_del(&rif->list); 645 | free(rif); 646 | } 647 | } 648 | 649 | static struct relayd_interface *alloc_interface(const char *ifname, bool managed) 650 | { 651 | struct relayd_interface *rif; 652 | 653 | if (strlen(ifname) >= IFNAMSIZ) 654 | return NULL; 655 | 656 | list_for_each_entry(rif, &interfaces, list) { 657 | if (!strncmp(rif->ifname, ifname, IFNAMSIZ)) 658 | return rif; 659 | } 660 | 661 | rif = calloc(1, sizeof(*rif)); 662 | if (!rif) 663 | return NULL; 664 | 665 | INIT_LIST_HEAD(&rif->hosts); 666 | strcpy(rif->ifname, ifname); 667 | list_add(&rif->list, &interfaces); 668 | rif->managed = managed; 669 | 670 | return rif; 671 | } 672 | 673 | static void die(int signo) 674 | { 675 | /* 676 | * When we hit SIGTERM, clean up interfaces directly, so that we 677 | * won't leave our routing in an invalid state. 678 | */ 679 | uloop_end(); 680 | } 681 | 682 | static int usage(const char *progname) 683 | { 684 | fprintf(stderr, "Usage: %s \n" 685 | "\n" 686 | "Options:\n" 687 | " -d Enable debug messages\n" 688 | " -i Add an interface for relaying\n" 689 | " -I Same as -i, except with ARP cache and host route management\n" 690 | " You need to specify at least two interfaces\n" 691 | " -G Set a gateway IP for clients\n" 692 | " -R :/\n" 693 | " Add a static route for / via \n" 694 | " -t Host entry expiry timeout\n" 695 | " -p Number of ARP ping attempts before considering a host dead\n" 696 | " -T Set routing table number for automatically added routes\n" 697 | " -B Enable broadcast forwarding\n" 698 | " -D Enable DHCP forwarding\n" 699 | " -P Disable DHCP options parsing\n" 700 | " -L Enable local access using as source address\n" 701 | "\n", 702 | progname); 703 | return -1; 704 | } 705 | 706 | int main(int argc, char **argv) 707 | { 708 | struct relayd_interface *rif = NULL; 709 | struct in_addr addr, addr2; 710 | bool local_addr_valid = false; 711 | bool managed = false; 712 | int ifnum = 0; 713 | char *s, *s2; 714 | int mask; 715 | int ch; 716 | 717 | debug = 0; 718 | inet_sock = socket(AF_INET, SOCK_DGRAM, 0); 719 | if (inet_sock < 0) { 720 | perror("socket(AF_INET)"); 721 | return 1; 722 | } 723 | 724 | host_timeout = 30; 725 | host_ping_tries = 5; 726 | forward_bcast = 0; 727 | local_route_table = 0; 728 | parse_dhcp = 1; 729 | uloop_init(); 730 | 731 | while ((ch = getopt(argc, argv, "I:i:t:p:BDPdT:G:R:L:")) != -1) { 732 | switch(ch) { 733 | case 'I': 734 | managed = true; 735 | /* fall through */ 736 | case 'i': 737 | ifnum++; 738 | rif = alloc_interface(optarg, managed); 739 | if (!rif) 740 | return 1; 741 | 742 | managed = false; 743 | break; 744 | case 't': 745 | host_timeout = atoi(optarg); 746 | if (host_timeout <= 0) 747 | return usage(argv[0]); 748 | break; 749 | case 'p': 750 | host_ping_tries = atoi(optarg); 751 | if (host_ping_tries <= 0) 752 | return usage(argv[0]); 753 | break; 754 | case 'd': 755 | debug++; 756 | break; 757 | case 'B': 758 | forward_bcast = 1; 759 | break; 760 | case 'D': 761 | forward_dhcp = 1; 762 | break; 763 | case 'P': 764 | parse_dhcp = 0; 765 | break; 766 | case 'T': 767 | route_table = atoi(optarg); 768 | if (route_table <= 0) 769 | return usage(argv[0]); 770 | break; 771 | case 'G': 772 | if (!inet_aton(optarg, &addr)) { 773 | fprintf(stderr, "Address '%s' not found\n", optarg); 774 | return 1; 775 | } 776 | relayd_add_pending_route((uint8_t *) &addr.s_addr, (const uint8_t *) "\x00\x00\x00\x00", 0, 0); 777 | break; 778 | case 'L': 779 | if (!inet_aton(optarg, &addr)) { 780 | fprintf(stderr, "Address '%s' not found\n", optarg); 781 | return 1; 782 | } 783 | memcpy(&local_addr, &addr.s_addr, sizeof(local_addr)); 784 | local_addr_valid = true; 785 | break; 786 | case 'R': 787 | s = strchr(optarg, ':'); 788 | if (!s) 789 | return usage(argv[0]); 790 | 791 | *(s++) = 0; 792 | if (!inet_aton(optarg, &addr)) { 793 | fprintf(stderr, "Address '%s' not found\n", optarg); 794 | return 1; 795 | } 796 | 797 | s2 = strchr(s, '/'); 798 | if (!s2) 799 | return usage(argv[0]); 800 | 801 | *(s2++) = 0; 802 | if (!inet_aton(s, &addr2)) { 803 | fprintf(stderr, "Address '%s' not found\n", s); 804 | return 1; 805 | } 806 | 807 | mask = atoi(s2); 808 | if (mask < 0 || mask > 32) 809 | return usage(argv[0]); 810 | 811 | relayd_add_pending_route((uint8_t *) &addr.s_addr, (uint8_t *) &addr2.s_addr, mask, 0); 812 | break; 813 | case '?': 814 | default: 815 | return usage(argv[0]); 816 | } 817 | } 818 | 819 | if (list_empty(&interfaces)) 820 | return usage(argv[0]); 821 | 822 | if (ifnum < 2) { 823 | fprintf(stderr, "ERROR: Need at least 2 interfaces for relaying\n"); 824 | return -1; 825 | } 826 | 827 | argc -= optind; 828 | argv += optind; 829 | 830 | signal(SIGTERM, die); 831 | signal(SIGHUP, die); 832 | signal(SIGUSR1, die); 833 | signal(SIGUSR2, die); 834 | 835 | if (local_addr_valid) 836 | local_route_table = route_table++; 837 | 838 | if (relayd_rtnl_init() < 0) 839 | return 1; 840 | 841 | if (init_interfaces() < 0) 842 | return 1; 843 | 844 | ping_static_routes(); 845 | 846 | uloop_run(); 847 | uloop_done(); 848 | 849 | cleanup_hosts(); 850 | free_interfaces(); 851 | relayd_rtnl_done(); 852 | close(inet_sock); 853 | 854 | return 0; 855 | } 856 | --------------------------------------------------------------------------------