├── .gitignore ├── Dockerfile ├── README.md ├── mdns-repeater.c └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine AS builder 2 | ARG MDNS_REPEATER_VERSION=local 3 | ADD mdns-repeater.c mdns-repeater.c 4 | RUN set -ex && \ 5 | apk add build-base && \ 6 | gcc -o /bin/mdns-repeater mdns-repeater.c -DMDNS_REPEATER_VERSION=\"${MDNS_REPEATER_VERSION}\" 7 | 8 | FROM alpine 9 | 10 | RUN set -ex && \ 11 | apk add vlan libcap bash 12 | COPY --from=builder /bin/mdns-repeater /bin/mdns-repeater 13 | RUN chmod +x /bin/mdns-repeater 14 | RUN setcap cap_net_raw=+ep /bin/mdns-repeater 15 | 16 | COPY run.sh /app/ 17 | RUN chmod +x /app/run.sh 18 | 19 | ENTRYPOINT ["/app/run.sh"] 20 | CMD ["/bin/mdns-repeater", "-f", "eth0.20", "eth0.100"] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | As of RouterOS 7.16, Mikrotik now has a [built-in mDNS repeater](https://help.mikrotik.com/docs/spaces/ROS/pages/37748767/DNS#DNS-mDNS) implementation: e.g. 3 | `/ip/dns/set mdns-repeat-ifaces=bridge,vlan-iot` 4 | 5 | 6 | # docker-mdns-repeater-mikrotik 7 | mdns-repeater in mikrotik container 8 | 9 | Based on: 10 | https://github.com/geekman/mdns-repeater 11 | https://github.com/monstrenyatko/docker-mdns-repeater 12 | 13 | This is work in progress, but you can base your config on that. 14 | 15 | ## Mikrotik config 16 | Based on official docs https://help.mikrotik.com/docs/display/ROS/Container 17 | Instead of adding `veth2` to docker bridge i've added it to my home-lan bridge `BR1`. 18 | Veth2 is added as tagged port with two vlans (20,100), so in container on `eth0` i will create two vlan interfaces `eth0.20` and `eth0.100` with active dhcp client for IP leese, please look at `run.sh`. 19 | 20 | ``` 21 | /interface/bridge/port/print 22 | Flags: I - INACTIVE; H - HW-OFFLOAD 23 | Columns: INTERFACE, BRIDGE, HW, PVID, PRIORITY, PATH-COST, INTERNAL-PATH-COST, HOR 24 | IZON 25 | # INTERFACE BRIDGE HW PVID PRIORITY PATH-COST IN HORIZON 26 | 10 veth2 BR1 1 0x80 10 10 none 27 | 28 | 29 | /interface/bridge/vlan/print 30 | Flags: D - DYNAMIC 31 | Columns: BRIDGE, VLAN-IDS, CURRENT-TAGGED, CURRENT-UNTAGGED 32 | # BRIDGE VLAN-IDS CURRENT-TAGGED CURRENT-UNTAGGED 33 | 0 BR1 100 BR1 34 | veth2 35 | 3 BR1 20 BR1 36 | veth2 37 | 4 D BR1 1 BR1 38 | veth2 39 | ``` 40 | 41 | ## Build & pack container 42 | ``` 43 | docker buildx build --no-cache --platform linux/arm/v6 -t mdns . 44 | docker save mdns -o mdns.tar 45 | 8.8M mdns.tar # size after pack 46 | ``` 47 | 48 | ## Logs from running container 49 | ``` 50 | log print where topics~"container" 51 | jun/29 22:01:28 container,info,debug create interface eth0.20 52 | jun/29 22:01:28 container,info,debug bring up eth0.20 interface 53 | jun/29 22:01:28 container,info,debug /app/run.sh: line 25: kill: (19) - No such process 54 | jun/29 22:01:28 container,info,debug starting dhcp client on eth0.20 55 | jun/29 22:01:28 container,info,debug udhcpc: started, v1.35.0 56 | jun/29 22:01:29 container,info,debug udhcpc: broadcasting discover 57 | jun/29 22:01:29 container,info,debug udhcpc: broadcasting select for 10.0.20.27, server 10.0.20.1 58 | jun/29 22:01:29 container,info,debug udhcpc: lease of 10.0.20.27 obtained from 10.0.20.1, lease time 86400 59 | jun/29 22:01:29 container,info,debug create interface eth0.100 60 | jun/29 22:01:29 container,info,debug bring up eth0.100 interface 61 | jun/29 22:01:29 container,info,debug /app/run.sh: line 25: kill: (34) - No such process 62 | jun/29 22:01:29 container,info,debug starting dhcp client on eth0.100 63 | jun/29 22:01:29 container,info,debug udhcpc: started, v1.35.0 64 | jun/29 22:01:29 container,info,debug udhcpc: broadcasting discover 65 | jun/29 22:01:30 container,info,debug udhcpc: broadcasting select for 10.0.100.244, server 10.0.100.1 66 | jun/29 22:01:30 container,info,debug udhcpc: lease of 10.0.100.244 obtained from 10.0.100.1, lease time 86400 67 | jun/29 22:01:30 container,info,debug + exec /bin/mdns-repeater -f eth0.20 eth0.100 68 | jun/29 22:01:30 container,info,debug mdns-repeater: dev eth0.20 addr 10.0.20.27 mask 255.255.255.0 net 10.0.20.0 69 | jun/29 22:01:30 container,info,debug mdns-repeater: dev eth0.100 addr 10.0.100.244 mask 255.255.255.0 net 10.0.100.0 70 | jul/01 21:49:34 container,info,debug bring up eth0.20 interface 71 | jul/01 21:49:34 container,info,debug /app/run.sh: line 25: kill: (22) - No such process 72 | jul/01 21:49:34 container,info,debug starting dhcp client on eth0.20 73 | jul/01 21:49:34 container,info,debug udhcpc: started, v1.35.0 74 | jul/01 21:49:34 container,info,debug udhcpc: broadcasting discover 75 | jul/01 21:49:34 container,info,debug udhcpc: broadcasting select for 10.0.20.27, server 10.0.20.1 76 | jul/01 21:49:34 container,info,debug udhcpc: lease of 10.0.20.27 obtained from 10.0.20.1, lease time 86400 77 | jul/01 21:49:34 container,info,debug bring up eth0.100 interface 78 | jul/01 21:49:34 container,info,debug /app/run.sh: line 25: kill: (40) - No such process 79 | jul/01 21:49:34 container,info,debug starting dhcp client on eth0.100 80 | jul/01 21:49:34 container,info,debug udhcpc: started, v1.35.0 81 | jul/01 21:49:34 container,info,debug udhcpc: broadcasting discover 82 | jul/01 21:49:35 container,info,debug udhcpc: broadcasting select for 10.0.100.244, server 10.0.100.1 83 | jul/01 21:49:35 container,info,debug udhcpc: lease of 10.0.100.244 obtained from 10.0.100.1, lease time 86400 84 | jul/01 21:49:35 container,info,debug + exec /bin/mdns-repeater -f eth0.20 eth0.100 85 | jul/01 21:49:35 container,info,debug mdns-repeater: dev eth0.20 addr 10.0.20.27 mask 255.255.255.0 net 10.0.20.0 86 | jul/01 21:49:35 container,info,debug mdns-repeater: dev eth0.100 addr 10.0.100.244 mask 255.255.255.0 net 10.0.100.0 87 | ``` 88 | -------------------------------------------------------------------------------- /mdns-repeater.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mdns-repeater.c - mDNS repeater daemon 3 | * Copyright (C) 2011 Darell Tan 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define PACKAGE "mdns-repeater" 38 | #define MDNS_ADDR "224.0.0.251" 39 | #define MDNS_PORT 5353 40 | 41 | #ifndef PIDFILE 42 | #define PIDFILE "/var/run/" PACKAGE ".pid" 43 | #endif 44 | 45 | #define MAX_SOCKS 16 46 | #define MAX_SUBNETS 16 47 | 48 | struct if_sock { 49 | const char *ifname; /* interface name */ 50 | int sockfd; /* socket filedesc */ 51 | struct in_addr addr; /* interface addr */ 52 | struct in_addr mask; /* interface mask */ 53 | struct in_addr net; /* interface network (computed) */ 54 | }; 55 | 56 | struct subnet { 57 | struct in_addr addr; /* subnet addr */ 58 | struct in_addr mask; /* subnet mask */ 59 | struct in_addr net; /* subnet net (computed) */ 60 | }; 61 | 62 | int server_sockfd = -1; 63 | 64 | int num_socks = 0; 65 | struct if_sock socks[MAX_SOCKS]; 66 | 67 | int num_blacklisted_subnets = 0; 68 | struct subnet blacklisted_subnets[MAX_SUBNETS]; 69 | 70 | int num_whitelisted_subnets = 0; 71 | struct subnet whitelisted_subnets[MAX_SUBNETS]; 72 | 73 | #define PACKET_SIZE 65536 74 | void *pkt_data = NULL; 75 | 76 | int foreground = 0; 77 | int debug = 0; 78 | int shutdown_flag = 0; 79 | 80 | char *pid_file = PIDFILE; 81 | 82 | void log_message(int loglevel, char *fmt_str, ...) { 83 | va_list ap; 84 | char buf[2048]; 85 | 86 | va_start(ap, fmt_str); 87 | vsnprintf(buf, 2047, fmt_str, ap); 88 | va_end(ap); 89 | buf[2047] = 0; 90 | 91 | if (foreground) { 92 | fprintf(stderr, "%s: %s\n", PACKAGE, buf); 93 | } else { 94 | syslog(loglevel, "%s", buf); 95 | } 96 | } 97 | 98 | static int create_recv_sock() { 99 | int sd = socket(AF_INET, SOCK_DGRAM, 0); 100 | if (sd < 0) { 101 | log_message(LOG_ERR, "recv socket(): %s", strerror(errno)); 102 | return sd; 103 | } 104 | 105 | int r = -1; 106 | 107 | int on = 1; 108 | if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) { 109 | log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %s", strerror(errno)); 110 | return r; 111 | } 112 | 113 | /* bind to an address */ 114 | struct sockaddr_in serveraddr; 115 | memset(&serveraddr, 0, sizeof(serveraddr)); 116 | serveraddr.sin_family = AF_INET; 117 | serveraddr.sin_port = htons(MDNS_PORT); 118 | serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ 119 | if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { 120 | log_message(LOG_ERR, "recv bind(): %s", strerror(errno)); 121 | } 122 | 123 | // enable loopback in case someone else needs the data 124 | if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on))) < 0) { 125 | log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %s", strerror(errno)); 126 | return r; 127 | } 128 | 129 | #ifdef IP_PKTINFO 130 | if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, &on, sizeof(on))) < 0) { 131 | log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %s", strerror(errno)); 132 | return r; 133 | } 134 | #endif 135 | 136 | return sd; 137 | } 138 | 139 | static int create_send_sock(int recv_sockfd, const char *ifname, struct if_sock *sockdata) { 140 | int sd = socket(AF_INET, SOCK_DGRAM, 0); 141 | if (sd < 0) { 142 | log_message(LOG_ERR, "send socket(): %s", strerror(errno)); 143 | return sd; 144 | } 145 | 146 | sockdata->ifname = ifname; 147 | sockdata->sockfd = sd; 148 | 149 | int r = -1; 150 | 151 | struct ifreq ifr; 152 | memset(&ifr, 0, sizeof(ifr)); 153 | strncpy(ifr.ifr_name, ifname, IFNAMSIZ); 154 | struct in_addr *if_addr = &((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; 155 | 156 | #ifdef SO_BINDTODEVICE 157 | if ((r = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(struct ifreq))) < 0) { 158 | log_message(LOG_ERR, "send setsockopt(SO_BINDTODEVICE): %s", strerror(errno)); 159 | return r; 160 | } 161 | #endif 162 | 163 | // get netmask 164 | if (ioctl(sd, SIOCGIFNETMASK, &ifr) == 0) { 165 | memcpy(&sockdata->mask, if_addr, sizeof(struct in_addr)); 166 | } 167 | 168 | // .. and interface address 169 | if (ioctl(sd, SIOCGIFADDR, &ifr) == 0) { 170 | memcpy(&sockdata->addr, if_addr, sizeof(struct in_addr)); 171 | } 172 | 173 | // compute network (address & mask) 174 | sockdata->net.s_addr = sockdata->addr.s_addr & sockdata->mask.s_addr; 175 | 176 | int on = 1; 177 | if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) { 178 | log_message(LOG_ERR, "send setsockopt(SO_REUSEADDR): %s", strerror(errno)); 179 | return r; 180 | } 181 | 182 | // bind to an address 183 | struct sockaddr_in serveraddr; 184 | memset(&serveraddr, 0, sizeof(serveraddr)); 185 | serveraddr.sin_family = AF_INET; 186 | serveraddr.sin_port = htons(MDNS_PORT); 187 | serveraddr.sin_addr.s_addr = if_addr->s_addr; 188 | if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { 189 | log_message(LOG_ERR, "send bind(): %s", strerror(errno)); 190 | } 191 | 192 | #if __FreeBSD__ 193 | if((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &serveraddr.sin_addr, sizeof(serveraddr.sin_addr))) < 0) { 194 | log_message(LOG_ERR, "send ip_multicast_if(): errno %d: %s", errno, strerror(errno)); 195 | } 196 | #endif 197 | 198 | // add membership to receiving socket 199 | struct ip_mreq mreq; 200 | memset(&mreq, 0, sizeof(struct ip_mreq)); 201 | mreq.imr_interface.s_addr = if_addr->s_addr; 202 | mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR); 203 | if ((r = setsockopt(recv_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) < 0) { 204 | log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %s", strerror(errno)); 205 | return r; 206 | } 207 | 208 | // enable loopback in case someone else needs the data 209 | if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on))) < 0) { 210 | log_message(LOG_ERR, "send setsockopt(IP_MULTICAST_LOOP): %s", strerror(errno)); 211 | return r; 212 | } 213 | 214 | char *addr_str = strdup(inet_ntoa(sockdata->addr)); 215 | char *mask_str = strdup(inet_ntoa(sockdata->mask)); 216 | char *net_str = strdup(inet_ntoa(sockdata->net)); 217 | log_message(LOG_INFO, "dev %s addr %s mask %s net %s", ifr.ifr_name, addr_str, mask_str, net_str); 218 | free(addr_str); 219 | free(mask_str); 220 | free(net_str); 221 | 222 | return sd; 223 | } 224 | 225 | static ssize_t send_packet(int fd, const void *data, size_t len) { 226 | static struct sockaddr_in toaddr; 227 | if (toaddr.sin_family != AF_INET) { 228 | memset(&toaddr, 0, sizeof(struct sockaddr_in)); 229 | toaddr.sin_family = AF_INET; 230 | toaddr.sin_port = htons(MDNS_PORT); 231 | toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR); 232 | } 233 | 234 | return sendto(fd, data, len, 0, (struct sockaddr *) &toaddr, sizeof(struct sockaddr_in)); 235 | } 236 | 237 | static void mdns_repeater_shutdown(int sig) { 238 | shutdown_flag = 1; 239 | } 240 | 241 | static pid_t already_running() { 242 | FILE *f; 243 | int count; 244 | pid_t pid; 245 | 246 | f = fopen(pid_file, "r"); 247 | if (f != NULL) { 248 | count = fscanf(f, "%d", &pid); 249 | fclose(f); 250 | if (count == 1) { 251 | if (kill(pid, 0) == 0) 252 | return pid; 253 | } 254 | } 255 | 256 | return -1; 257 | } 258 | 259 | static int write_pidfile() { 260 | FILE *f; 261 | int r; 262 | 263 | f = fopen(pid_file, "w"); 264 | if (f != NULL) { 265 | r = fprintf(f, "%d", getpid()); 266 | fclose(f); 267 | return (r > 0); 268 | } 269 | 270 | return 0; 271 | } 272 | 273 | static void daemonize() { 274 | pid_t running_pid; 275 | pid_t pid = fork(); 276 | if (pid < 0) { 277 | log_message(LOG_ERR, "fork(): %s", strerror(errno)); 278 | exit(1); 279 | } 280 | 281 | // exit parent process 282 | if (pid > 0) 283 | exit(0); 284 | 285 | // signals 286 | signal(SIGCHLD, SIG_IGN); 287 | signal(SIGHUP, SIG_IGN); 288 | signal(SIGTERM, mdns_repeater_shutdown); 289 | 290 | setsid(); 291 | umask(0027); 292 | chdir("/"); 293 | 294 | // close all std fd and reopen /dev/null for them 295 | int i; 296 | for (i = 0; i < 3; i++) { 297 | close(i); 298 | if (open("/dev/null", O_RDWR) != i) { 299 | log_message(LOG_ERR, "unable to open /dev/null for fd %d", i); 300 | exit(1); 301 | } 302 | } 303 | 304 | // check for pid file 305 | running_pid = already_running(); 306 | if (running_pid != -1) { 307 | log_message(LOG_ERR, "already running as pid %d", running_pid); 308 | exit(1); 309 | } else if (! write_pidfile()) { 310 | log_message(LOG_ERR, "unable to write pid file %s", pid_file); 311 | exit(1); 312 | } 313 | } 314 | 315 | static void show_help(const char *progname) { 316 | fprintf(stderr, "mDNS repeater (version " MDNS_REPEATER_VERSION ")\n"); 317 | fprintf(stderr, "Copyright (C) 2011 Darell Tan\n\n"); 318 | fprintf(stderr, "usage: %s [ -f ] ...\n", progname); 319 | fprintf(stderr, "\n" 320 | " specifies an interface like \"eth0\"\n" 321 | "packets received on an interface is repeated across all other specified interfaces\n" 322 | "maximum number of interfaces is 5\n" 323 | "\n" 324 | " flags:\n" 325 | " -f runs in foreground\n" 326 | " -d log debug messages when runs in foreground\n" 327 | " -b blacklist subnet (eg. 192.168.1.1/24)\n" 328 | " -w whitelist subnet (eg. 192.168.1.1/24)\n" 329 | " -p specifies the pid file path (default: " PIDFILE ")\n" 330 | " -h shows this help\n" 331 | "\n" 332 | ); 333 | } 334 | 335 | int parse(char *input, struct subnet *s) { 336 | int delim = 0; 337 | int end = 0; 338 | while (input[end] != 0) { 339 | if (input[end] == '/') { 340 | delim = end; 341 | } 342 | end++; 343 | } 344 | 345 | if (end == 0 || delim == 0 || end == delim) { 346 | return -1; 347 | } 348 | 349 | char *addr = (char*) malloc(end); 350 | 351 | memset(addr, 0, end); 352 | strncpy(addr, input, delim); 353 | if (inet_pton(AF_INET, addr, &s->addr) != 1) { 354 | free(addr); 355 | return -2; 356 | } 357 | 358 | memset(addr, 0, end); 359 | strncpy(addr, input+delim+1, end-delim-1); 360 | int mask = atoi(addr); 361 | free(addr); 362 | 363 | if (mask < 0 || mask > 32) { 364 | return -3; 365 | } 366 | 367 | s->mask.s_addr = ntohl((uint32_t)0xFFFFFFFF << (32 - mask)); 368 | s->net.s_addr = s->addr.s_addr & s->mask.s_addr; 369 | 370 | return 0; 371 | } 372 | 373 | int tostring(struct subnet *s, char* buf, int len) { 374 | char *addr_str = strdup(inet_ntoa(s->addr)); 375 | char *mask_str = strdup(inet_ntoa(s->mask)); 376 | char *net_str = strdup(inet_ntoa(s->net)); 377 | int l = snprintf(buf, len, "addr %s mask %s net %s", addr_str, mask_str, net_str); 378 | free(addr_str); 379 | free(mask_str); 380 | free(net_str); 381 | 382 | return l; 383 | } 384 | 385 | static int parse_opts(int argc, char *argv[]) { 386 | int c, res; 387 | int help = 0; 388 | struct subnet *ss; 389 | char *msg; 390 | while ((c = getopt(argc, argv, "hfdp:b:w:")) != -1) { 391 | switch (c) { 392 | case 'h': help = 1; break; 393 | case 'f': foreground = 1; break; 394 | case 'd': debug = 1; break; 395 | case 'p': 396 | if (optarg[0] != '/') 397 | log_message(LOG_ERR, "pid file path must be absolute"); 398 | else 399 | pid_file = optarg; 400 | break; 401 | 402 | case 'b': 403 | if (num_blacklisted_subnets >= MAX_SUBNETS) { 404 | log_message(LOG_ERR, "too many blacklisted subnets (maximum is %d)", MAX_SUBNETS); 405 | exit(2); 406 | } 407 | 408 | if (num_whitelisted_subnets != 0) { 409 | log_message(LOG_ERR, "simultaneous whitelisting and blacklisting does not make sense"); 410 | exit(2); 411 | } 412 | 413 | ss = &blacklisted_subnets[num_blacklisted_subnets]; 414 | res = parse(optarg, ss); 415 | switch (res) { 416 | case -1: 417 | log_message(LOG_ERR, "invalid blacklist argument"); 418 | exit(2); 419 | case -2: 420 | log_message(LOG_ERR, "could not parse netmask"); 421 | exit(2); 422 | case -3: 423 | log_message(LOG_ERR, "invalid netmask"); 424 | exit(2); 425 | } 426 | 427 | num_blacklisted_subnets++; 428 | 429 | msg = malloc(128); 430 | memset(msg, 0, 128); 431 | tostring(ss, msg, 128); 432 | log_message(LOG_INFO, "blacklist %s", msg); 433 | free(msg); 434 | break; 435 | case 'w': 436 | if (num_whitelisted_subnets >= MAX_SUBNETS) { 437 | log_message(LOG_ERR, "too many whitelisted subnets (maximum is %d)", MAX_SUBNETS); 438 | exit(2); 439 | } 440 | 441 | if (num_blacklisted_subnets != 0) { 442 | log_message(LOG_ERR, "simultaneous whitelisting and blacklisting does not make sense"); 443 | exit(2); 444 | } 445 | 446 | ss = &whitelisted_subnets[num_whitelisted_subnets]; 447 | res = parse(optarg, ss); 448 | switch (res) { 449 | case -1: 450 | log_message(LOG_ERR, "invalid whitelist argument"); 451 | exit(2); 452 | case -2: 453 | log_message(LOG_ERR, "could not parse netmask"); 454 | exit(2); 455 | case -3: 456 | log_message(LOG_ERR, "invalid netmask"); 457 | exit(2); 458 | } 459 | 460 | num_whitelisted_subnets++; 461 | 462 | msg = malloc(128); 463 | memset(msg, 0, 128); 464 | tostring(ss, msg, 128); 465 | log_message(LOG_INFO, "whitelist %s", msg); 466 | free(msg); 467 | break; 468 | case '?': 469 | case ':': 470 | fputs("\n", stderr); 471 | break; 472 | 473 | default: 474 | log_message(LOG_ERR, "unknown option %c", optopt); 475 | exit(2); 476 | } 477 | } 478 | 479 | if (help) { 480 | show_help(argv[0]); 481 | exit(0); 482 | } 483 | 484 | return optind; 485 | } 486 | 487 | int main(int argc, char *argv[]) { 488 | pid_t running_pid; 489 | fd_set sockfd_set; 490 | int r = 0; 491 | 492 | parse_opts(argc, argv); 493 | 494 | if ((argc - optind) <= 1) { 495 | show_help(argv[0]); 496 | log_message(LOG_ERR, "error: at least 2 interfaces must be specified"); 497 | exit(2); 498 | } 499 | 500 | openlog(PACKAGE, LOG_PID | LOG_CONS, LOG_DAEMON); 501 | if (! foreground) 502 | daemonize(); 503 | else { 504 | // check for pid file when running in foreground 505 | running_pid = already_running(); 506 | if (running_pid != -1) { 507 | log_message(LOG_ERR, "already running as pid %d", running_pid); 508 | exit(1); 509 | } 510 | } 511 | 512 | // create receiving socket 513 | server_sockfd = create_recv_sock(); 514 | if (server_sockfd < 0) { 515 | log_message(LOG_ERR, "unable to create server socket"); 516 | r = 1; 517 | goto end_main; 518 | } 519 | 520 | // create sending sockets 521 | int i; 522 | for (i = optind; i < argc; i++) { 523 | if (num_socks >= MAX_SOCKS) { 524 | log_message(LOG_ERR, "too many sockets (maximum is %d)", MAX_SOCKS); 525 | exit(2); 526 | } 527 | 528 | int sockfd = create_send_sock(server_sockfd, argv[i], &socks[num_socks]); 529 | if (sockfd < 0) { 530 | log_message(LOG_ERR, "unable to create socket for interface %s", argv[i]); 531 | r = 1; 532 | goto end_main; 533 | } 534 | num_socks++; 535 | } 536 | 537 | pkt_data = malloc(PACKET_SIZE); 538 | if (pkt_data == NULL) { 539 | log_message(LOG_ERR, "cannot malloc() packet buffer: %s", strerror(errno)); 540 | r = 1; 541 | goto end_main; 542 | } 543 | 544 | while (! shutdown_flag) { 545 | struct timeval tv = { 546 | .tv_sec = 10, 547 | .tv_usec = 0, 548 | }; 549 | 550 | FD_ZERO(&sockfd_set); 551 | FD_SET(server_sockfd, &sockfd_set); 552 | int numfd = select(server_sockfd + 1, &sockfd_set, NULL, NULL, &tv); 553 | if (numfd <= 0) 554 | continue; 555 | 556 | if (FD_ISSET(server_sockfd, &sockfd_set)) { 557 | struct sockaddr_in fromaddr; 558 | socklen_t sockaddr_size = sizeof(struct sockaddr_in); 559 | 560 | ssize_t recvsize = recvfrom(server_sockfd, pkt_data, PACKET_SIZE, 0, 561 | (struct sockaddr *) &fromaddr, &sockaddr_size); 562 | if (recvsize < 0) { 563 | log_message(LOG_ERR, "recv(): %s", strerror(errno)); 564 | } 565 | 566 | int j; 567 | char self_generated_packet = 0; 568 | for (j = 0; j < num_socks; j++) { 569 | // check for loopback 570 | if (fromaddr.sin_addr.s_addr == socks[j].addr.s_addr) { 571 | self_generated_packet = 1; 572 | break; 573 | } 574 | } 575 | 576 | if (self_generated_packet) 577 | continue; 578 | 579 | if (num_whitelisted_subnets != 0) { 580 | char whitelisted_packet = 0; 581 | for (j = 0; j < num_whitelisted_subnets; j++) { 582 | // check for whitelist 583 | if ((fromaddr.sin_addr.s_addr & whitelisted_subnets[j].mask.s_addr) == whitelisted_subnets[j].net.s_addr) { 584 | whitelisted_packet = 1; 585 | break; 586 | } 587 | } 588 | 589 | if (!whitelisted_packet) { 590 | if (foreground && debug) 591 | printf("skipping packet from=%s size=%zd\n", inet_ntoa(fromaddr.sin_addr), recvsize); 592 | continue; 593 | } 594 | } else { 595 | char blacklisted_packet = 0; 596 | for (j = 0; j < num_blacklisted_subnets; j++) { 597 | // check for blacklist 598 | if ((fromaddr.sin_addr.s_addr & blacklisted_subnets[j].mask.s_addr) == blacklisted_subnets[j].net.s_addr) { 599 | blacklisted_packet = 1; 600 | break; 601 | } 602 | } 603 | 604 | if (blacklisted_packet) { 605 | if (foreground && debug) 606 | printf("skipping packet from=%s size=%zd\n", inet_ntoa(fromaddr.sin_addr), recvsize); 607 | continue; 608 | } 609 | } 610 | 611 | for (j = 0; j < num_socks; j++) { 612 | // do not repeat packet back to the same network from which it originated 613 | if ((fromaddr.sin_addr.s_addr & socks[j].mask.s_addr) == socks[j].net.s_addr) 614 | continue; 615 | 616 | if (foreground && debug) 617 | printf("%s (%zd bytes) -> %s\n", inet_ntoa(fromaddr.sin_addr), recvsize, socks[j].ifname); 618 | 619 | // repeat data 620 | ssize_t sentsize = send_packet(socks[j].sockfd, pkt_data, (size_t) recvsize); 621 | if (sentsize != recvsize) { 622 | if (sentsize < 0) 623 | log_message(LOG_ERR, "send(): %s", strerror(errno)); 624 | else 625 | log_message(LOG_ERR, "send_packet size differs: sent=%zd actual=%zd", 626 | recvsize, sentsize); 627 | } 628 | } 629 | } 630 | } 631 | 632 | log_message(LOG_INFO, "shutting down..."); 633 | 634 | end_main: 635 | 636 | if (pkt_data != NULL) 637 | free(pkt_data); 638 | 639 | if (server_sockfd >= 0) 640 | close(server_sockfd); 641 | 642 | for (i = 0; i < num_socks; i++) 643 | close(socks[i].sockfd); 644 | 645 | // remove pid file if it belongs to us 646 | if (already_running() == getpid()) 647 | unlink(pid_file); 648 | 649 | log_message(LOG_INFO, "exit."); 650 | 651 | return r; 652 | } 653 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | 6 | HOSTNAME="mDns" 7 | INTERFACE="eth0" 8 | VLANS="20 100" 9 | 10 | MTU=$(ip link show "$INTERFACE" | awk '{print $5}') 11 | 12 | for VLAN in $VLANS; do 13 | # INTERFACE PROVISION 14 | IFNAME="${INTERFACE}.${VLAN}" 15 | [ ! -d "/sys/class/net/${IFNAME}" ] && { 16 | echo "create interface ${IFNAME}" 17 | ip link add link "$INTERFACE" name "$IFNAME" mtu "$MTU" type vlan id "$VLAN" 18 | } 19 | echo "bring up ${IFNAME} interface" 20 | ip link set "${IFNAME}" up 21 | 22 | # DHCP 23 | [ -f "/var/run/udhcpc.${IFNAME}.pid" ] && { 24 | kill "$(cat "/var/run/udhcpc.$IFNAME.pid")" || true 25 | rm "/var/run/udhcpc.$IFNAME.pid" 26 | } 27 | echo "starting dhcp client on ${IFNAME}" 28 | udhcpc -b -i "$IFNAME" -x hostname:"$HOSTNAME" -p "/var/run/udhcpc.${IFNAME}.pid" 29 | done 30 | 31 | exec "$@" 32 | --------------------------------------------------------------------------------