├── .dockerignore ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── docker-compose.yml ├── run.sh ├── Dockerfile ├── README.md ├── LICENSE.txt └── mdns-repeater.c /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | **/*.swp 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /.settings/ 3 | .project 4 | tmp/ 5 | .vscode/ 6 | 7 | *.o 8 | _hgversion 9 | mdns-repeater 10 | mdns-repeater-*.zip 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mdns-repeater: 4 | image: ${DOCKER_REGISTRY}monstrenyatko/mdns-repeater 5 | container_name: mdns-repeater 6 | restart: unless-stopped 7 | command: mdns-repeater-app -f $MDNS_REPEATER_INTERFACES 8 | network_mode: "host" 9 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | 6 | # Load functions 7 | source /scripts/update-app-user-uid-gid.sh 8 | 9 | # Debug output 10 | set -x 11 | 12 | update_user_gid $APP_USERNAME $APP_GROUPNAME $APP_GID 13 | update_user_uid $APP_USERNAME $APP_UID 14 | 15 | if [ "$1" = $APP_NAME ]; then 16 | shift; 17 | exec /scripts/app-entrypoint.sh $APP_BIN "$@" 18 | fi 19 | 20 | exec "$@" 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM monstrenyatko/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 monstrenyatko/alpine 9 | 10 | LABEL maintainer="Oleg Kovalenko " 11 | 12 | COPY --from=builder /bin/mdns-repeater /bin/mdns-repeater 13 | RUN chown root:root /bin/mdns-repeater 14 | RUN chmod 0755 /bin/mdns-repeater 15 | RUN setcap cap_net_raw=+ep /bin/mdns-repeater 16 | 17 | ENV APP_NAME="mdns-repeater-app" \ 18 | APP_BIN="/bin/mdns-repeater" \ 19 | APP_USERNAME="daemon" \ 20 | APP_GROUPNAME="daemon" 21 | 22 | COPY run.sh /app/ 23 | RUN chown -R root:root /app 24 | RUN chmod -R 0744 /app 25 | RUN chmod 0755 /app/run.sh 26 | 27 | ENTRYPOINT ["/app/run.sh"] 28 | CMD ["mdns-repeater-app", "-f", "eth0", "docker0"] 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | schedule: 4 | - cron: '0 2 1 * *' 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | main: 10 | runs-on: ubuntu-latest 11 | env: 12 | REPO: monstrenyatko/mdns-repeater 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v1 20 | - name: Login to DockerHub 21 | uses: docker/login-action@v1 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 25 | - name: Build and push 26 | run: | 27 | MDNS_REPEATER_VERSION=$(git rev-parse HEAD) 28 | ./build.sh "$REPO:$(date +%Y%m%d)" "--build-arg MDNS_REPEATER_VERSION=$MDNS_REPEATER_VERSION --push" 29 | ./build.sh "$REPO:latest" "--build-arg MDNS_REPEATER_VERSION=$MDNS_REPEATER_VERSION --push" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mdns-repeater Docker image 2 | ========================== 3 | 4 | [![](https://github.com/monstrenyatko/docker-mdns-repeater/workflows/ci/badge.svg?branch=master)](https://github.com/monstrenyatko/docker-mdns-repeater/actions?query=workflow%3Aci) 5 | 6 | About 7 | ===== 8 | 9 | `mdns-repeater` in the `Docker` container. 10 | 11 | mdns-repeater 12 | ------------- 13 | 14 | `mdns-repeater` is a [Multicast DNS](https://en.wikipedia.org/wiki/Multicast_DNS) (`mDNS`) repeater for `Linux`. 15 | 16 | This program is an application level gateway which re-broadcasts `mDNS` packets received on one interface to other interfaces. 17 | Since `mDNS` is "administratively scoped" such a gateway is required when `mDNS` resolutions should work across subnet borders. 18 | 19 | Upstream Links 20 | -------------- 21 | * Docker Registry @[monstrenyatko/mdns-repeater](https://hub.docker.com/r/monstrenyatko/mdns-repeater/) 22 | * GitHub @[monstrenyatko/docker-mdns-repeater](https://github.com/monstrenyatko/docker-mdns-repeater) 23 | 24 | Usage 25 | ===== 26 | 27 | `mdns-repeater` only requires the interface names and it will do the rest. 28 | 29 | Example: 30 | ```sh 31 | mdns-repeater eth0 vlan1 32 | ``` 33 | 34 | You can also specify the: 35 | - `-f` flag to keep `mdns-repeater` running in foreground. 36 | - `-d` flag additionally to `-f` to print out parsed `mDNS` packets as they are received 37 | 38 | Docker image Usage 39 | ================== 40 | 41 | Container is already configured for automatic restart (See `docker-compose.yml`). 42 | 43 | * Configure environment: 44 | 45 | - `MDNS_REPEATER_INTERFACES`: names of the interfaces: 46 | 47 | ```sh 48 | export MDNS_REPEATER_INTERFACES="eth0 docker0" 49 | ``` 50 | - `DOCKER_REGISTRY`: [**OPTIONAL**] registry prefix to pull image from a custom `Docker` registry: 51 | 52 | ```sh 53 | export DOCKER_REGISTRY="my_registry_hostname:5000/" 54 | ``` 55 | * Pull prebuilt `Docker` image: 56 | 57 | ```sh 58 | docker-compose pull 59 | ``` 60 | * Start prebuilt image: 61 | 62 | ```sh 63 | docker-compose up -d 64 | ``` 65 | * Stop/Restart: 66 | 67 | ```sh 68 | docker-compose stop 69 | docker-compose start 70 | ``` 71 | 72 | Acknowledgments 73 | =============== 74 | 75 | - **Darell Tan** who created the initial version of this [program](https://github.com/geekman/mdns-repeater) 76 | and who described it in this [blog post](http://irq5.io/2011/01/02/mdns-repeater-mdns-across-subnets/). 77 | - **Kenny Levinsen** and contributors for maintaining the code and adding features in 78 | this [repository](https://github.com/kennylevinsen/mdns-repeater). 79 | - **Matthias Dettling** for `-d` flag idea in this [repository](https://github.com/devsecurity-io/mdns-repeater). 80 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------