pax_global_header00006660000000000000000000000064144701144640014517gustar00rootroot0000000000000052 comment=09e31e92fa3d2a1d3ca261adaeb012c8d75a8194 slirp4netns-1.2.1/000077500000000000000000000000001447011446400140055ustar00rootroot00000000000000slirp4netns-1.2.1/.clang-format000066400000000000000000000034321447011446400163620ustar00rootroot00000000000000# https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html --- Language: Cpp AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false # although we like it, it creates churn AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: false # churn AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None # AlwaysBreakAfterDefinitionReturnType is taken into account AlwaysBreakBeforeMultilineStrings: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterControlStatement: false AfterEnum: false AfterFunction: true AfterStruct: false AfterUnion: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeTernaryOperators: false BreakStringLiterals: true ColumnLimit: 80 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ? MacroBlockEnd: '.*_END$' MaxEmptyLinesToKeep: 2 PointerAlignment: Right ReflowComments: true SortIncludes: false SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInContainerLiterals: true SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto UseTab: Never ... slirp4netns-1.2.1/.gitattributes000066400000000000000000000000311447011446400166720ustar00rootroot00000000000000Makefile.am export-subst slirp4netns-1.2.1/.github/000077500000000000000000000000001447011446400153455ustar00rootroot00000000000000slirp4netns-1.2.1/.github/workflows/000077500000000000000000000000001447011446400174025ustar00rootroot00000000000000slirp4netns-1.2.1/.github/workflows/main.yaml000066400000000000000000000031601447011446400212120ustar00rootroot00000000000000name: Main on: [push, pull_request] jobs: test-main: runs-on: ubuntu-20.04 strategy: matrix: libslirp_commit: [master, v4.7.0, v4.6.1, v4.1.0] steps: - uses: actions/checkout@v2 - run: docker build -t slirp4netns-tests --build-arg LIBSLIRP_COMMIT -f Dockerfile.tests . env: LIBSLIRP_COMMIT: ${{ matrix.libslirp_commit }} - run: docker run --rm --privileged slirp4netns-tests test-build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - run: DOCKER_BUILDKIT=1 docker build -f Dockerfile.buildtests . test-centos7: runs-on: macos-12 env: LIBSECCOMP_COMMIT: v2.3.3 LIBSLIRP_COMMIT: v4.1.0 BENCHMARK_IPERF3_DURATION: 3 steps: - uses: actions/checkout@v2 - name: Setup CentOS 7 VM run: | vagrant up --provision --no-tty cat > ./run-vagrant-tests <<'EOF' exec vagrant ssh --no-tty -c " export LIBSECCOMP_COMMIT=\"${LIBSECCOMP_COMMIT}\" export LIBSLIRP_COMMIT=\"${LIBSLIRP_COMMIT}\" export BENCHMARK_IPERF3_DURATION=\"${BENCHMARK_IPERF3_DURATION}\" /src/build-and-test " EOF - name: Build and test with Debian 10's version of libseccomp run: sh ./run-vagrant-tests - name: Build and test with Ubuntu 20.04's versions of libseccomp/libslirp run: sh ./run-vagrant-tests env: LIBSECCOMP_COMMIT: v2.4.3 LIBSLIRP_COMMIT: v4.1.0 - name: Build and test with recent versions of libseccomp/libslirp run: sh ./run-vagrant-tests env: LIBSECCOMP_COMMIT: v2.5.4 LIBSLIRP_COMMIT: v4.7.0 slirp4netns-1.2.1/.github/workflows/release.yaml000066400000000000000000000052571447011446400217170ustar00rootroot00000000000000name: Release on: push: pull_request: jobs: release: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - uses: docker/setup-buildx-action@v2 - name: "Build binaries from Dockerfile.artifact" run: docker buildx build -o /tmp/slirpbuilds --platform=amd64,arm64,arm,s390x,ppc64le,riscv64 -f Dockerfile.artifact . - name: "Create /tmp/artifact" run: | mkdir -p /tmp/artifact mv /tmp/slirpbuilds/linux_amd64/slirp4netns /tmp/artifact/slirp4netns-x86_64 mv /tmp/slirpbuilds/linux_arm64/slirp4netns /tmp/artifact/slirp4netns-aarch64 mv /tmp/slirpbuilds/linux_arm_v7/slirp4netns /tmp/artifact/slirp4netns-armv7l mv /tmp/slirpbuilds/linux_s390x/slirp4netns /tmp/artifact/slirp4netns-s390x mv /tmp/slirpbuilds/linux_ppc64le/slirp4netns /tmp/artifact/slirp4netns-ppc64le mv /tmp/slirpbuilds/linux_riscv64/slirp4netns /tmp/artifact/slirp4netns-riscv64 - name: "SHA256SUMS" run: (cd /tmp/artifact; sha256sum *) | tee /tmp/SHA256SUMS - name: "The sha256sum of the SHA256SUMS file" run: sha256sum /tmp/SHA256SUMS - name: "Prepare the release note" run: | tag="${GITHUB_REF##*/}" shasha=$(sha256sum /tmp/SHA256SUMS | awk '{print $1}') libslirp_version=$(/tmp/artifact/slirp4netns-x86_64 -v | grep -oP '^libslirp: \K\S*') libseccomp_version=$(/tmp/artifact/slirp4netns-x86_64 -v | grep -oP '^libseccomp: \K\S*') ubuntu_version=$(grep -oP '^ARG UBUNTU_VERSION=\K\S*' Dockerfile.artifact) cat << EOF | tee /tmp/release-note.txt ${tag} #### Changes (To be documented) #### Install \`\`\` curl -o slirp4netns --fail -L https://github.com/${{ github.repository }}/releases/download/${tag}/slirp4netns-\$(uname -m) chmod +x slirp4netns \`\`\` #### About the binaries The binaries are statically linked with libslirp ${libslirp_version} and libseccomp ${libseccomp_version} using Ubuntu ${ubuntu_version}. The binaries were built automatically on GitHub Actions. The build log is available for 90 days: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} The sha256sum of the SHA256SUMS file itself is \`${shasha}\` . EOF - name: "Create release" if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tag="${GITHUB_REF##*/}" asset_flags=() for f in /tmp/artifact/* /tmp/SHA256SUMS; do asset_flags+=("-a" "$f"); done hub release create "${asset_flags[@]}" -F /tmp/release-note.txt --draft "${tag}" slirp4netns-1.2.1/.gitignore000066400000000000000000000016271447011446400160030ustar00rootroot00000000000000slirp4netns a.out .deps *~ *.log *.o *.a *.trs config.h config.status INSTALL /.vagrant ################################################################################ # https://github.com/github/gitignore/blob/c24cdc2175d7514d504d7f060a5d69675c8a9b50/Autotools.gitignore ################################################################################ # http://www.gnu.org/software/automake Makefile Makefile.in /ar-lib /mdate-sh /py-compile /test-driver /ylwrap *.dirstamp *tar.* # http://www.gnu.org/software/autoconf autom4te.cache /autoscan.log /autoscan-*.log /aclocal.m4 /compile /config.guess /config.h.in /config.sub /configure /configure.scan /depcomp /install-sh /missing /stamp-h1 # https://www.gnu.org/software/libtool/ /ltmain.sh # http://www.gnu.org/software/texinfo /texinfo.tex # http://www.gnu.org/software/m4/ m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 slirp4netns-1.2.1/COPYING000066400000000000000000000354421447011446400150500ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS slirp4netns-1.2.1/Dockerfile.artifact000066400000000000000000000024071447011446400175760ustar00rootroot00000000000000ARG LIBSLIRP_COMMIT=v4.7.0 ARG UBUNTU_VERSION=22.04 ARG XX_VERSION=1.2.1@sha256:8879a398dedf0aadaacfbd332b29ff2f84bc39ae6d4e9c0a1109db27ac5ba012 FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM ubuntu:${UBUNTU_VERSION} AS build-libslirp ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y apt-utils automake autotools-dev make git ninja-build meson RUN git clone https://git.qemu.org/libslirp.git /libslirp WORKDIR /libslirp ARG LIBSLIRP_COMMIT RUN git pull && git checkout ${LIBSLIRP_COMMIT} COPY --from=xx / / ARG TARGETPLATFORM RUN xx-apt-get install -y gcc libglib2.0-dev libcap-dev libseccomp-dev COPY Dockerfile.artifact.d/meson-cross /meson-cross RUN meson_setup_flags="--default-library=both" ; \ if xx-info is-cross; then meson_setup_flags="${meson_setup_flags} --cross-file=/meson-cross/$(xx-info) -Dprefix=/usr/local/$(xx-info)"; fi ; \ meson setup ${meson_setup_flags} build && ninja -C build install FROM build-libslirp AS build COPY . /src WORKDIR /src RUN ./autogen.sh && \ ./configure LDFLAGS="-static" --host=$(xx-info) && \ make && \ $(xx-info)-strip slirp4netns && \ xx-verify --static slirp4netns && \ cp -a slirp4netns / FROM scratch COPY --from=build /slirp4netns /slirp4netns slirp4netns-1.2.1/Dockerfile.artifact.d/000077500000000000000000000000001447011446400200725ustar00rootroot00000000000000slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/000077500000000000000000000000001447011446400223425ustar00rootroot00000000000000slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/aarch64-linux-gnu000066400000000000000000000004141447011446400254400ustar00rootroot00000000000000[binaries] c = 'aarch64-linux-gnu-gcc' cpp = 'aarch64-linux-gnu-g++' ar = 'aarch64-linux-gnu-gcc-ar' strip = 'aarch64-linux-gnu-strip' pkgconfig = 'aarch64-linux-gnu-pkg-config' [host_machine] system = 'linux' cpu_family = 'aarch64' cpu = 'aarch64' endian = 'little' slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/arm-linux-gnueabihf000066400000000000000000000004201447011446400261230ustar00rootroot00000000000000[binaries] c = 'arm-linux-gnueabihf-gcc' cpp = 'arm-linux-gnueabihf-g++' ar = 'arm-linux-gnueabihf-gcc-ar' strip = 'arm-linux-gnueabihf-strip' pkgconfig = 'arm-linux-gnueabihf-pkg-config' [host_machine] system = 'linux' cpu_family = 'arm' cpu = 'armhf' endian = 'little' slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/powerpc64le-linux-gnu000066400000000000000000000004421447011446400263630ustar00rootroot00000000000000[binaries] c = 'powerpc64le-linux-gnu-gcc' cpp = 'powerpc64le-linux-gnu-g++' ar = 'powerpc64le-linux-gnu-gcc-ar' strip = 'powerpc64le-linux-gnu-strip' pkgconfig = 'powerpc64le-linux-gnu-pkg-config' [host_machine] system = 'linux' cpu_family = 'ppc64' cpu = 'powerpc64le' endian = 'little' slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/riscv64-linux-gnu000066400000000000000000000004141447011446400255100ustar00rootroot00000000000000[binaries] c = 'riscv64-linux-gnu-gcc' cpp = 'riscv64-linux-gnu-g++' ar = 'riscv64-linux-gnu-gcc-ar' strip = 'riscv64-linux-gnu-strip' pkgconfig = 'riscv64-linux-gnu-pkg-config' [host_machine] system = 'linux' cpu_family = 'riscv64' cpu = 'riscv64' endian = 'little' slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/s390x-linux-gnu000066400000000000000000000003731447011446400251020ustar00rootroot00000000000000[binaries] c = 's390x-linux-gnu-gcc' cpp = 's390x-linux-gnu-g++' ar = 's390x-linux-gnu-gcc-ar' strip = 's390x-linux-gnu-strip' pkgconfig = 's390x-linux-gnu-pkg-config' [host_machine] system = 'linux' cpu_family = 's390x' cpu = 's390x' endian = 'big' slirp4netns-1.2.1/Dockerfile.artifact.d/meson-cross/x86_64-linux-gnu000066400000000000000000000004051447011446400251460ustar00rootroot00000000000000[binaries] c = 'x86_64-linux-gnu-gcc' cpp = 'x86_64-linux-gnu-g++' ar = 'x86_64-linux-gnu-gcc-ar' strip = 'x86_64-linux-gnu-strip' pkgconfig = 'x86_64-linux-gnu-pkg-config' [host_machine] system = 'linux' cpu_family = 'x86_64' cpu = 'x86_64' endian = 'little' slirp4netns-1.2.1/Dockerfile.buildtests000066400000000000000000000041261447011446400201630ustar00rootroot00000000000000ARG LIBSLIRP_COMMIT=v4.7.0 # Alpine FROM alpine:3 AS buildtest-alpine3-static RUN apk add --no-cache git build-base autoconf automake libtool linux-headers glib-dev glib-static libcap-static libcap-dev libseccomp-dev libseccomp-static git meson RUN git clone https://git.qemu.org/libslirp.git /libslirp WORKDIR /libslirp ARG LIBSLIRP_COMMIT RUN git pull && git checkout ${LIBSLIRP_COMMIT} && meson setup --default-library=both build && ninja -C build install COPY . /src WORKDIR /src RUN ./autogen.sh && ./configure LDFLAGS="-static" && make && cp -f slirp4netns / # Ubuntu FROM ubuntu:18.04 AS buildtest-ubuntu1804-common ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y automake autotools-dev make gcc libglib2.0-dev libcap-dev libseccomp-dev git ninja-build python3-pip RUN pip3 install meson RUN git clone https://git.qemu.org/libslirp.git /libslirp WORKDIR /libslirp ARG LIBSLIRP_COMMIT RUN git pull && git checkout ${LIBSLIRP_COMMIT} && meson setup build && ninja -C build install COPY . /src WORKDIR /src RUN ./autogen.sh FROM buildtest-ubuntu1804-common AS buildtest-ubuntu1804-dynamic RUN ./configure && make && cp -f slirp4netns / # CentOS 7 is no longer tested in this file. # See Vagrantfile for CentOS 7 tests. # openSUSE (dynamic only) FROM opensuse/leap:15 AS buildtest-opensuse15-common RUN zypper install -y --no-recommends autoconf automake gcc glib2-devel git make libcap-devel libseccomp-devel ninja python3-pip RUN pip3 install meson RUN git clone https://git.qemu.org/libslirp.git /libslirp WORKDIR /libslirp ARG LIBSLIRP_COMMIT RUN git pull && git checkout ${LIBSLIRP_COMMIT} && meson setup --default-library=both build && ninja -C build install COPY . /src WORKDIR /src RUN ./autogen.sh FROM buildtest-opensuse15-common AS buildtest-opensuse15-dynamic RUN ./configure && make && cp -f slirp4netns / FROM scratch AS buildtest-final-stage COPY --from=buildtest-alpine3-static /slirp4netns /buildtest-alpine3-static COPY --from=buildtest-ubuntu1804-dynamic /slirp4netns /buildtest-ubuntu1804-dynamic COPY --from=buildtest-opensuse15-dynamic /slirp4netns /buildtest-opensuse15-dynamic slirp4netns-1.2.1/Dockerfile.tests000066400000000000000000000013731447011446400171440ustar00rootroot00000000000000ARG LIBSLIRP_COMMIT=v4.7.0 FROM ubuntu:22.04 AS build ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt install -y automake autotools-dev make gcc libglib2.0-dev libcap-dev libseccomp-dev git ninja-build python3-pip RUN pip3 install meson RUN git clone https://git.qemu.org/libslirp.git /libslirp WORKDIR /libslirp ARG LIBSLIRP_COMMIT RUN git pull && git checkout ${LIBSLIRP_COMMIT} && meson setup build && ninja -C build install COPY . /slirp4netns WORKDIR /slirp4netns RUN chown -R 1000:1000 /slirp4netns USER 1000:1000 RUN ./autogen.sh && ./configure && make -j $(nproc) FROM build AS test USER 0 RUN apt update && apt install -y git libtool iproute2 clang clang-format clang-tidy iputils-ping iperf3 ncat jq udhcpc USER 1000:1000 CMD ["make", "ci"] slirp4netns-1.2.1/MAINTAINERS000066400000000000000000000075761447011446400155210ustar00rootroot00000000000000# slirp4netns maintainers file (Moby-style TOML syntax) [Org] # Approvers (aka Core Maintainers) approve pull requests and ship releases. # GitHub Team: rootless-containers/slirp4netns-approvers # # An approver may approve PRs and ship releases without waiting for LGTMs from other approvers. # However, the approver should try to get LGTMs from other approvers for significant changes. # # Release guide (since v1.1.1): # 1. Bump up the version string in `configure.ac` to `vX.Y.Z` (or `vX.Y.Z-beta.W`) # 2. `git commit -a -s -m vX.Y.Z` # 3. Bump up the version string to `vX.Y.Z+dev` (or `vX.Y.Z-beta.W+dev`) # 4. `git commit -a -s -m vX.Y.Z+dev` # 5. Open a PR and merge it. # 6. Create a tag `v.X.Y.Z` for the `vX.Y.Z` commit, with a GPG sign: `git tag -s vX.Y.Z ` # 7. Push the tag to the upstream: `git push upstream vX.Y.Z` # 8. GitHub Actions automatically ships a draft release with statically compiled binaries `slirp4netns-$(uname -m)` and `SHA256SUMS`: https://github.com/rootless-containers/slirp4netns/releases # If it fails, check the GitHub Actions log: https://github.com/rootless-containers/slirp4netns/actions?query=workflow%3ARelease # 9. Sign `SHA256SUMS`: `gpg --detach-sign -a SHA256SUMS` # This command will create `SHA256SUMS.asc`. Make sure to use the same GPG key used for `git tag -s vX.Y.Z `. # 10. Attach `SHA256SUMS.asc` and add release note texts to the draft release, and ship the release. [Org.Approvers] people = [ "akihirosuda", "cyphar", "giuseppe", ] # Package maintainers maintain distro packages. # GitHub Team: rootless-containers/slirp4netns-package-maintainers [Org.PackageMaintainers] # https://src.fedoraproject.org/rpms/slirp4netns # https://copr.fedorainfracloud.org/coprs/vbatts/shadow-utils-newxidmap/package/slirp4netns/ [Org.PackageMaintainers.Fedora] people = [ "giuseppe", "lsm5", "rhatdan", "vbatts", ] # https://www.archlinux.org/packages/community/x86_64/slirp4netns/ [Org.PackageMaintainers.Arch] people = [ "barthalion", ] # https://build.opensuse.org/package/show/openSUSE%3AFactory/slirp4netns [Org.PackageMaintainers.openSUSE] people = [ "cyphar", "saschagrunert", ] # https://packages.debian.org/buster/slirp4netns [Org.PackageMaintainers.Debian] people = [ "siretart", ] # https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/networking/slirp4netns [Org.PackageMaintainers.NixOS] people = [ "orivej-nixos", ] # https://packages.gentoo.org/packages/app-emulation/slirp4netns [Org.PackageMaintainers.Gentoo] people = [ "zmedico", "g-braeunlich", ] # https://git.slackbuilds.org/slackbuilds/tree/network/slirp4netns [Org.PackageMaintainers.Slackware] people = [ "vbatts", ] # https://github.com/void-linux/void-packages/tree/master/srcpkgs/slirp4netns [Org.PackageMaintainers.Void] people = [ "cameronnemo", ] # alphabetical order [people] [people.akihirosuda] Name = "Akihiro Suda" Email = "akihiro.suda.cz@hco.ntt.co.jp" GitHub = "AkihiroSuda" [people.barthalion] Name = "Bartłomiej Piotrowski" GitHub = "barthalion" [people.cameronnemo] Name = "Cameron Nemo" GitHub = "CameronNemo" [people.cyphar] Name = "Aleksa Sarai" Email = "cyphar@cyphar.com" GitHub = "cyphar" [people.g-braeunlich] Name = "Gerhard Bräunlich" GitHub = "g-braeunlich" [people.giuseppe] Name = "Giuseppe Scrivano" Email = "giuseppe@scrivano.org" GitHub = "giuseppe" [people.lsm5] Name = "Lokesh Mandvekar" GitHub = "lsm5" [people.rhatdan] Name = "Daniel Walsh" GitHub = "rhatdan" [people.orivej-nixos] Name = "Orivej Desh" GitHub = "orivej-nixos" [people.saschagrunert] Name = "Sascha Grunert" GitHub = "saschagrunert" [people.siretart] Name = "Reinhard Tartler" GitHub = "siretart" [people.vbatts] Name = "Vincent Batts" GitHub = "vbatts" [people.zmedico] Name = "Zac Medico" GitHub = "zmedico" slirp4netns-1.2.1/Makefile.am000066400000000000000000000054631447011446400160510ustar00rootroot00000000000000bin_PROGRAMS = slirp4netns AM_CFLAGS = @GLIB_CFLAGS@ @SLIRP_CFLAGS@ @LIBCAP_CFLAGS@ @LIBSECCOMP_CFLAGS@ noinst_LIBRARIES = libparson.a AM_TESTS_ENVIRONMENT = PATH="$(abs_top_builddir):$(PATH)" TESTS = tests/test-slirp4netns-api-socket.sh \ tests/test-slirp4netns-cidr.sh \ tests/test-slirp4netns-configure.sh \ tests/test-slirp4netns-dhcp.sh \ tests/test-slirp4netns-disable-dns.sh \ tests/test-slirp4netns-disable-host-loopback.sh \ tests/test-slirp4netns-exit-fd.sh \ tests/test-slirp4netns-macaddress.sh \ tests/test-slirp4netns-nspath.sh \ tests/test-slirp4netns-outbound-addr.sh \ tests/test-slirp4netns-ready-fd.sh \ tests/test-slirp4netns-sandbox.sh \ tests/test-slirp4netns-sandbox-no-unmount.sh \ tests/test-slirp4netns-seccomp.sh EXTRA_DIST = \ slirp4netns.1.md \ slirp4netns.1 \ $(TESTS) \ tests/common.sh \ slirp4netns.h \ api.h \ sandbox.h \ seccomparch.h \ seccompfilter.h \ seccompfilter_rules.h \ vendor/parson/LICENSE \ vendor/parson/README.md \ vendor/parson/parson.h # define specific commit if git available or it was replaced during git-archive creation COMMIT := $(shell V=09e31e92fa3d2a1d3ca261adaeb012c8d75a8194 ; \ expr match "$$V" ormat: >/dev/null \ && (cd "$$abs_srcdir" && [ -d .git ] && git describe --always --abbrev=0 --dirty --exclude=\* || echo unknown) \ || echo "$$V" ) DEFINE_COMMIT = -DCOMMIT="\"$(COMMIT)\"" slirp4netns_CFLAGS = $(AM_CFLAGS) $(DEFINE_COMMIT) libparson_a_CFLAGS = $(AM_CFLAGS) -I$(abs_top_builddir)/vendor/parson libparson_a_SOURCES = vendor/parson/parson.c slirp4netns_SOURCES = main.c slirp4netns.c api.c sandbox.c seccompfilter.c slirp4netns_LDADD = libparson.a @GLIB_LIBS@ @SLIRP_LIBS@ @LIBSECCOMP_LIBS@ -lpthread man1_MANS = slirp4netns.1 generate-man: go-md2man -in slirp4netns.1.md -out slirp4netns.1 # clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling is disabled because too nitpicky: https://github.com/rootless-containers/slirp4netns/issues/216 CLANGTIDY = clang-tidy -warnings-as-errors='*' --checks='-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling' CLANGFORMAT = clang-format lint: $(CLANGTIDY) $(slirp4netns_SOURCES) -- $(AM_CFLAGS) lint-full: $(CLANGTIDY) $(slirp4netns_SOURCES) $(libparson_a_SOURCES) -- $(AM_CFLAGS) indent: $(CLANGFORMAT) -i $(slirp4netns_SOURCES) benchmark: "$(abs_srcdir)/benchmarks/benchmark-iperf3.sh" "$(abs_srcdir)/benchmarks/benchmark-iperf3-reverse.sh" ci: $(MAKE) indent git -C "$(abs_srcdir)" diff --exit-code # TODO: make sure ./vendor is synced with ./vendor.sh $(MAKE) lint $(MAKE) -j $(shell nproc) distcheck || ( find . -name test-suite.log | xargs cat; exit 1 ) PATH=$(shell pwd):$$PATH $(MAKE) benchmark MTU=1500 PATH=$(shell pwd):$$PATH $(MAKE) benchmark MTU=65520 .PHONY: generate-man lint lint-full indent benchmark ci slirp4netns-1.2.1/README.md000066400000000000000000000220571447011446400152720ustar00rootroot00000000000000# slirp4netns: User-mode networking for unprivileged network namespaces slirp4netns provides user-mode networking ("slirp") for unprivileged network namespaces. - [Motivation](#motivation) - [Projects using slirp4netns](#projects-using-slirp4netns) - [Maintenance policy](#maintenance-policy) - [Quick start](#quick-start) - [Install](#install) - [Usage](#usage) - [Manual](#manual) - [Benchmarks](#benchmarks) - [iperf3 (netns -> host)](#iperf3-netns---host) - [Install from source](#install-from-source) - [Acknowledgement](#acknowledgement) - [License](#license) ## Motivation Starting with Linux 3.8, unprivileged users can create [`network_namespaces(7)`](http://man7.org/linux/man-pages/man7/network_namespaces.7.html) along with [`user_namespaces(7)`](http://man7.org/linux/man-pages/man7/user_namespaces.7.html). However, unprivileged network namespaces had not been very useful, because creating [`veth(4)`](http://man7.org/linux/man-pages/man4/veth.4.html) pairs across the host and network namespaces still requires the root privileges. (i.e. No internet connection) slirp4netns allows connecting a network namespace to the Internet in a completely unprivileged way, by connecting a TAP device in a network namespace to the usermode TCP/IP stack (["slirp"](https://gitlab.freedesktop.org/slirp/libslirp)). ## Projects using slirp4netns Kubernetes distributions: * [Usernetes](https://github.com/rootless-containers/usernetes) (via RootlessKit) * [k3s](https://k3s.io) (via RootlessKit) Container engines: * [Podman](https://github.com/containers/libpod) * [Buildah](https://github.com/containers/buildah) * [ctnr](https://github.com/mgoltzsche/ctnr) (via slirp-cni-plugin) * [Docker & Moby](https://get.docker.com/rootless) (optionally, via RootlessKit) * [containerd/nerdctl](https://github.com/containerd/nerdctl) (optionally, via RootlessKit) Tools: * [RootlessKit](https://github.com/rootless-containers/rootlesskit) * [become-root](https://github.com/giuseppe/become-root) * [slirp-cni-plugin](https://github.com/mgoltzsche/slirp-cni-plugin) ## Maintenance policy Version | Status -------------------------------|------------------------------------------------------------------------ v1.2.x | :white_check_mark: Active v1.1.x | End of Life (May 2, 2022) v1.0.x | End of Life (Jun 2, 2020) v0.4.x | End of Life (Sep 30, 2020) v0.3.x | End of Life (Mar 31, 2020) v0.2.x | End of Life (Aug 30, 2019) Early versions prior to v0.2.x | End of Life (Jan 5, 2019) See https://github.com/rootless-containers/slirp4netns/releases for the releases. ### Security advisories See https://github.com/rootless-containers/slirp4netns/security/advisories for the past security advisories. :warning: We had been collecting [the vulnerabilities of QEMU/libslirp](https://www.cvedetails.com/product/57329/Libslirp-Project-Libslirp.html?vendor_id=20192) in this slirp4netns repo until the end of 2020, as the slirp4netns releases prior to v1.0.0 were always statically linked with a specific version of QEMU/libslirp. Starting with 2021, the vulnerabilities of libslirp are no longer collected in this slirp4netns repo, as slirp4netns >= v1.0.0 can be linked with an arbitrary version of libslirp.
Run slirp4netns --version to check the version of the linked libslirp.

```console $ slirp4netns --version slirp4netns version 1.1.8 commit: d361001f495417b880f20329121e3aa431a8f90f libslirp: 4.4.0 SLIRP_CONFIG_VERSION_MAX: 3 libseccomp: 2.4.3 ```

## Quick start ### Install Statically linked binaries available for x86\_64, aarch64, armv7l, s390x, ppc64le, and riscv64: https://github.com/rootless-containers/slirp4netns/releases Also available as a package on almost all Linux distributions: * [RHEL/CentOS (since 7.7 and 8.0)](https://pkgs.org/search/?q=slirp4netns) * [Fedora (since 28)](https://src.fedoraproject.org/rpms/slirp4netns) * [Arch Linux](https://www.archlinux.org/packages/community/x86_64/slirp4netns/) * [openSUSE (since Leap 15.0)](https://build.opensuse.org/package/show/openSUSE%3AFactory/slirp4netns) * [SUSE Linux Enterprise (since 15)](https://build.opensuse.org/package/show/devel%3Akubic/slirp4netns) * [Debian GNU/Linux (since 10.0)](https://packages.debian.org/buster/slirp4netns) * [Ubuntu (since 19.04)](https://packages.ubuntu.com/search?keywords=slirp4netns) * [NixOS](https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/networking/slirp4netns) * [Gentoo Linux](https://packages.gentoo.org/packages/app-emulation/slirp4netns) * [Slackware](https://git.slackbuilds.org/slackbuilds/tree/network/slirp4netns) * [Void Linux](https://github.com/void-linux/void-packages/tree/master/srcpkgs/slirp4netns) * [Alpine Linux (since 3.14)](https://pkgs.alpinelinux.org/packages?name=slirp4netns) e.g. ```console $ sudo apt-get install slirp4netns ``` To install slirp4netns from the source, see [Install from source](#install-from-source). ### Usage **Terminal 1**: Create user/network/mount namespaces ```console (host)$ unshare --user --map-root-user --net --mount (namespace)$ echo $$ > /tmp/pid ``` In this documentation, we use `(host)$` as the prompt of the host shell, `(namespace)$` as the prompt of the shell running in the namespaces. If `unshare` fails, try the following commands (known to be needed on Debian, Arch, and old CentOS 7.X): ```console (host)$ sudo sh -c 'echo "user.max_user_namespaces=28633" >> /etc/sysctl.d/userns.conf' (host)$ [ -f /proc/sys/kernel/unprivileged_userns_clone ] && sudo sh -c 'echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.d/userns.conf' (host)$ sudo sysctl --system ``` **Terminal 2**: Start slirp4netns ```console (host)$ slirp4netns --configure --mtu=65520 --disable-host-loopback $(cat /tmp/pid) tap0 starting slirp, MTU=65520 ... ``` **Terminal 1**: Make sure the `tap0` is configured and connected to the Internet ```console (namespace)$ ip a 1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: tap0: mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000 link/ether c2:28:0c:0e:29:06 brd ff:ff:ff:ff:ff:ff inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0 valid_lft forever preferred_lft forever inet6 fe80::c028:cff:fe0e:2906/64 scope link valid_lft forever preferred_lft forever (namespace)$ echo "nameserver 10.0.2.3" > /tmp/resolv.conf (namespace)$ mount --bind /tmp/resolv.conf /etc/resolv.conf (namespace)$ curl https://example.com ``` ## Manual Manual: [`slirp4netns.1.md`](slirp4netns.1.md) * [Description](./slirp4netns.1.md#description) * [Options](./slirp4netns.1.md#options) * [Example](./slirp4netns.1.md#example) * [Routing ping packets](./slirp4netns.1.md#routing-ping-packets) * [API socket](./slirp4netns.1.md#api-socket) * [Defined namespace paths](./slirp4netns.1.md#defined-namespace-paths) * [Outbound addresses](./slirp4netns.1.md#outbound-addresses) * [Inter-namespace communication](./slirp4netns.1.md#inter-namespace-communication) * [Inter-host communication](./slirp4netns.1.md#inter-host-communication) * [Bugs](./slirp4netns.1.md#bugs) ## Benchmarks ### iperf3 (netns -> host) Aug 28, 2018, on [RootlessKit](https://github.com/rootless-containers/rootlesskit) Travis: https://github.com/rootless-containers/rootlesskit/pull/16 Implementation | MTU=1500 | MTU=4000 | MTU=16384 | MTU=65520 ---------------|------------|------------|-------------|------------ vde_plug | 763 Mbps |Unsupported | Unsupported | Unsupported VPNKit | 514 Mbps | 526 Mbps | 540 Mbps | Unsupported slirp4netns | 1.07 Gbps | 2.78 Gbps | 4.55 Gbps | 9.21 Gbps slirp4netns is faster than [vde_plug](https://github.com/rd235/vdeplug_slirp) and [VPNKit](https://github.com/moby/vpnkit) because slirp4netns is optimized to avoid copying packets across the namespaces. The latest revision of slirp4netns is regularly benchmarked (`make benchmark`) on [CI](https://github.com/rootless-containers/slirp4netns/actions?query=workflow%3AMain). ## Install from source Build dependencies (`apt-get`): ```console $ sudo apt-get install libglib2.0-dev libslirp-dev libcap-dev libseccomp-dev ``` Build dependencies (`dnf`): ```console $ sudo dnf install glib2-devel libslirp-devel libcap-devel libseccomp-devel ``` Installation steps: ```console $ ./autogen.sh $ ./configure --prefix=/usr $ make $ sudo make install ``` * [libslirp](https://gitlab.freedesktop.org/slirp/libslirp) needs to be v4.1.0 or later. * To build `slirp4netns` as a static binary, run `./configure` with `LDFLAGS=-static`. * If you set `--prefix` to `$HOME`, you don't need to run `make install` with `sudo`. ## Acknowledgement See [`vendor/README.md`](./vendor/README.md). ## License [GPL-2.0-or-later](COPYING) slirp4netns-1.2.1/SECURITY_CONTACTS000066400000000000000000000002261447011446400164750ustar00rootroot00000000000000If you found a security issue of slirp4netns, please make a contact to "Approvers" (aka Core Maintainers) listed in the `MAINTAINERS` file privately. slirp4netns-1.2.1/Vagrantfile000066400000000000000000000054621447011446400162010ustar00rootroot00000000000000Vagrant.configure("2") do |config| require 'etc' config.vm.provider "virtualbox" do |vbox| vbox.cpus = [1, Etc.nprocessors].max # Change VirtualBox itself's slirp CIDR so that it doesn't conflict with slirp4netns vbox.customize ["modifyvm", :id, "--natnet1", "10.0.200.0/24"] end config.vm.box = "centos/7" config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.synced_folder ".", "/src/slirp4netns", type: "rsync" config.vm.provision "shell", inline: <<~'SHELL' set -xeu sysctl user.max_user_namespaces=65536 yum install -y \ epel-release \ https://repo.ius.io/ius-release-el7.rpm yum install -y \ autoconf automake make gcc gperf libtool \ git-core meson ninja-build \ glib2-devel libcap-devel \ git-core libtool iproute iputils iperf3 nmap jq # TODO: install udhcpc (required by test-slirp4netns-dhcp.sh) cd /src chown vagrant . su vagrant -c ' set -xeu git clone --depth=1 --no-checkout https://github.com/seccomp/libseccomp git -C ./libseccomp fetch --tags --depth=1 git clone --depth=1 --no-checkout https://git.qemu.org/libslirp.git git -C ./libslirp fetch --tags --depth=1 touch ./build-and-test chmod a+x ./build-and-test ' cat > ./build-and-test <<'EOS' #! /bin/sh set -xeu src_dir='/src' prefix="${PREFIX:-${HOME}/prefix}" build_root="${BUILD_ROOT:-${prefix}/build}" rm -rf "${prefix}" "${build_root}" mkdir -p "${build_root}" export CFLAGS="-I${prefix}" export LDFLAGS="-L${prefix} -Wl,-rpath,${prefix}/lib" export PKG_CONFIG_PATH="${prefix}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" git -C "${src_dir}/libseccomp" fetch --depth=1 origin "${LIBSECCOMP_COMMIT:-v2.4.3}" git -C "${src_dir}/libseccomp" checkout FETCH_HEAD ( cd "${src_dir}/libseccomp" && ./autogen.sh ) mkdir "${build_root}/libseccomp" pushd "${build_root}/libseccomp" "${src_dir}/libseccomp/configure" --prefix="${prefix}" make -j "$( nproc )" CFLAGS+="-I$( pwd )/include" make install popd git -C "${src_dir}/libslirp" fetch --depth=1 origin "${LIBSLIRP_COMMIT:-v4.1.0}" git -C "${src_dir}/libslirp" checkout FETCH_HEAD mkdir "${build_root}/libslirp" pushd "${build_root}/libslirp" meson setup --prefix="${prefix}" --libdir=lib . "${src_dir}/libslirp" ninja -C . install popd ( cd "${src_dir}/slirp4netns" && ./autogen.sh ) mkdir "${build_root}/slirp4netns" pushd "${build_root}/slirp4netns" "${src_dir}/slirp4netns/configure" --prefix="${prefix}" make -j "$( nproc )" make ci 'CLANGTIDY=echo skipping:' 'CLANGFORMAT=echo skipping:' popd EOS SHELL end slirp4netns-1.2.1/api.c000066400000000000000000000275071447011446400147350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include #include #include #include #include #include #include "vendor/parson/parson.h" #include "api.h" #include "slirp4netns.h" int api_bindlisten(const char *api_socket) { int fd; struct sockaddr_un addr; unlink(api_socket); /* avoid EADDRINUSE */ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("api_bindlisten: socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (strlen(api_socket) >= sizeof(addr.sun_path)) { fprintf(stderr, "the specified API socket path is too long (>= %lu)\n", sizeof(addr.sun_path)); return -1; } strncpy(addr.sun_path, api_socket, sizeof(addr.sun_path) - 1); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("api_bindlisten: bind"); return -1; } if (listen(fd, 0) < 0) { perror("api_bindlisten: listen"); return -1; } return fd; } struct api_hostfwd { int id; int is_udp; struct in_addr host_addr; int host_port; struct in_addr guest_addr; int guest_port; }; struct api_ctx { uint8_t *buf; size_t buflen; GList *hostfwds; int hostfwds_nextid; struct slirp4netns_config *cfg; }; struct api_ctx *api_ctx_alloc(struct slirp4netns_config *cfg) { struct api_ctx *ctx = (struct api_ctx *)g_malloc0(sizeof(*ctx)); if (ctx == NULL) { return NULL; } ctx->buflen = 4096; ctx->buf = malloc(ctx->buflen); /* FIXME: realloc */ if (ctx->buf == NULL) { free(ctx); return NULL; } ctx->cfg = cfg; ctx->hostfwds = NULL; ctx->hostfwds_nextid = 1; return ctx; } void api_ctx_free(struct api_ctx *ctx) { if (ctx != NULL) { if (ctx->buf != NULL) { free(ctx->buf); } g_list_free_full(ctx->hostfwds, g_free); free(ctx); } } /* Handler for add_hostfwd. e.g. {"execute": "add_hostfwd", "arguments": {"proto": "tcp", "host_addr": "0.0.0.0", "host_port": 8080, "guest_addr": "10.0.2.100", "guest_port": 80}} This function returns the return value of write(2), not the return value of slirp_add_hostfwd(). */ static int api_handle_req_add_hostfwd(Slirp *slirp, int fd, struct api_ctx *ctx, JSON_Object *jo) { int wrc = 0; char idbuf[64]; const char *proto_s = json_object_dotget_string(jo, "arguments.proto"); const char *host_addr_s = json_object_dotget_string(jo, "arguments.host_addr"); const char *guest_addr_s = json_object_dotget_string(jo, "arguments.guest_addr"); struct api_hostfwd *fwd = g_malloc0(sizeof(*fwd)); if (fwd == NULL) { perror("fatal: malloc"); exit(EXIT_FAILURE); } fwd->is_udp = -1; /* TODO: support SCTP */ if (strcmp(proto_s, "udp") == 0) { fwd->is_udp = 1; } else if (strcmp(proto_s, "tcp") == 0) { fwd->is_udp = 0; } if (fwd->is_udp == -1) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "bad arguments.proto\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } if (host_addr_s == NULL || host_addr_s[0] == '\0') { host_addr_s = "0.0.0.0"; } if (inet_pton(AF_INET, host_addr_s, &fwd->host_addr) != 1) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "bad arguments.host_addr\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } fwd->host_port = (int)json_object_dotget_number(jo, "arguments.host_port"); if (fwd->host_port == 0) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "bad arguments.host_port\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } if (guest_addr_s == NULL || guest_addr_s[0] == '\0') { fwd->guest_addr = ctx->cfg->recommended_vguest; } else if (inet_pton(AF_INET, guest_addr_s, &fwd->guest_addr) != 1) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "bad arguments.guest_addr\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } fwd->guest_port = (int)json_object_dotget_number(jo, "arguments.guest_port"); if (fwd->guest_port == 0) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "bad arguments.guest_port\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } if (slirp_add_hostfwd(slirp, fwd->is_udp, fwd->host_addr, fwd->host_port, fwd->guest_addr, fwd->guest_port) < 0) { const char *err = "{\"error\":{\"desc\":\"bad request: add_hostfwd: " "slirp_add_hostfwd failed\"}}"; wrc = write(fd, err, strlen(err)); free(fwd); goto finish; } fwd->id = ctx->hostfwds_nextid; ctx->hostfwds_nextid++; ctx->hostfwds = g_list_append(ctx->hostfwds, fwd); if (snprintf(idbuf, sizeof(idbuf), "{\"return\":{\"id\":%d}}", fwd->id) > sizeof(idbuf)) { fprintf(stderr, "fatal: unexpected id=%d\n", fwd->id); exit(EXIT_FAILURE); } wrc = write(fd, idbuf, strlen(idbuf)); finish: return wrc; } static void api_handle_req_list_hostfwd_foreach(gpointer data, gpointer user_data) { struct api_hostfwd *fwd = data; JSON_Array *entries_array = (JSON_Array *)user_data; JSON_Value *entry_value = json_value_init_object(); JSON_Object *entry_object = json_value_get_object(entry_value); char host_addr[INET_ADDRSTRLEN], guest_addr[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &fwd->host_addr, host_addr, sizeof(host_addr)) == NULL) { perror("fatal: inet_ntop"); exit(EXIT_FAILURE); } if (inet_ntop(AF_INET, &fwd->guest_addr, guest_addr, sizeof(guest_addr)) == NULL) { perror("fatal: inet_ntop"); exit(EXIT_FAILURE); } json_object_set_number(entry_object, "id", fwd->id); json_object_set_string(entry_object, "proto", fwd->is_udp ? "udp" : "tcp"); json_object_set_string(entry_object, "host_addr", host_addr); json_object_set_number(entry_object, "host_port", fwd->host_port); json_object_set_string(entry_object, "guest_addr", guest_addr); json_object_set_number(entry_object, "guest_port", fwd->guest_port); /* json_array_append_value does not copy passed value */ if (json_array_append_value(entries_array, entry_value) != JSONSuccess) { fprintf(stderr, "fatal: json_array_append_value\n"); exit(EXIT_FAILURE); } } /* Handler for list_hostfwd. e.g. {"execute": "list_hostfwd"} */ static int api_handle_req_list_hostfwd(Slirp *slirp, int fd, struct api_ctx *ctx, JSON_Object *jo) { int wrc = 0; JSON_Value *root_value = json_value_init_object(), *entries_value = json_value_init_array(); JSON_Object *root_object = json_value_get_object(root_value); JSON_Array *entries_array = json_array(entries_value); char *serialized_string = NULL; g_list_foreach(ctx->hostfwds, api_handle_req_list_hostfwd_foreach, entries_array); json_object_set_value(root_object, "entries", entries_value); serialized_string = json_serialize_to_string(root_value); wrc = write(fd, serialized_string, strlen(serialized_string)); json_free_serialized_string(serialized_string); json_value_free(root_value); return wrc; } static int api_handle_remove_list_hostfwd_find(gconstpointer gcp_fwd, gconstpointer gcp_id) { struct api_hostfwd *fwd = (struct api_hostfwd *)gcp_fwd; int id = *(int *)gcp_id; return id == fwd->id ? 0 : 1; } /* Handler for remove_hostfwd. e.g. {"execute": "remove_hostfwd", "arguments": {"id": 42}} */ static int api_handle_req_remove_hostfwd(Slirp *slirp, int fd, struct api_ctx *ctx, JSON_Object *jo) { int wrc = 0; int id = (int)json_object_dotget_number(jo, "arguments.id"); GList *found = g_list_find_custom(ctx->hostfwds, &id, api_handle_remove_list_hostfwd_find); if (found == NULL) { const char *err = "{\"error\":{\"desc\":\"bad request: remove_hostfwd: " "bad arguments.id\"}}"; wrc = write(fd, err, strlen(err)); } else { struct api_hostfwd *fwd = found->data; const char *api_ok = "{\"return\":{}}"; if (slirp_remove_hostfwd(slirp, fwd->is_udp, fwd->host_addr, fwd->host_port) < 0) { const char *err = "{\"error\":{\"desc\":\"bad request: " "remove_hostfwd: slirp_remove_hostfwd failed\"}}"; wrc = write(fd, err, strlen(err)); } else { ctx->hostfwds = g_list_remove(ctx->hostfwds, fwd); g_free(fwd); wrc = write(fd, api_ok, strlen(api_ok)); } } return wrc; } static int api_handle_req(Slirp *slirp, int fd, struct api_ctx *ctx) { JSON_Value *jv = NULL; JSON_Object *jo = NULL; const char *execute = NULL; int wrc = 0; if ((jv = json_parse_string((const char *)ctx->buf)) == NULL) { const char *err = "{\"error\":{\"desc\":\"bad request: cannot parse JSON\"}}"; wrc = write(fd, err, strlen(err)); goto finish; } if ((jo = json_object(jv)) == NULL) { const char *err = "{\"error\":{\"desc\":\"bad request: json_object() failed\"}}"; wrc = write(fd, err, strlen(err)); goto finish; } /* TODO: json_validate */ if ((execute = json_object_get_string(jo, "execute")) == NULL) { const char *err = "{\"error\":{\"desc\":\"bad request: no execute found\"}}"; wrc = write(fd, err, strlen(err)); goto finish; } if ((strcmp(execute, "add_hostfwd")) == 0) { wrc = api_handle_req_add_hostfwd(slirp, fd, ctx, jo); } else if ((strcmp(execute, "list_hostfwd")) == 0) { wrc = api_handle_req_list_hostfwd(slirp, fd, ctx, jo); } else if ((strcmp(execute, "remove_hostfwd")) == 0) { wrc = api_handle_req_remove_hostfwd(slirp, fd, ctx, jo); } else { const char *err = "{\"error\":{\"desc\":\"bad request: unknown execute\"}}"; wrc = write(fd, err, strlen(err)); goto finish; } finish: if (jv != NULL) { json_value_free(jv); } return wrc; } /* API handler. This function returns the return value of either read(2) or write(2). */ int api_handler(Slirp *slirp, int listenfd, struct api_ctx *ctx) { struct sockaddr_un addr; socklen_t addrlen = sizeof(struct sockaddr_un); int fd; int rc = 0, wrc = 0; ssize_t len; memset(&addr, 0, sizeof(addr)); if ((fd = accept(listenfd, (struct sockaddr *)&addr, &addrlen)) < 0) { perror("api_handler: accept"); return -1; } if ((len = read(fd, ctx->buf, ctx->buflen)) < 0) { perror("api_handler: read"); rc = len; goto finish; } if (len == ctx->buflen) { const char *err = "{\"error\":{\"desc\":\"bad request: too large message\"}}"; fprintf(stderr, "api_handler: too large message (>= %ld bytes)\n", len); wrc = write(fd, err, strlen(err)); rc = -1; goto finish; } ctx->buf[len] = 0; fprintf(stderr, "api_handler: got request: %s\n", ctx->buf); wrc = api_handle_req(slirp, fd, ctx); finish: shutdown(fd, SHUT_RDWR); if (rc == 0 && wrc != 0) { rc = wrc; } close(fd); return rc; } slirp4netns-1.2.1/api.h000066400000000000000000000005551447011446400147340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SLIRP4NETNS_API_H # define SLIRP4NETNS_API_H int api_bindlisten(const char *api_socket); struct api_ctx; struct slirp4netns_config; struct api_ctx *api_ctx_alloc(struct slirp4netns_config *cfg); void api_ctx_free(struct api_ctx *ctx); int api_handler(Slirp * slirp, int listenfd, struct api_ctx *ctx); #endif slirp4netns-1.2.1/autogen.sh000077500000000000000000000000371447011446400160060ustar00rootroot00000000000000#!/bin/sh exec autoreconf -fis slirp4netns-1.2.1/benchmarks/000077500000000000000000000000001447011446400161225ustar00rootroot00000000000000slirp4netns-1.2.1/benchmarks/benchmark-iperf3-reverse.sh000077500000000000000000000013451447011446400232550ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/../tests/common.sh iperf3 -s > /dev/null & unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child mtu=${MTU:=1500} tmpdir=$(mktemp -d /tmp/slirp4netns-bench.XXXXXXXXXX) apisocket=${tmpdir}/slirp4netns.sock slirp4netns -c --mtu $mtu --api-socket $apisocket $child tun11 & slirp_pid=$! wait_for_network_device $child tun11 wait_for_ping_connectivity $child 10.0.2.2 nsenter --preserve-credentials -U -n --target=$child iperf3 -s -p 15201 > /dev/null & iperf3_pid=$! expose_tcp $apisocket 15201 15201 function cleanup { kill -9 $iperf3_pid $child $slirp_pid rm -rf $tmpdir } trap cleanup EXIT iperf3 -c 127.0.0.1 -p 15201 -t "${BENCHMARK_IPERF3_DURATION:-60}" slirp4netns-1.2.1/benchmarks/benchmark-iperf3.sh000077500000000000000000000010201447011446400215720ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/../tests/common.sh iperf3 -s > /dev/null & iperf3_pid=$! unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child mtu=${MTU:=1500} slirp4netns -c --mtu $mtu $child tun11 & slirp_pid=$! wait_for_network_device $child tun11 wait_for_ping_connectivity $child 10.0.2.2 function cleanup { kill -9 $iperf3_pid $child $slirp_pid } trap cleanup EXIT nsenter --preserve-credentials -U -n --target=$child iperf3 -c 10.0.2.2 -t "${BENCHMARK_IPERF3_DURATION:-60}" slirp4netns-1.2.1/configure.ac000066400000000000000000000020361447011446400162740ustar00rootroot00000000000000AC_PREREQ([2.69]) AC_INIT([slirp4netns], [1.2.1], [https://github.com/rootless-containers/slirp4netns/issues]) AC_CONFIG_SRCDIR([main.c]) AC_CONFIG_HEADERS([config.h]) AC_PROG_CC AC_PROG_RANLIB AM_INIT_AUTOMAKE([1.11.2 foreign subdir-objects]) AM_PROG_AR AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/timeb.h unistd.h getopt.h]) AC_CHECK_HEADER_STDBOOL AC_C_INLINE AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INT8_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINT8_T AC_FUNC_ALLOCA AC_FUNC_STRTOD AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([atexit clock_gettime dup2 getopt_long memmove memset mkdir regcomp rmdir socket strcasecmp strchr strdup strerror strstr strtol strtoul]) PKG_CHECK_MODULES(GLIB, glib-2.0) PKG_CHECK_MODULES(SLIRP, slirp >= 4.1.0) PKG_CHECK_MODULES(LIBCAP, libcap) PKG_CHECK_MODULES(LIBSECCOMP, libseccomp) AC_CONFIG_FILES([Makefile]) AC_OUTPUT slirp4netns-1.2.1/main.c000066400000000000000000001127231447011446400151030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "slirp4netns.h" #include #include #define DEFAULT_MTU (1500) #define DEFAULT_CIDR ("10.0.2.0/24") #define DEFAULT_VHOST_OFFSET (2) // 10.0.2.2 #define DEFAULT_VDHCPSTART_OFFSET (15) // 10.0.2.15 #define DEFAULT_VNAMESERVER_OFFSET (3) // 10.0.2.3 #define DEFAULT_RECOMMENDED_VGUEST_OFFSET (100) // 10.0.2.100 #define DEFAULT_NETNS_TYPE ("pid") #define DEFAULT_TARGET_TYPE ("netns") #define NETWORK_PREFIX_MIN (1) // >=26 is not supported because the recommended guest IP is set to network addr // + 100 . #define NETWORK_PREFIX_MAX (25) static int nsenter(pid_t target_pid, char *netns, char *userns, bool only_userns) { int usernsfd = -1, netnsfd = -1; if (!only_userns && !netns) { if (asprintf(&netns, "/proc/%d/ns/net", target_pid) < 0) { perror("cannot get netns path"); return -1; } } if (!userns && target_pid) { if (asprintf(&userns, "/proc/%d/ns/user", target_pid) < 0) { perror("cannot get userns path"); return -1; } } if (!only_userns && (netnsfd = open(netns, O_RDONLY)) < 0) { perror(netns); return netnsfd; } if (userns && (usernsfd = open(userns, O_RDONLY)) < 0) { perror(userns); return usernsfd; } if (usernsfd != -1) { int r = setns(usernsfd, CLONE_NEWUSER); if (only_userns && r < 0) { perror("setns(CLONE_NEWUSER)"); return -1; } close(usernsfd); } if (netnsfd != -1 && setns(netnsfd, CLONE_NEWNET) < 0) { perror("setns(CLONE_NEWNET)"); return -1; } close(netnsfd); return 0; } static int open_tap(const char *tapname) { int fd; struct ifreq ifr; if (tapname == NULL) { fprintf(stderr, "tapname is NULL\n"); return -1; } if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { perror("open(\"/dev/net/tun\")"); return fd; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strncpy(ifr.ifr_name, tapname, sizeof(ifr.ifr_name) - 1); if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) { perror("ioctl(TUNSETIFF)"); close(fd); return -1; } return fd; } static int sendfd(int sock, int fd) { ssize_t rc; struct msghdr msg; struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(fd))]; struct iovec iov; char dummy = '\0'; memset(&msg, 0, sizeof(msg)); iov.iov_base = &dummy; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); msg.msg_controllen = cmsg->cmsg_len; if ((rc = sendmsg(sock, &msg, 0)) < 0) { perror("sendmsg"); } return rc; } static int configure_network(const char *tapname, struct slirp4netns_config *cfg) { struct rtentry route; struct ifreq ifr; struct sockaddr_in *sai = (struct sockaddr_in *)&ifr.ifr_addr; int sockfd; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("cannot create socket"); return -1; } // set loopback device to UP struct ifreq ifr_lo = { .ifr_name = "lo", .ifr_flags = IFF_UP | IFF_RUNNING }; if (ioctl(sockfd, SIOCSIFFLAGS, &ifr_lo) < 0) { perror("cannot set device up"); return -1; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_UP | IFF_RUNNING; strncpy(ifr.ifr_name, tapname, sizeof(ifr.ifr_name) - 1); if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { perror("cannot set device up"); return -1; } ifr.ifr_mtu = (int)cfg->mtu; if (ioctl(sockfd, SIOCSIFMTU, &ifr) < 0) { perror("cannot set MTU"); return -1; } if (cfg->vmacaddress_len > 0) { ifr.ifr_ifru.ifru_hwaddr = cfg->vmacaddress; if (ioctl(sockfd, SIOCSIFHWADDR, &ifr) < 0) { perror("cannot set MAC address"); return -1; } } sai->sin_family = AF_INET; sai->sin_port = 0; sai->sin_addr = cfg->recommended_vguest; if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) { perror("cannot set device address"); return -1; } sai->sin_addr = cfg->vnetmask; if (ioctl(sockfd, SIOCSIFNETMASK, &ifr) < 0) { perror("cannot set device netmask"); return -1; } memset(&route, 0, sizeof(route)); sai = (struct sockaddr_in *)&route.rt_gateway; sai->sin_family = AF_INET; sai->sin_addr = cfg->vhost; sai = (struct sockaddr_in *)&route.rt_dst; sai->sin_family = AF_INET; sai->sin_addr.s_addr = INADDR_ANY; sai = (struct sockaddr_in *)&route.rt_genmask; sai->sin_family = AF_INET; sai->sin_addr.s_addr = INADDR_ANY; route.rt_flags = RTF_UP | RTF_GATEWAY; route.rt_metric = 0; route.rt_dev = (char *)tapname; if (ioctl(sockfd, SIOCADDRT, &route) < 0) { perror("set route"); return -1; } return 0; } /* Child (--target-type=netns) */ static int child(int sock, pid_t target_pid, bool do_config_network, const char *tapname, char *netns_path, char *userns_path, struct slirp4netns_config *cfg) { int rc, tapfd; if ((rc = nsenter(target_pid, netns_path, userns_path, false)) < 0) { return rc; } if ((tapfd = open_tap(tapname)) < 0) { return tapfd; } if (do_config_network && configure_network(tapname, cfg) < 0) { return -1; } if (sendfd(sock, tapfd) < 0) { close(tapfd); close(sock); return -1; } fprintf(stderr, "sent tapfd=%d for %s\n", tapfd, tapname); close(sock); return 0; } /* * Child (--target-type=bess) * * FIXME: We do not really need to fork the child for BESS mode */ static int child_bess(int sock, const char *bess_socket) { int bess_listenfd = -1, bess_acceptfd = -1; struct sockaddr_un bess_listenaddr, bess_acceptaddr; socklen_t bess_acceptaddrlen = sizeof(struct sockaddr_un); memset(&bess_acceptaddr, 0, sizeof(bess_acceptaddr)); memset(&bess_listenaddr, 0, sizeof(bess_listenaddr)); /* Listen on the BESS socket */ if ((bess_listenfd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) == -1) { perror("child_bess: socket"); goto error; } bess_listenaddr.sun_family = AF_UNIX; if (bess_socket == NULL) { fprintf(stderr, "the specified BESS socket path is NULL\n"); goto error; } if (strlen(bess_socket) >= sizeof(bess_listenaddr.sun_path)) { fprintf(stderr, "the specified BESS socket path is too long (>= %lu)\n", sizeof(bess_listenaddr.sun_path)); goto error; } strncpy(bess_listenaddr.sun_path, bess_socket, sizeof(bess_listenaddr.sun_path) - 1); unlink(bess_socket); /* avoid EADDRINUSE */ if (bind(bess_listenfd, (struct sockaddr *)&bess_listenaddr, sizeof(bess_listenaddr)) < 0) { perror("child_bess: bind"); goto error; } if (listen(bess_listenfd, 0) < 0) { perror("child_bess: listen"); goto error; } /* Accept a connection, and send its connection to the parent */ fprintf(stderr, "Waiting for connection to %s\n", bess_socket); if ((bess_acceptfd = accept(bess_listenfd, (struct sockaddr *)&bess_acceptaddr, &bess_acceptaddrlen)) < 0) { perror("child_bess: accept"); goto error; } if (sendfd(sock, bess_acceptfd) < 0) { goto error; } fprintf(stderr, "sent \"tapfd\"=%d (not really TAP) for %s\n", bess_acceptfd, bess_socket); close(sock); close(bess_listenfd); return 0; error: close(bess_acceptfd); close(bess_listenfd); close(sock); return -1; } static int recvfd(int sock) { int fd; ssize_t rc; struct msghdr msg; struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(fd))]; struct iovec iov; char dummy = '\0'; memset(&msg, 0, sizeof(msg)); iov.iov_base = &dummy; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); if ((rc = recvmsg(sock, &msg, 0)) < 0) { perror("recvmsg"); return (int)rc; } if (rc == 0) { fprintf(stderr, "the message is empty\n"); return -1; } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg == NULL || cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "the message does not contain fd\n"); return -1; } memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd)); return fd; } static int parent(int sock, int ready_fd, int exit_fd, const char *api_socket, struct slirp4netns_config *cfg, pid_t target_pid) { char str[INET6_ADDRSTRLEN]; int rc, tapfd; struct in_addr vdhcp_end = { #define NB_BOOTP_CLIENTS 16 /* NB_BOOTP_CLIENTS is hard-coded to 16 in libslirp: https://gitlab.freedesktop.org/slirp/libslirp/-/issues/49 */ .s_addr = htonl(ntohl(cfg->vdhcp_start.s_addr) + NB_BOOTP_CLIENTS - 1), #undef NB_BOOTP_CLIENTS }; if ((tapfd = recvfd(sock)) < 0) { return tapfd; } fprintf(stderr, "received tapfd=%d\n", tapfd); close(sock); printf("Starting slirp\n"); printf("* MTU: %d\n", cfg->mtu); printf("* Network: %s\n", inet_ntop(AF_INET, &cfg->vnetwork, str, sizeof(str))); printf("* Netmask: %s\n", inet_ntop(AF_INET, &cfg->vnetmask, str, sizeof(str))); printf("* Gateway: %s\n", inet_ntop(AF_INET, &cfg->vhost, str, sizeof(str))); printf("* DNS: %s\n", inet_ntop(AF_INET, &cfg->vnameserver, str, sizeof(str))); printf("* DHCP begin: %s\n", inet_ntop(AF_INET, &cfg->vdhcp_start, str, sizeof(str))); printf("* DHCP end: %s\n", inet_ntop(AF_INET, &vdhcp_end, str, sizeof(str))); printf("* Recommended IP: %s\n", inet_ntop(AF_INET, &cfg->recommended_vguest, str, sizeof(str))); if (api_socket != NULL) { printf("* API Socket: %s\n", api_socket); } #if SLIRP_CONFIG_VERSION_MAX >= 2 if (cfg->enable_outbound_addr) { printf( "* Outbound IPv4: %s\n", inet_ntop(AF_INET, &cfg->outbound_addr.sin_addr, str, sizeof(str))); } if (cfg->enable_outbound_addr6) { if (inet_ntop(AF_INET6, &cfg->outbound_addr6.sin6_addr, str, sizeof(str)) != NULL) { printf("* Outbound IPv6: %s\n", str); } } #endif if (cfg->vmacaddress_len > 0) { unsigned char *mac = (unsigned char *)cfg->vmacaddress.sa_data; printf("* MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (!cfg->disable_host_loopback) { printf( "WARNING: 127.0.0.1:* on the host is accessible as %s (set " "--disable-host-loopback to prohibit connecting to 127.0.0.1:*)\n", inet_ntop(AF_INET, &cfg->vhost, str, sizeof(str))); } if (cfg->enable_sandbox && geteuid() != 0) { if ((rc = nsenter(target_pid, NULL, NULL, true)) < 0) { close(tapfd); return rc; } if ((rc = setegid(0)) < 0) { fprintf(stderr, "setegid(0)\n"); close(tapfd); return rc; } if ((rc = seteuid(0)) < 0) { fprintf(stderr, "seteuid(0)\n"); close(tapfd); return rc; } } if ((rc = do_slirp(tapfd, ready_fd, exit_fd, api_socket, cfg)) < 0) { fprintf(stderr, "do_slirp failed\n"); close(tapfd); return rc; } /* NOT REACHED */ return 0; } static void usage(const char *argv0) { printf("Usage: %s [OPTION]... PID|PATH [TAPNAME]\n", argv0); printf("User-mode networking for unprivileged network namespaces.\n\n"); printf("-c, --configure bring up the interface\n"); printf("-e, --exit-fd=FD specify the FD for terminating " "slirp4netns\n"); printf("-r, --ready-fd=FD specify the FD to write to when the " "network is configured\n"); /* v0.2.0 */ printf("-m, --mtu=MTU specify MTU (default=%d, max=65521)\n", DEFAULT_MTU); printf("-6, --enable-ipv6 enable IPv6 (experimental)\n"); /* v0.3.0 */ printf("-a, --api-socket=PATH specify API socket path\n"); printf( "--cidr=CIDR specify network address CIDR (default=%s)\n", DEFAULT_CIDR); printf("--disable-host-loopback prohibit connecting to 127.0.0.1:* on the " "host namespace\n"); /* v0.4.0 */ printf("--netns-type=TYPE specify network namespace type ([path|pid], " "default=%s)\n", DEFAULT_NETNS_TYPE); printf("--userns-path=PATH specify user namespace path\n"); printf( "--enable-sandbox create a new mount namespace (and drop all " "caps except CAP_NET_BIND_SERVICE if running as the root)\n"); printf("--enable-seccomp enable seccomp to limit syscalls " "(experimental)\n"); /* v1.1.0 */ #if SLIRP_CONFIG_VERSION_MAX >= 2 printf("--outbound-addr=IPv4 sets outbound ipv4 address to bound to " "(experimental)\n"); printf("--outbound-addr6=IPv6 sets outbound ipv6 address to bound to " "(experimental)\n"); #endif #if SLIRP_CONFIG_VERSION_MAX >= 3 printf("--disable-dns disables 10.0.2.3 (or configured internal " "ip) to host dns redirect (experimental)\n"); #endif /* v1.1.9 */ printf("--macaddress=MAC specify the MAC address of the TAP (only " "valid with -c)\n"); /* v1.2.0 */ printf("--target-type=TYPE specify the target type ([netns|bess], " "default=%s)\n", DEFAULT_TARGET_TYPE); /* others */ printf("-h, --help show this help and exit\n"); printf("-v, --version show version and exit\n"); } // version output is runc-compatible and machine-parsable static void version() { const struct scmp_version *scmpv = seccomp_version(); printf("slirp4netns version %s\n", VERSION ? VERSION : PACKAGE_VERSION); #ifdef COMMIT printf("commit: %s\n", COMMIT); #endif printf("libslirp: %s\n", slirp_version_string()); printf("SLIRP_CONFIG_VERSION_MAX: %d\n", SLIRP_CONFIG_VERSION_MAX); if (scmpv != NULL) { printf("libseccomp: %d.%d.%d\n", scmpv->major, scmpv->minor, scmpv->micro); /* Do not free scmpv */ } } struct options { char *tapname; // argv[2] char *cidr; // --cidr char *api_socket; // -a char *netns_type; // argv[1] char *netns_path; // --netns-path char *userns_path; // --userns-path char *outbound_addr; // --outbound-addr char *outbound_addr6; // --outbound-addr6 pid_t target_pid; // argv[1] int exit_fd; // -e int ready_fd; // -r unsigned int mtu; // -m bool do_config_network; // -c bool disable_host_loopback; // --disable-host-loopback bool enable_ipv6; // -6 bool enable_sandbox; // --enable-sandbox bool enable_seccomp; // --enable-seccomp bool disable_dns; // --disable-dns char *macaddress; // --macaddress char *target_type; // --target-type char *bess_socket; // argv[1] (When --target-type="bess") }; static void options_init(struct options *options) { memset(options, 0, sizeof(*options)); options->exit_fd = options->ready_fd = -1; options->mtu = DEFAULT_MTU; } static void options_destroy(struct options *options) { if (options->tapname != NULL) { free(options->tapname); options->tapname = NULL; } if (options->cidr != NULL) { free(options->cidr); options->cidr = NULL; } if (options->api_socket != NULL) { free(options->api_socket); options->api_socket = NULL; } if (options->netns_type != NULL) { free(options->netns_type); options->netns_type = NULL; } if (options->netns_path != NULL) { free(options->netns_path); options->netns_path = NULL; } if (options->userns_path != NULL) { free(options->userns_path); options->userns_path = NULL; } if (options->outbound_addr != NULL) { free(options->outbound_addr); options->outbound_addr = NULL; } if (options->outbound_addr6 != NULL) { free(options->outbound_addr6); options->outbound_addr6 = NULL; } if (options->macaddress != NULL) { free(options->macaddress); options->macaddress = NULL; } if (options->target_type != NULL) { free(options->target_type); options->target_type = NULL; } if (options->bess_socket != NULL) { free(options->bess_socket); options->bess_socket = NULL; } } // * caller does not need to call options_init() // * caller needs to call options_destroy() after calling this function. // * this function calls exit() on an error. static void parse_args(int argc, char *const argv[], struct options *options) { int opt; char *strtol_e = NULL; char *optarg_cidr = NULL; char *optarg_netns_type = NULL; char *optarg_userns_path = NULL; char *optarg_api_socket = NULL; char *optarg_outbound_addr = NULL; char *optarg_outbound_addr6 = NULL; char *optarg_macaddress = NULL; char *optarg_target_type = NULL; #define CIDR -42 #define DISABLE_HOST_LOOPBACK -43 #define NETNS_TYPE -44 #define USERNS_PATH -45 #define ENABLE_SANDBOX -46 #define ENABLE_SECCOMP -47 #define OUTBOUND_ADDR -48 #define OUTBOUND_ADDR6 -49 #define DISABLE_DNS -50 #define MACADDRESS -51 #define TARGET_TYPE -52 #define _DEPRECATED_NO_HOST_LOOPBACK \ -10043 // deprecated in favor of disable-host-loopback #define _DEPRECATED_CREATE_SANDBOX \ -10044 // deprecated in favor of enable-sandbox const struct option longopts[] = { { "configure", no_argument, NULL, 'c' }, { "exit-fd", required_argument, NULL, 'e' }, { "ready-fd", required_argument, NULL, 'r' }, { "mtu", required_argument, NULL, 'm' }, { "cidr", required_argument, NULL, CIDR }, { "disable-host-loopback", no_argument, NULL, DISABLE_HOST_LOOPBACK }, { "no-host-loopback", no_argument, NULL, _DEPRECATED_NO_HOST_LOOPBACK }, { "netns-type", required_argument, NULL, NETNS_TYPE }, { "userns-path", required_argument, NULL, USERNS_PATH }, { "api-socket", required_argument, NULL, 'a' }, { "enable-ipv6", no_argument, NULL, '6' }, { "enable-sandbox", no_argument, NULL, ENABLE_SANDBOX }, { "create-sandbox", no_argument, NULL, _DEPRECATED_CREATE_SANDBOX }, { "enable-seccomp", no_argument, NULL, ENABLE_SECCOMP }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "outbound-addr", required_argument, NULL, OUTBOUND_ADDR }, { "outbound-addr6", required_argument, NULL, OUTBOUND_ADDR6 }, { "disable-dns", no_argument, NULL, DISABLE_DNS }, { "macaddress", required_argument, NULL, MACADDRESS }, { "target-type", required_argument, NULL, TARGET_TYPE }, { 0, 0, 0, 0 }, }; options_init(options); /* NOTE: clang-tidy hates strdup(optarg) in the while loop (#112) */ while ((opt = getopt_long(argc, argv, "ce:r:m:a:6hv", longopts, NULL)) != -1) { switch (opt) { case 'c': options->do_config_network = true; break; case 'e': errno = 0; options->exit_fd = strtol(optarg, &strtol_e, 10); if (errno || *strtol_e != '\0' || options->exit_fd < 0) { fprintf(stderr, "exit-fd must be a non-negative integer\n"); goto error; } break; case 'r': errno = 0; options->ready_fd = strtol(optarg, &strtol_e, 10); if (errno || *strtol_e != '\0' || options->ready_fd < 0) { fprintf(stderr, "ready-fd must be a non-negative integer\n"); goto error; } break; case 'm': errno = 0; options->mtu = strtol(optarg, &strtol_e, 10); if (errno || *strtol_e != '\0' || options->mtu <= 0 || options->mtu > 65521) { fprintf(stderr, "MTU must be a positive integer (< 65522)\n"); goto error; } break; case CIDR: optarg_cidr = optarg; break; case _DEPRECATED_NO_HOST_LOOPBACK: // There was no tagged release with support for --no-host-loopback. // So no one will be affected by removal of --no-host-loopback. printf("WARNING: --no-host-loopback is deprecated and will be " "removed in future releases, please use " "--disable-host-loopback instead.\n"); /* FALLTHROUGH */ case DISABLE_HOST_LOOPBACK: options->disable_host_loopback = true; break; case _DEPRECATED_CREATE_SANDBOX: // There was no tagged release with support for --create-sandbox. // So no one will be affected by removal of --create-sandbox. printf("WARNING: --create-sandbox is deprecated and will be " "removed in future releases, please use " "--enable-sandbox instead.\n"); /* FALLTHROUGH */ case ENABLE_SANDBOX: options->enable_sandbox = true; break; case ENABLE_SECCOMP: printf("WARNING: Support for seccomp is experimental\n"); options->enable_seccomp = true; break; case NETNS_TYPE: optarg_netns_type = optarg; break; case USERNS_PATH: optarg_userns_path = optarg; if (access(optarg_userns_path, F_OK) == -1) { fprintf(stderr, "userns path doesn't exist: %s\n", optarg_userns_path); goto error; } break; case 'a': optarg_api_socket = optarg; break; case '6': options->enable_ipv6 = true; printf("WARNING: Support for IPv6 is experimental\n"); break; case 'h': usage(argv[0]); exit(EXIT_SUCCESS); break; case 'v': version(); exit(EXIT_SUCCESS); break; case OUTBOUND_ADDR: printf("WARNING: Support for --outbound-addr is experimental\n"); optarg_outbound_addr = optarg; break; case OUTBOUND_ADDR6: printf("WARNING: Support for --outbound-addr6 is experimental\n"); optarg_outbound_addr6 = optarg; break; case DISABLE_DNS: options->disable_dns = true; break; case MACADDRESS: optarg_macaddress = optarg; break; case TARGET_TYPE: optarg_target_type = optarg; break; default: goto error; break; } } if (optarg_cidr != NULL) { options->cidr = strdup(optarg_cidr); } if (optarg_netns_type != NULL) { options->netns_type = strdup(optarg_netns_type); } if (optarg_userns_path != NULL) { options->userns_path = strdup(optarg_userns_path); } if (optarg_api_socket != NULL) { options->api_socket = strdup(optarg_api_socket); } if (optarg_outbound_addr != NULL) { options->outbound_addr = strdup(optarg_outbound_addr); } if (optarg_outbound_addr6 != NULL) { options->outbound_addr6 = strdup(optarg_outbound_addr6); } if (optarg_macaddress != NULL) { if (!options->do_config_network) { fprintf(stderr, "--macaddr cannot be specified when --configure or " "-c is not specified\n"); goto error; } else { options->macaddress = strdup(optarg_macaddress); } } if (optarg_target_type != NULL) { options->target_type = strdup(optarg_target_type); } #undef CIDR #undef DISABLE_HOST_LOOPBACK #undef NETNS_TYPE #undef USERNS_PATH #undef _DEPRECATED_NO_HOST_LOOPBACK #undef ENABLE_SANDBOX #undef ENABLE_SECCOMP #undef OUTBOUND_ADDR #undef OUTBOUND_ADDR6 #undef DISABLE_DNS #undef MACADDRESS #undef TARGET_TYPE /* BESS mode */ if (options->target_type != NULL && strcmp(options->target_type, "bess") == 0) { if (argc - optind < 1) { goto error; } if (argc - optind > 1) { fprintf(stderr, "too many arguments\n"); goto error; } options->bess_socket = strdup(argv[optind]); if (options->do_config_network) { fprintf(stderr, "--configure conflicts with --target-type=bess\n"); goto error; } if (options->netns_type != NULL) { fprintf(stderr, "--netns-type conflicts with --target-type=bess\n"); goto error; } if (options->userns_path != NULL) { fprintf(stderr, "--userns-path conflicts with --target-type=bess\n"); goto error; } printf("WARNING: BESS mode is experimental\n"); return; } /* NetNS mode*/ if (options->target_type != NULL && strcmp(options->target_type, "netns") != 0) { fprintf(stderr, "--target-type must be either \"netns\" or \"bess\"\n"); goto error; } if (argc - optind < 2) { goto error; } if (argc - optind > 2) { // not an error, for preventing potential compatibility issue printf("WARNING: too many arguments\n"); } if (!options->netns_type || strcmp(options->netns_type, DEFAULT_NETNS_TYPE) == 0) { errno = 0; options->target_pid = strtol(argv[optind], &strtol_e, 10); if (errno || *strtol_e != '\0' || options->target_pid <= 0) { fprintf(stderr, "PID must be a positive integer\n"); goto error; } } else { options->netns_path = strdup(argv[optind]); if (access(options->netns_path, F_OK) == -1) { perror("existing path expected when --netns-type=path"); goto error; } } options->tapname = strdup(argv[optind + 1]); return; error: usage(argv[0]); options_destroy(options); exit(EXIT_FAILURE); } static int from_regmatch(char *buf, size_t buf_len, regmatch_t match, const char *orig) { size_t len = match.rm_eo - match.rm_so; if (len > buf_len - 1) { return -1; } memset(buf, 0, buf_len); strncpy(buf, &orig[match.rm_so], len); return 0; } static int parse_cidr(struct in_addr *network, struct in_addr *netmask, const char *cidr) { int rc = 0; regex_t r; regmatch_t matches[4]; size_t nmatch = sizeof(matches) / sizeof(matches[0]); const char *cidr_regex = "^(([0-9]{1,3}\\.){3}[0-9]{1,3})/([0-9]{1,2})$"; char snetwork[16], sprefix[16]; int prefix; rc = regcomp(&r, cidr_regex, REG_EXTENDED); if (rc != 0) { fprintf(stderr, "internal regex error\n"); rc = -1; goto finish; } rc = regexec(&r, cidr, nmatch, matches, 0); if (rc != 0) { fprintf(stderr, "invalid CIDR: %s\n", cidr); rc = -1; goto finish; } rc = from_regmatch(snetwork, sizeof(snetwork), matches[1], cidr); if (rc < 0) { fprintf(stderr, "invalid CIDR: %s\n", cidr); goto finish; } rc = from_regmatch(sprefix, sizeof(sprefix), matches[3], cidr); if (rc < 0) { fprintf(stderr, "invalid CIDR: %s\n", cidr); goto finish; } if (inet_pton(AF_INET, snetwork, network) != 1) { fprintf(stderr, "invalid network address: %s\n", snetwork); rc = -1; goto finish; } errno = 0; prefix = strtoul(sprefix, NULL, 10); if (errno) { fprintf(stderr, "invalid prefix length: %s\n", sprefix); rc = -1; goto finish; } if (prefix < NETWORK_PREFIX_MIN || prefix > NETWORK_PREFIX_MAX) { fprintf(stderr, "prefix length needs to be %d-%d\n", NETWORK_PREFIX_MIN, NETWORK_PREFIX_MAX); rc = -1; goto finish; } netmask->s_addr = htonl(~((1 << (32 - prefix)) - 1)); if ((network->s_addr & netmask->s_addr) != network->s_addr) { fprintf(stderr, "CIDR needs to be a network address like 10.0.2.0/24, " "not like 10.0.2.100/24\n"); rc = -1; goto finish; } finish: regfree(&r); return rc; } static int slirp4netns_config_from_cidr(struct slirp4netns_config *cfg, const char *cidr) { int rc; rc = parse_cidr(&cfg->vnetwork, &cfg->vnetmask, cidr); if (rc < 0) { goto finish; } cfg->vhost.s_addr = htonl(ntohl(cfg->vnetwork.s_addr) + DEFAULT_VHOST_OFFSET); cfg->vdhcp_start.s_addr = htonl(ntohl(cfg->vnetwork.s_addr) + DEFAULT_VDHCPSTART_OFFSET); cfg->vnameserver.s_addr = htonl(ntohl(cfg->vnetwork.s_addr) + DEFAULT_VNAMESERVER_OFFSET); cfg->recommended_vguest.s_addr = htonl(ntohl(cfg->vnetwork.s_addr) + DEFAULT_RECOMMENDED_VGUEST_OFFSET); finish: return rc; } static int get_interface_addr(const char *interface, int af, void *addr) { struct ifaddrs *ifaddr, *ifa; if (interface == NULL) return -1; if (getifaddrs(&ifaddr) == -1) { fprintf(stderr, "getifaddrs failed to obtain interface addresses"); return -1; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL) continue; if (ifa->ifa_addr->sa_family == af) { if (strcmp(ifa->ifa_name, interface) == 0) { if (af == AF_INET) { *(struct in_addr *)addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; } else { *(struct in6_addr *)addr = ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; } return 0; } } } return -1; } /* * Convert a MAC string (macaddr) to bytes (data). * macaddr must be a null-terminated string in the format of * xx:xx:xx:xx:xx:xx. The data buffer needs to be at least 6 bytes. * Typically the data is put into sockaddr sa_data, which is 14 bytes. */ static int slirp4netns_macaddr_hexstring_to_data(char *macaddr, char *data) { int temp; char *macaddr_ptr; char *data_ptr; for (macaddr_ptr = macaddr, data_ptr = data; macaddr_ptr != NULL && data_ptr < data + 6; macaddr_ptr = strchr(macaddr_ptr, ':'), data_ptr++) { if (macaddr_ptr != macaddr) { macaddr_ptr++; // advance over the : } if (sscanf(macaddr_ptr, "%x", &temp) != 1 || temp < 0 || temp > 255) { fprintf(stderr, "\"%s\" is an invalid MAC address.\n", macaddr); return -1; } *data_ptr = temp; } if (macaddr_ptr != NULL) { fprintf(stderr, "\"%s\" is an invalid MAC address. Is it too long?\n", macaddr); return -1; } return (int)(data_ptr - data); } static int slirp4netns_config_from_options(struct slirp4netns_config *cfg, struct options *opt) { int rc = 0; cfg->mtu = opt->mtu; rc = slirp4netns_config_from_cidr(cfg, opt->cidr == NULL ? DEFAULT_CIDR : opt->cidr); if (rc < 0) { goto finish; } cfg->enable_ipv6 = opt->enable_ipv6; cfg->disable_host_loopback = opt->disable_host_loopback; cfg->enable_sandbox = opt->enable_sandbox; cfg->enable_seccomp = opt->enable_seccomp; #if SLIRP_CONFIG_VERSION_MAX >= 2 cfg->enable_outbound_addr = false; cfg->enable_outbound_addr6 = false; #endif if (opt->outbound_addr != NULL) { #if SLIRP_CONFIG_VERSION_MAX >= 2 cfg->outbound_addr.sin_family = AF_INET; cfg->outbound_addr.sin_port = 0; // Any local port will do if (inet_pton(AF_INET, opt->outbound_addr, &cfg->outbound_addr.sin_addr) == 1) { cfg->enable_outbound_addr = true; } else { if (get_interface_addr(opt->outbound_addr, AF_INET, &cfg->outbound_addr.sin_addr) != 0) { fprintf(stderr, "outbound-addr has to be valid ipv4 address or " "interface name."); rc = -1; goto finish; } cfg->enable_outbound_addr = true; } #else fprintf(stderr, "slirp4netns has to be compiled against libslrip 4.2.0 " "or newer for --outbound-addr support."); rc = -1; goto finish; #endif } if (opt->outbound_addr6 != NULL) { #if SLIRP_CONFIG_VERSION_MAX >= 2 cfg->outbound_addr6.sin6_family = AF_INET6; cfg->outbound_addr6.sin6_port = 0; // Any local port will do if (inet_pton(AF_INET6, opt->outbound_addr6, &cfg->outbound_addr6.sin6_addr) == 1) { cfg->enable_outbound_addr6 = true; } else { if (get_interface_addr(opt->outbound_addr, AF_INET6, &cfg->outbound_addr6.sin6_addr) != 0) { fprintf(stderr, "outbound-addr has to be valid ipv4 address or " "iterface name."); rc = -1; goto finish; } cfg->enable_outbound_addr6 = true; } #else fprintf(stderr, "slirp4netns has to be compiled against libslirp 4.2.0 " "or newer for --outbound-addr6 support."); rc = -1; goto finish; #endif } #if SLIRP_CONFIG_VERSION_MAX >= 3 cfg->disable_dns = opt->disable_dns; #else if (opt->disable_dns) { fprintf(stderr, "slirp4netns has to be compiled against libslirp 4.3.0 " "or newer for --disable-dns support."); rc = -1; goto finish; } #endif cfg->vmacaddress_len = 0; memset(&cfg->vmacaddress, 0, sizeof(cfg->vmacaddress)); if (opt->macaddress != NULL) { cfg->vmacaddress.sa_family = AF_LOCAL; int macaddr_len; if ((macaddr_len = slirp4netns_macaddr_hexstring_to_data( opt->macaddress, cfg->vmacaddress.sa_data)) < 0) { fprintf(stderr, "macaddress has to be a valid MAC address (hex " "string, 6 bytes, each byte separated by a ':')."); rc = -1; goto finish; } cfg->vmacaddress_len = macaddr_len; } finish: return rc; } int main(int argc, char *const argv[]) { int sv[2]; pid_t child_pid; struct options options; struct slirp4netns_config slirp4netns_config; int exit_status = 0; parse_args(argc, argv, &options); if (slirp4netns_config_from_options(&slirp4netns_config, &options) < 0) { exit_status = EXIT_FAILURE; goto finish; } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); exit_status = EXIT_FAILURE; goto finish; } if ((child_pid = fork()) < 0) { perror("fork"); exit_status = EXIT_FAILURE; goto finish; } if (child_pid == 0) { int ret; if (options.target_type != NULL && strcmp(options.target_type, "bess") == 0) { ret = child_bess(sv[1], options.bess_socket); } else { ret = child(sv[1], options.target_pid, options.do_config_network, options.tapname, options.netns_path, options.userns_path, &slirp4netns_config); } if (ret < 0) { exit_status = EXIT_FAILURE; goto finish; } } else { int ret, child_wstatus, child_status; do ret = waitpid(child_pid, &child_wstatus, 0); while (ret < 0 && errno == EINTR); if (ret < 0) { perror("waitpid"); exit_status = EXIT_FAILURE; goto finish; } if (!WIFEXITED(child_wstatus)) { fprintf(stderr, "child failed(wstatus=%d, !WIFEXITED)\n", child_wstatus); exit_status = EXIT_FAILURE; goto finish; } child_status = WEXITSTATUS(child_wstatus); if (child_status != 0) { fprintf(stderr, "child failed(%d)\n", child_status); exit_status = child_status; goto finish; } if (parent(sv[0], options.ready_fd, options.exit_fd, options.api_socket, &slirp4netns_config, options.target_pid) < 0) { fprintf(stderr, "parent failed\n"); exit_status = EXIT_FAILURE; goto finish; } } finish: options_destroy(&options); exit(exit_status); return 0; } slirp4netns-1.2.1/sandbox.c000066400000000000000000000136471447011446400156220ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include static int add_mount(const char *from, const char *to) { int ret; ret = mount("", from, "", MS_SLAVE | MS_REC, NULL); if (ret < 0 && errno != EINVAL) { fprintf(stderr, "cannot make mount propagation slave %s\n", from); return ret; } ret = mount(from, to, "", MS_BIND | MS_REC | MS_SLAVE | MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL); if (ret < 0) { fprintf(stderr, "cannot bind mount %s to %s (errno: %d)\n", from, to, errno); return ret; } ret = mount("", to, "", MS_SLAVE | MS_REC, NULL); if (ret < 0) { fprintf(stderr, "cannot make mount propagation slave %s\n", to); return ret; } ret = mount(from, to, "", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL); if (ret < 0) { fprintf(stderr, "cannot remount ro %s\n", to); return ret; } return 0; } /* Bind /etc/resolv.conf if it is a symlink to a file outside /etc or * /run. */ static int bind_escaped_resolv_conf(const char *root) { char *real_resolv = realpath("/etc/resolv.conf", NULL); /* Doesn't exist or is not an escaping symlink */ if (real_resolv == NULL || g_str_has_prefix(real_resolv, "/etc") || g_str_has_prefix(real_resolv, "/run")) { free(real_resolv); return 0; } char *resolv_dest = g_strconcat(root, real_resolv, NULL); char *resolv_dest_dir = g_path_get_dirname(resolv_dest); int ret = 0; fprintf(stderr, "sandbox: /etc/resolv.conf (-> %s) seems a symlink to a file " "outside {/etc, /run}, attempting to bind it as well.\n", real_resolv); ret = g_mkdir_with_parents(resolv_dest_dir, 0755); if (ret < 0) { fprintf(stderr, "cannot create resolve dest dir path: %s\n", resolv_dest_dir); goto finish; } ret = creat(resolv_dest, 0644); if (ret < 0) { fprintf(stderr, "cannot create empty resolv.conf dest file %s\n", resolv_dest); goto finish; } close(ret); ret = add_mount(real_resolv, resolv_dest); if (ret < 0) { fprintf(stderr, "cannot bind mount resolv.conf\n"); } finish: free(real_resolv); g_free(resolv_dest); g_free(resolv_dest_dir); return ret; } /* lock down the process doing the following: - create a new mount namespace - bind mount /etc and /run from the host - pivot_root in the new tmpfs. - drop all capabilities. */ int create_sandbox() { int ret, i; struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; struct __user_cap_data_struct data[2] = { { 0 } }; ret = unshare(CLONE_NEWNS); if (ret < 0) { fprintf(stderr, "cannot unshare new mount namespace\n"); return ret; } ret = mount("", "/", "", MS_PRIVATE, NULL); if (ret < 0) { fprintf(stderr, "cannot remount / private\n"); return ret; } ret = mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, "size=1k"); if (ret < 0) { fprintf(stderr, "cannot mount tmpfs on /tmp\n"); return ret; } ret = mkdir("/tmp/etc", 0755); if (ret < 0) { fprintf(stderr, "cannot mkdir /etc\n"); return ret; } ret = mkdir("/tmp/old", 0755); if (ret < 0) { fprintf(stderr, "cannot mkdir /old\n"); return ret; } ret = mkdir("/tmp/run", 0755); if (ret < 0) { fprintf(stderr, "cannot mkdir /run\n"); return ret; } ret = add_mount("/etc", "/tmp/etc"); if (ret < 0) { return ret; } ret = bind_escaped_resolv_conf("/tmp"); if (ret < 0) { return ret; } ret = add_mount("/run", "/tmp/run"); if (ret < 0) { return ret; } ret = chdir("/tmp"); if (ret < 0) { fprintf(stderr, "cannot chdir to /tmp\n"); return ret; } ret = syscall(__NR_pivot_root, ".", "old"); if (ret < 0) { fprintf(stderr, "cannot pivot_root to /tmp\n"); return ret; } ret = chdir("/"); if (ret < 0) { fprintf(stderr, "cannot chdir to /\n"); return ret; } ret = umount2("/old", MNT_DETACH); if (ret < 0) { fprintf(stderr, "cannot umount /old\n"); return ret; } ret = rmdir("/old"); if (ret < 0) { fprintf(stderr, "cannot rmdir /old\n"); return ret; } ret = mount("tmpfs", "/", "tmpfs", MS_REMOUNT | MS_RDONLY, "size=0k"); if (ret < 0) { fprintf(stderr, "cannot remount / as read-only\n"); /* error is negligible (#163) */ } ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (ret < 0) { fprintf(stderr, "prctl(PR_SET_NO_NEW_PRIVS)\n"); return ret; } ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); if (ret < 0) { fprintf(stderr, "prctl(PR_CAP_AMBIENT_CLEAR_ALL)\n"); return ret; } for (i = 0;; i++) { if (i == CAP_NET_BIND_SERVICE) continue; ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); if (ret < 0) { if (errno == EINVAL) break; fprintf(stderr, "prctl(PR_CAPBSET_DROP)\n"); return ret; } } memset(&data, 0, sizeof(data)); data[0].effective |= 1 << CAP_NET_BIND_SERVICE; data[0].permitted |= 1 << CAP_NET_BIND_SERVICE; data[0].inheritable |= 1 << CAP_NET_BIND_SERVICE; ret = capset(&hdr, data); if (ret < 0) { fprintf(stderr, "capset(0)\n"); return ret; } return 0; } slirp4netns-1.2.1/sandbox.h000066400000000000000000000002121447011446400156070ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SLIRP4NETNS_SANDBOX_H # define SLIRP4NETNS_SANDBOX_H int create_sandbox(); #endif slirp4netns-1.2.1/seccomparch.h000066400000000000000000000046611447011446400164540ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef SLIRP4NETNS_SECCOMPARCH_H #define SLIRP4NETNS_SECCOMPARCH_H #include #include /* * seccomp_extra_archs derived from systemd seccomp_local_archs * but does NOT contain native archs: * https://github.com/systemd/systemd/blob/v245/src/shared/seccomp-util.c#L25-L95 * . */ const uint32_t seccomp_extra_archs[] = { #if defined(__x86_64__) && \ defined(__ILP32__) /* X32 ( https://en.wikipedia.org/wiki/X32_ABI ) */ SCMP_ARCH_X86, SCMP_ARCH_X86_64, #elif defined(__x86_64__) && !defined(__ILP32__) /* X86_64 */ SCMP_ARCH_X86, SCMP_ARCH_X32, #elif defined(__i386__) /* X86 */ /* NONE */ #elif defined(__aarch64__) /* AARCH64 */ SCMP_ARCH_ARM, #elif defined(__arm__) /* ARM */ /* NONE */ #elif defined(__mips__) && __BYTE_ORDER == __BIG_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_ABI32 /* MIPS */ SCMP_ARCH_MIPSEL, #elif defined(__mips__) && __BYTE_ORDER == __LITTLE_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_ABI32 /* MIPSEL */ SCMP_ARCH_MIPS, #elif defined(__mips__) && __BYTE_ORDER == __BIG_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_ABI64 /* MIPS64 */ SCMP_ARCH_MIPSEL, SCMP_ARCH_MIPS, SCMP_ARCH_MIPSEL64N32, SCMP_ARCH_MIPS64N32, SCMP_ARCH_MIPSEL64, #elif defined(__mips__) && __BYTE_ORDER == __LITTLE_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_ABI64 /* MIPSEL64 */ SCMP_ARCH_MIPS, SCMP_ARCH_MIPSEL, SCMP_ARCH_MIPS64N32, SCMP_ARCH_MIPSEL64N32, SCMP_ARCH_MIPS64, #elif defined(__mips__) && __BYTE_ORDER == __BIG_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_NABI32 /* MIPS64N32 */ SCMP_ARCH_MIPSEL, SCMP_ARCH_MIPS, SCMP_ARCH_MIPSEL64, SCMP_ARCH_MIPS64, SCMP_ARCH_MIPSEL64N32, #elif defined(__mips__) && __BYTE_ORDER == __LITTLE_ENDIAN && \ _MIPS_SIM == _MIPS_SIM_NABI32 /* MIPSEL64N32 */ SCMP_ARCH_MIPS, SCMP_ARCH_MIPSEL, SCMP_ARCH_MIPS64, SCMP_ARCH_MIPSEL64, SCMP_ARCH_MIPS64N32, #elif defined(__powerpc64__) && __BYTE_ORDER == __BIG_ENDIAN /* PPC64 */ SCMP_ARCH_PPC, SCMP_ARCH_PPC64LE, #elif defined(__powerpc64__) && __BYTE_ORDER == __LITTLE_ENDIAN /* PPC64LE */ SCMP_ARCH_PPC, SCMP_ARCH_PPC64, #elif defined(__powerpc__) /* PPC */ /* NONE */ #elif defined(__s390x__) /* S390X */ SCMP_ARCH_S390, #elif defined(__s390__) /* S390 */ /* NONE */ #endif (uint32_t)-1 }; /* seccomp_extra_archs_items can be 0 */ const int seccomp_extra_archs_items = sizeof(seccomp_extra_archs) / sizeof(seccomp_extra_archs[0]) - 1; #endif slirp4netns-1.2.1/seccompfilter.c000066400000000000000000000063721447011446400170200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include #include #include #include #include #include #include "seccomparch.h" #if defined(SCMP_ACT_KILL_PROCESS) && defined(SECCOMP_GET_ACTION_AVAIL) && \ defined(SECCOMP_RET_KILL_PROCESS) #include #include static uint32_t get_block_action() { const uint32_t action = SECCOMP_RET_KILL_PROCESS; /* Syscall fails if either actions_avail or kill_process is not available */ if (syscall(__NR_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &action) == 0) return SCMP_ACT_KILL_PROCESS; return SCMP_ACT_KILL; } #else static uint32_t get_block_action() { return SCMP_ACT_KILL; } #endif static void add_block_rule(scmp_filter_ctx ctx, const char *name, uint32_t block_action, GString *blocked, GString *skipped_undefined, GString *skipped_failed) { int rc = -1, num = seccomp_syscall_resolve_name(name); if (num == __NR_SCMP_ERROR) { g_string_append_printf(skipped_undefined, " %s", name); return; } if ((rc = seccomp_rule_add(ctx, block_action, num, 0)) != 0) { g_string_append_printf(skipped_failed, " %s(%s)", name, strerror(-rc)); return; } g_string_append_printf(blocked, " %s", name); } int enable_seccomp() { int rc = -1, i; uint32_t block_action = get_block_action(); GString *blocked = g_string_new(NULL); GString *skipped_undefined = g_string_new(NULL); GString *skipped_failed = g_string_new(NULL); /* Allow everything by default and block dangerous syscalls explicitly, * as it is hard to find the correct set of required syscalls */ scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) goto ret; for (i = 0; i < seccomp_extra_archs_items; i++) { uint32_t arch = seccomp_extra_archs[i]; rc = seccomp_arch_add(ctx, arch); if (rc < 0 && rc != -EEXIST && rc != -EDOM) { fprintf(stderr, "seccomp: WARNING: can't add extra arch (i=%d): %s\n", i, strerror(-rc)); } } #define BLOCK(x) \ add_block_rule(ctx, #x, block_action, blocked, skipped_undefined, \ skipped_failed) #include "seccompfilter_rules.h" #undef BLOCK if ((rc = seccomp_load(ctx)) != 0) { fprintf(stderr, "seccomp: seccomp_load(): %s\n", strerror(-rc)); goto ret; } printf("seccomp: The following syscalls are blocked:%s\n", blocked->str); if (skipped_undefined->len > 0) { fprintf(stderr, "seccomp: WARNING: the following syscalls are not defined " "in libseccomp and cannot be " "blocked:%s\n", skipped_undefined->str); } if (skipped_failed->len > 0) { fprintf(stderr, "seccomp: WARNING: the following syscalls cannot be " "blocked due to unexpected errors:%s\n", skipped_failed->str); } ret: seccomp_release(ctx); g_string_free(blocked, TRUE); g_string_free(skipped_undefined, TRUE); g_string_free(skipped_failed, TRUE); return rc; } slirp4netns-1.2.1/seccompfilter.h000066400000000000000000000002261447011446400170150ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SLIRP4NETNS_SECCOMPFILTER_H # define SLIRP4NETNS_SECCOMPFILTER_H int enable_seccomp(); #endif slirp4netns-1.2.1/seccompfilter_rules.h000066400000000000000000000023101447011446400202230ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ /* We do not need ifndef _XXX_H guard: https://github.com/rootless-containers/slirp4netns/pull/238#discussion_r530214521 */ #ifndef BLOCK #error "Included in an unexpected way?" #endif /* NOTE: - Run `sudo systemd-analyze syscall-filter` to show list of syscall groups. - Ideally we should also block open() and openat(), but these calls are required for opening resolv.conf */ /* group: @default */ BLOCK(execve); /* group: @debug */ BLOCK(lookup_dcookie); BLOCK(pidfd_getfd); BLOCK(ptrace); /* group: @ipc */ BLOCK(process_vm_readv); BLOCK(process_vm_writev); /* group: @module*/ BLOCK(delete_module); BLOCK(finit_module); BLOCK(init_module); /* group: @mount */ BLOCK(chroot); BLOCK(fsconfig); BLOCK(fsmount); BLOCK(fsopen); BLOCK(fspick); BLOCK(mount); BLOCK(move_mount); BLOCK(open_tree); BLOCK(pivot_root); BLOCK(umount); BLOCK(umount2); /* group: @privileged */ BLOCK(open_by_handle_at); /* group: @process */ BLOCK(execveat); BLOCK(pidfd_open); BLOCK(pidfd_send_signal); BLOCK(prctl); BLOCK(setns); BLOCK(unshare); /* group: @reboot */ BLOCK(kexec_file_load); BLOCK(kexec_load); BLOCK(reboot); /* group: @system-service */ BLOCK(name_to_handle_at); slirp4netns-1.2.1/slirp4netns.1000066400000000000000000000347411447011446400163650ustar00rootroot00000000000000.nh .TH SLIRP4NETNS 1 "January 2022" "Rootless Containers" "User Commands" .SH NAME .PP slirp4netns \- User\-mode networking for unprivileged network namespaces .SH SYNOPSIS .PP slirp4netns [OPTION]... PID|PATH [TAPNAME] .SH DESCRIPTION .PP slirp4netns provides user\-mode networking ("slirp") for network namespaces. .PP Unlike \fB\fCveth(4)\fR, slirp4netns does not require the root privileges on the host. .PP Default configuration: .RS .IP \(bu 2 MTU: 1500 .IP \(bu 2 CIDR: 10.0.2.0/24 .IP \(bu 2 Gateway/Host: 10.0.2.2 (network address + 2) .IP \(bu 2 DNS: 10.0.2.3 (network address + 3) .IP \(bu 2 DHCP begin: 10.0.2.15 (network address + 15) .IP \(bu 2 DHCP end: 10.0.2.30 (network address + 30) .IP \(bu 2 IPv6 CIDR: fd00::/64 .IP \(bu 2 IPv6 Gateway/Host: fd00::2 .IP \(bu 2 IPv6 DNS: fd00::3 .RE .SH OPTIONS .PP \fB\fC\-c\fR, \fB\fC\-\-configure\fR bring up the TAP interface. IP will be set to 10.0.2.100 (network address + 100) by default. IPv6 will be set to a random address. Starting with v0.4.0, the loopback interface (\fB\fClo\fR) is brought up as well. .PP \fB\fC\-e\fR, \fB\fC\-\-exit\-fd=FD\fR specify the FD for terminating slirp4netns. When the FD is specified, slirp4netns exits when a \fB\fCpoll(2)\fR event happens on the FD. .PP \fB\fC\-r\fR, \fB\fC\-\-ready\-fd=FD\fR specify the FD to write to when the initialization steps are finished. When the FD is specified, slirp4netns writes \fB\fC"1"\fR to the FD and close the FD. Prior to v0.4.0, the FD was written after the network configuration (\fB\fC\-c\fR) but before the API socket configuration (\fB\fC\-a\fR). .PP \fB\fC\-m\fR, \fB\fC\-\-mtu=MTU\fR (since v0.2.0) specify MTU (max=65521). .PP \fB\fC\-6\fR, \fB\fC\-\-enable\-ipv6\fR (since v0.2.0, EXPERIMENTAL) enable IPv6 .PP \fB\fC\-a\fR, \fB\fC\-\-api\-socket\fR (since v0.3.0) API socket path .PP \fB\fC\-\-cidr\fR (since v0.3.0) specify CIDR, e.g. 10.0.2.0/24 .PP \fB\fC\-\-disable\-host\-loopback\fR (since v0.3.0) prohibit connecting to 127.0.0.1:* on the host namespace .PP \fB\fC\-\-netns\-type=TYPE\fR (since v0.4.0) specify network namespace type ([path|pid], default=pid) .PP \fB\fC\-\-userns\-path=PATH\fR (since v0.4.0) specify user namespace path .PP \fB\fC\-\-enable\-sandbox\fR (since v0.4.0) enter the user namespace and create a new mount namespace where only /etc and /run are mounted from the host. .PP Requires \fB\fC/etc/resolv.conf\fR not to be a symlink to a file outside /etc and /run. .PP When running as the root, the process does not enter the user namespace but all the capabilities except \fB\fCCAP\_NET\_BIND\_SERVICE\fR are dropped. .PP \fB\fC\-\-enable\-seccomp\fR (since v0.4.0, EXPERIMENTAL) enable \fB\fCseccomp(2)\fR to limit syscalls. Typically used in conjunction with \fB\fC\-\-enable\-sandbox\fR\&. .PP \fB\fC\-\-outbound\-addr=IPv4\fR (since v1.1.0, EXPERIMENTAL) specify outbound ipv4 address slirp should bind to .PP \fB\fC\-\-outbound\-addr=INTERFACE\fR (since v1.1.0, EXPERIMENTAL) specify outbound interface slirp should bind to (ipv4 traffic only) .PP \fB\fC\-\-outbound\-addr=IPv6\fR (since v1.1.0, EXPERIMENTAL) specify outbound ipv6 address slirp should bind to .PP \fB\fC\-\-outbound\-addr6=INTERFACE\fR (since v1.1.0, EXPERIMENTAL) specify outbound interface slirp should bind to (ipv6 traffic only) .PP \fB\fC\-\-disable\-dns\fR (since v1.1.0) disable built\-in DNS (10.0.2.3 by default) .PP \fB\fC\-\-macaddress\fR (since v1.1.9) specify MAC address of the TAP interface (only valid with \-c) .PP \fB\fC\-\-target\-type=TYPE\fR (since v1.2.0) specify the target type ([netns|bess], default=netns). .PP The \fB\fCbess\fR mode (since v1.2.0, EXPERIMENTAL) is expected to be used with User Mode Linux. The \fB\fCbess\fR mode conflicts with \fB\fC\-\-configure\fR, \fB\fC\-\-netns\-type\fR, and \fB\fC\-\-userns\-path\fR\&. .PP \fB\fC\-h\fR, \fB\fC\-\-help\fR (since v0.2.0) show help and exit .PP \fB\fC\-v\fR, \fB\fC\-\-version\fR (since v0.2.0) show version and exit .SH EXAMPLE .PP \fBTerminal 1\fP: Create user/network/mount namespaces .PP .RS .nf (host)$ unshare \-\-user \-\-map\-root\-user \-\-net \-\-mount (namespace)$ echo $$ > /tmp/pid .fi .RE .PP In this documentation, we use \fB\fC(host)$\fR as the prompt of the host shell, \fB\fC(namespace)$\fR as the prompt of the shell running in the namespaces. .PP If \fB\fCunshare\fR fails, try the following commands (known to be needed on Debian, Arch, and old CentOS 7.X): .PP .RS .nf (host)$ sudo sh \-c 'echo "user.max\_user\_namespaces=28633" >> /etc/sysctl.d/userns.conf' (host)$ if [ \-f /proc/sys/kernel/unprivileged\_userns\_clone ]; then sudo sh \-c 'echo "kernel.unprivileged\_userns\_clone=1" >> /etc/sysctl.d/userns.conf'; fi (host)$ sudo sysctl \-\-system .fi .RE .PP \fBTerminal 2\fP: Start slirp4netns .PP .RS .nf (host)$ slirp4netns \-\-configure \-\-mtu=65520 $(cat /tmp/pid) tap0 starting slirp, MTU=65520 ... .fi .RE .PP \fBTerminal 1\fP: Make sure \fB\fCtap0\fR is configured and connected to the Internet .PP .RS .nf (namespace)$ ip a 1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: tap0: mtu 65520 qdisc fq\_codel state UNKNOWN group default qlen 1000 link/ether c2:28:0c:0e:29:06 brd ff:ff:ff:ff:ff:ff inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0 valid\_lft forever preferred\_lft forever inet6 fe80::c028:cff:fe0e:2906/64 scope link valid\_lft forever preferred\_lft forever (namespace)$ echo "nameserver 10.0.2.3" > /tmp/resolv.conf (namespace)$ mount \-\-bind /tmp/resolv.conf /etc/resolv.conf (namespace)$ curl https://example.com .fi .RE .PP Bind\-mounting \fB\fC/etc/resolv.conf\fR is only needed when \fB\fC/etc/resolv.conf\fR on the host refers to loopback addresses (\fB\fC127.0.0.X\fR, typically \fB\fCdnsmasq(8)\fR or \fB\fCsystemd\-resolved.service(8)\fR) that cannot be accessed from the namespace. .PP If your \fB\fC/etc/resolv.conf\fR on the host is managed by \fB\fCnetworkmanager(8)\fR or \fB\fCsystemd\-resolved.service(8)\fR, you might need to mount a new filesystem on \fB\fC/etc\fR instead, so as to prevent the new \fB\fC/etc/resolv.conf\fR from being unmounted unexpectedly when \fB\fC/etc/resolv.conf\fR on the host is regenerated. .PP .RS .nf (namespace)$ mkdir /tmp/a /tmp/b (namespace)$ mount \-\-rbind /etc /tmp/a (namespace)$ mount \-\-rbind /tmp/b /etc (namespace)$ mkdir /etc/.ro (namespace)$ mount \-\-move /tmp/a /etc/.ro (namespace)$ cd /etc (namespace)$ for f in .ro/*; do ln \-s $f $(basename $f); done (namespace)$ rm resolv.conf (namespace)$ echo "nameserver 10.0.2.3" > resolv.conf (namespace)$ curl https://example.com .fi .RE .PP These steps can be simplified with \fB\fCrootlesskit \-\-copy\-up=/etc\fR if \fB\fCrootlesskit\fR is installed: .PP .RS .nf (host)$ rootlesskit \-\-net=slirp4netns \-\-copy\-up=/etc bash (namespace)$ cat /etc/resolv.conf nameserver 10.0.2.3 .fi .RE .SH ROUTING PING PACKETS .PP To route ping packets, you may need to set up \fB\fCnet.ipv4.ping\_group\_range\fR properly as the root. .PP e.g. .PP .RS .nf (host)$ sudo sh \-c 'echo "net.ipv4.ping\_group\_range=0 2147483647" > /etc/sysctl.d/ping\_group\_range.conf' (host)$ sudo sysctl \-\-system .fi .RE .SH FILTERING CONNECTIONS .PP By default, ports listening on \fB\fCINADDR\_LOOPBACK\fR (\fB\fC127.0.0.1\fR) on the host are accessible from the child namespace via the gateway (default: \fB\fC10.0.2.2\fR). \fB\fC\-\-disable\-host\-loopback\fR can be used to prohibit connecting to \fB\fCINADDR\_LOOPBACK\fR on the host. .PP However, a host loopback address might be still accessible via the built\-in DNS (default: \fB\fC10.0.2.3\fR) if \fB\fC/etc/resolv.conf\fR on the host refers to a loopback address. You may want to set up iptables for limiting access to the built\-in DNS in such a case. .PP .RS .nf (host)$ nsenter \-t $(cat /tmp/pid) \-U \-\-preserve\-credentials \-n (namespace)$ iptables \-A OUTPUT \-d 10.0.2.3 \-p udp \-\-dport 53 \-j ACCEPT (namespace)$ iptables \-A OUTPUT \-d 10.0.2.3 \-j DROP .fi .RE .SH API SOCKET .PP slirp4netns can provide QMP\-like API server over an UNIX socket file: .PP .RS .nf (host)$ slirp4netns \-\-api\-socket /tmp/slirp4netns.sock ... .fi .RE .PP \fB\fCadd\_hostfwd\fR: Expose a port (IPv4 only) .PP .RS .nf (namespace)$ json='{"execute": "add\_hostfwd", "arguments": {"proto": "tcp", "host\_addr": "0.0.0.0", "host\_port": 8080, "guest\_addr": "10.0.2.100", "guest\_port": 80}}' (namespace)$ echo \-n $json | nc \-U /tmp/slirp4netns.sock {"return": {"id": 42}} .fi .RE .PP If \fB\fChost\_addr\fR is not specified, then it defaults to "0.0.0.0". .PP If \fB\fCguest\_addr\fR is not specified, then it will be set to the default address that corresponds to \fB\fC\-\-configure\fR\&. .PP \fB\fClist\_hostfwd\fR: List exposed ports .PP .RS .nf (namespace)$ json='{"execute": "list\_hostfwd"}' (namespace)$ echo \-n $json | nc \-U /tmp/slirp4netns.sock {"return": {"entries": [{"id": 42, "proto": "tcp", "host\_addr": "0.0.0.0", "host\_port": 8080, "guest\_addr": "10.0.2.100", "guest\_port": 80}]}} .fi .RE .PP \fB\fCremove\_hostfwd\fR: Remove an exposed port .PP .RS .nf (namespace)$ json='{"execute": "remove\_hostfwd", "arguments": {"id": 42}}' (namespace)$ echo \-n $json | nc \-U /tmp/slirp4netns.sock {"return": {}} .fi .RE .PP Remarks: .RS .IP \(bu 2 Client needs to \fB\fCshutdown(2)\fR the socket with \fB\fCSHUT\_WR\fR after sending every request. i.e. No support for keep\-alive and timeout. .IP \(bu 2 slirp4netns "stops the world" during processing API requests. .IP \(bu 2 A request must be less than 4096 bytes. .IP \(bu 2 JSON responses may contain \fB\fCerror\fR instead of \fB\fCreturn\fR\&. .RE .SH DEFINED NAMESPACE PATHS .PP A user can define a network namespace path as opposed to the default process ID: .PP .RS .nf (host)$ slirp4netns \-\-netns\-type=path ... /path/to/netns tap0 .fi .RE .PP Currently, the \fB\fCnetns\-type=TYPE\fR argument supports \fB\fCpath\fR or \fB\fCpid\fR args with the default being \fB\fCpid\fR\&. .PP Additionally, a \fB\fC\-\-userns\-path=PATH\fR argument can be included to override any user namespace path defaults .PP .RS .nf (host)$ slirp4netns \-\-netns\-type=path \-\-userns\-path=/path/to/userns /path/to/netns tap0 .fi .RE .SH OUTBOUND ADDRESSES .PP A user can defined preferred outbound ipv4 and ipv6 address in multi IP scenarios. .PP .RS .nf (host)$ slirp4netns \-\-outbound\-addr=10.2.2.10 \-\-outbound\-addr6=fe80::10 ... .fi .RE .PP Optionally you can use interface names instead of ip addresses. .PP .RS .nf (host)$ slirp4netns \-\-outbound\-addr=eth0 \-\-outbound\-addr6=eth0 ... .fi .RE .SH INTER\-NAMESPACE COMMUNICATION .PP The easiest way to allow inter\-namespace communication is to nest network namespaces inside the slirp4netns's network namespace. .PP .RS .nf (host)$ nsenter \-t $(cat /tmp/pid) \-U \-\-preserve\-credentials \-n \-m (namespace)$ mount \-t tmpfs none /run (namespace)$ ip netns add foo (namespace)$ ip netns add bar (namespace)$ ip link add veth\-foo type veth peer name veth\-bar (namespace)$ ip link set veth\-foo netns foo (namespace)$ ip link set veth\-bar netns bar (namespace)$ ip netns exec foo ip link set veth\-foo name eth0 (namespace)$ ip netns exec bar ip link set veth\-bar name eth0 (namespace)$ ip netns exec foo ip link set lo up (namespace)$ ip netns exec bar ip link set lo up (namespace)$ ip netns exec foo ip link set eth0 up (namespace)$ ip netns exec bar ip link set eth0 up (namespace)$ ip netns exec foo ip addr add 192.168.42.100/24 dev eth0 (namespace)$ ip netns exec bar ip addr add 192.168.42.101/24 dev eth0 (namespace)$ ip netns exec bar ping 192.168.42.100 .fi .RE .PP However, this method does not work when you want to allow communication across multiple slirp4netns instances. To allow communication across multiple slirp4netns instances, you need to combine another network stack such as \fB\fCvde\_plug(1)\fR with slirp4netns. .PP .RS .nf (host)$ vde\_plug \-\-daemon switch:///tmp/switch null:// (host)$ nsenter \-t $(cat /tmp/pid\-instance0) \-U \-\-preserve\-credentials \-n (namespace\-instance0)$ vde\_plug \-\-daemon vde:///tmp/switch tap://vde (namespace\-instance0)$ ip link set vde up (namespace\-instance0)$ ip addr add 192.168.42.100/24 dev vde (namespace\-instance0)$ exit (host)$ nsenter \-t $(cat /tmp/pid\-instance1) \-U \-\-preserve\-credentials \-n (namespace\-instance1)$ vde\_plug \-\-daemon vde:///tmp/switch tap://vde (namespace\-instance1)$ ip link set vde up (namespace\-instance1)$ ip addr add 192.168.42.101/24 dev vde (namespace\-instance1)$ ping 192.168.42.100 .fi .RE .SH INTER\-HOST COMMUNICATION .PP VXLAN is known to work. See Usernetes project for the example of multi\-node rootless Kubernetes cluster with VXLAN: \fB\fChttps://github.com/rootless\-containers/usernetes\fR .SH BESS MODE (FOR USER MODE LINUX) .PP slirp4netns (since v1.2.0) can be also used as a BESS\-compatible server to provide network connectivity to User Mode Linux. .PP \fBTerminal 1\fP: Start slirp4netns .PP .RS .nf (host)$ slirp4netns \-\-target\-type=bess /tmp/bess.sock .fi .RE .PP \fBTerminal 2\fP: Start User Mode Linux .PP .RS .nf (host)$ linux.uml vec0:transport=bess,dst=/tmp/bess.sock,depth=128,gro=1 root=/dev/root rootfstype=hostfs init=/bin/bash mem=2G (UML)$ ip addr add 10.0.2.100/24 dev vec0 (UML)$ ip link set vec0 up (UML)$ ip route add default via 10.0.2.2 .fi .RE .PP Currently, only a single instance of User Mode Linux can be connected to the slirp4netns BESS server. .PP See also User Mode Linux documentation: \fB\fChttps://www.kernel.org/doc/html/latest/virt/uml/user\_mode\_linux\_howto\_v2.html#bess\-socket\-transport\fR .SH BUGS .PP Kernel 4.20 bumped up the default value of \fB\fC/proc/sys/net/ipv4/tcp\_rmem\fR from 87380 to 131072. This is known to slow down slirp4netns port forwarding: \fB\fChttps://github.com/rootless\-containers/slirp4netns/issues/128\fR\&. .PP As a workaround, you can adjust the value of \fB\fC/proc/sys/net/ipv4/tcp\_rmem\fR inside the namespace. No real root privilege is needed to modify the file since kernel 4.15. .PP .RS .nf (host)$ nsenter \-t $(cat /tmp/pid) \-U \-\-preserve\-credentials \-n \-m (namespace)$ c=$(cat /proc/sys/net/ipv4/tcp\_rmem); echo $c | sed \-e s/131072/87380/g > /proc/sys/net/ipv4/tcp\_rmem .fi .RE .SH SEE ALSO .PP \fB\fCnetwork\_namespaces(7)\fR, \fB\fCuser\_namespaces(7)\fR, \fB\fCveth(4)\fR .SH AVAILABILITY .PP The slirp4netns command is available from \fB\fChttps://github.com/rootless\-containers/slirp4netns\fR under GNU GENERAL PUBLIC LICENSE Version 2 (or later). slirp4netns-1.2.1/slirp4netns.1.md000066400000000000000000000317721447011446400167650ustar00rootroot00000000000000SLIRP4NETNS 1 "January 2022" "Rootless Containers" "User Commands" ================================================== # NAME slirp4netns - User-mode networking for unprivileged network namespaces # SYNOPSIS slirp4netns [OPTION]... PID|PATH [TAPNAME] # DESCRIPTION slirp4netns provides user-mode networking ("slirp") for network namespaces. Unlike `veth(4)`, slirp4netns does not require the root privileges on the host. Default configuration: * MTU: 1500 * CIDR: 10.0.2.0/24 * Gateway/Host: 10.0.2.2 (network address + 2) * DNS: 10.0.2.3 (network address + 3) * DHCP begin: 10.0.2.15 (network address + 15) * DHCP end: 10.0.2.30 (network address + 30) * IPv6 CIDR: fd00::/64 * IPv6 Gateway/Host: fd00::2 * IPv6 DNS: fd00::3 # OPTIONS `-c`, `--configure` bring up the TAP interface. IP will be set to 10.0.2.100 (network address + 100) by default. IPv6 will be set to a random address. Starting with v0.4.0, the loopback interface (`lo`) is brought up as well. `-e`, `--exit-fd=FD` specify the FD for terminating slirp4netns. When the FD is specified, slirp4netns exits when a `poll(2)` event happens on the FD. `-r`, `--ready-fd=FD` specify the FD to write to when the initialization steps are finished. When the FD is specified, slirp4netns writes `"1"` to the FD and close the FD. Prior to v0.4.0, the FD was written after the network configuration (`-c`) but before the API socket configuration (`-a`). `-m`, `--mtu=MTU` (since v0.2.0) specify MTU (max=65521). `-6`, `--enable-ipv6` (since v0.2.0, EXPERIMENTAL) enable IPv6 `-a`, `--api-socket` (since v0.3.0) API socket path `--cidr` (since v0.3.0) specify CIDR, e.g. 10.0.2.0/24 `--disable-host-loopback` (since v0.3.0) prohibit connecting to 127.0.0.1:\* on the host namespace `--netns-type=TYPE` (since v0.4.0) specify network namespace type ([path|pid], default=pid) `--userns-path=PATH` (since v0.4.0) specify user namespace path `--enable-sandbox` (since v0.4.0) enter the user namespace and create a new mount namespace where only /etc and /run are mounted from the host. Requires `/etc/resolv.conf` not to be a symlink to a file outside /etc and /run. When running as the root, the process does not enter the user namespace but all the capabilities except `CAP_NET_BIND_SERVICE` are dropped. `--enable-seccomp` (since v0.4.0, EXPERIMENTAL) enable `seccomp(2)` to limit syscalls. Typically used in conjunction with `--enable-sandbox`. `--outbound-addr=IPv4` (since v1.1.0, EXPERIMENTAL) specify outbound ipv4 address slirp should bind to `--outbound-addr=INTERFACE` (since v1.1.0, EXPERIMENTAL) specify outbound interface slirp should bind to (ipv4 traffic only) `--outbound-addr=IPv6` (since v1.1.0, EXPERIMENTAL) specify outbound ipv6 address slirp should bind to `--outbound-addr6=INTERFACE` (since v1.1.0, EXPERIMENTAL) specify outbound interface slirp should bind to (ipv6 traffic only) `--disable-dns` (since v1.1.0) disable built-in DNS (10.0.2.3 by default) `--macaddress` (since v1.1.9) specify MAC address of the TAP interface (only valid with -c) `--target-type=TYPE` (since v1.2.0) specify the target type ([netns|bess], default=netns). The `bess` mode (since v1.2.0, EXPERIMENTAL) is expected to be used with User Mode Linux. The `bess` mode conflicts with `--configure`, `--netns-type`, and `--userns-path`. `-h`, `--help` (since v0.2.0) show help and exit `-v`, `--version` (since v0.2.0) show version and exit # EXAMPLE **Terminal 1**: Create user/network/mount namespaces ```console (host)$ unshare --user --map-root-user --net --mount (namespace)$ echo $$ > /tmp/pid ``` In this documentation, we use `(host)$` as the prompt of the host shell, `(namespace)$` as the prompt of the shell running in the namespaces. If `unshare` fails, try the following commands (known to be needed on Debian, Arch, and old CentOS 7.X): ```console (host)$ sudo sh -c 'echo "user.max_user_namespaces=28633" >> /etc/sysctl.d/userns.conf' (host)$ if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then sudo sh -c 'echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.d/userns.conf'; fi (host)$ sudo sysctl --system ``` **Terminal 2**: Start slirp4netns ```console (host)$ slirp4netns --configure --mtu=65520 $(cat /tmp/pid) tap0 starting slirp, MTU=65520 ... ``` **Terminal 1**: Make sure `tap0` is configured and connected to the Internet ```console (namespace)$ ip a 1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: tap0: mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000 link/ether c2:28:0c:0e:29:06 brd ff:ff:ff:ff:ff:ff inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0 valid_lft forever preferred_lft forever inet6 fe80::c028:cff:fe0e:2906/64 scope link valid_lft forever preferred_lft forever (namespace)$ echo "nameserver 10.0.2.3" > /tmp/resolv.conf (namespace)$ mount --bind /tmp/resolv.conf /etc/resolv.conf (namespace)$ curl https://example.com ``` Bind-mounting `/etc/resolv.conf` is only needed when `/etc/resolv.conf` on the host refers to loopback addresses (`127.0.0.X`, typically `dnsmasq(8)` or `systemd-resolved.service(8)`) that cannot be accessed from the namespace. If your `/etc/resolv.conf` on the host is managed by `networkmanager(8)` or `systemd-resolved.service(8)`, you might need to mount a new filesystem on `/etc` instead, so as to prevent the new `/etc/resolv.conf` from being unmounted unexpectedly when `/etc/resolv.conf` on the host is regenerated. ```console (namespace)$ mkdir /tmp/a /tmp/b (namespace)$ mount --rbind /etc /tmp/a (namespace)$ mount --rbind /tmp/b /etc (namespace)$ mkdir /etc/.ro (namespace)$ mount --move /tmp/a /etc/.ro (namespace)$ cd /etc (namespace)$ for f in .ro/*; do ln -s $f $(basename $f); done (namespace)$ rm resolv.conf (namespace)$ echo "nameserver 10.0.2.3" > resolv.conf (namespace)$ curl https://example.com ``` These steps can be simplified with `rootlesskit --copy-up=/etc` if `rootlesskit` is installed: ```console (host)$ rootlesskit --net=slirp4netns --copy-up=/etc bash (namespace)$ cat /etc/resolv.conf nameserver 10.0.2.3 ``` # ROUTING PING PACKETS To route ping packets, you may need to set up `net.ipv4.ping_group_range` properly as the root. e.g. ```console (host)$ sudo sh -c 'echo "net.ipv4.ping_group_range=0 2147483647" > /etc/sysctl.d/ping_group_range.conf' (host)$ sudo sysctl --system ``` # FILTERING CONNECTIONS By default, ports listening on `INADDR_LOOPBACK` (`127.0.0.1`) on the host are accessible from the child namespace via the gateway (default: `10.0.2.2`). `--disable-host-loopback` can be used to prohibit connecting to `INADDR_LOOPBACK` on the host. However, a host loopback address might be still accessible via the built-in DNS (default: `10.0.2.3`) if `/etc/resolv.conf` on the host refers to a loopback address. You may want to set up iptables for limiting access to the built-in DNS in such a case. ```console (host)$ nsenter -t $(cat /tmp/pid) -U --preserve-credentials -n (namespace)$ iptables -A OUTPUT -d 10.0.2.3 -p udp --dport 53 -j ACCEPT (namespace)$ iptables -A OUTPUT -d 10.0.2.3 -j DROP ``` # API SOCKET slirp4netns can provide QMP-like API server over an UNIX socket file: ```console (host)$ slirp4netns --api-socket /tmp/slirp4netns.sock ... ``` `add_hostfwd`: Expose a port (IPv4 only) ```console (namespace)$ json='{"execute": "add_hostfwd", "arguments": {"proto": "tcp", "host_addr": "0.0.0.0", "host_port": 8080, "guest_addr": "10.0.2.100", "guest_port": 80}}' (namespace)$ echo -n $json | nc -U /tmp/slirp4netns.sock {"return": {"id": 42}} ``` If `host_addr` is not specified, then it defaults to "0.0.0.0". If `guest_addr` is not specified, then it will be set to the default address that corresponds to `--configure`. `list_hostfwd`: List exposed ports ```console (namespace)$ json='{"execute": "list_hostfwd"}' (namespace)$ echo -n $json | nc -U /tmp/slirp4netns.sock {"return": {"entries": [{"id": 42, "proto": "tcp", "host_addr": "0.0.0.0", "host_port": 8080, "guest_addr": "10.0.2.100", "guest_port": 80}]}} ``` `remove_hostfwd`: Remove an exposed port ```console (namespace)$ json='{"execute": "remove_hostfwd", "arguments": {"id": 42}}' (namespace)$ echo -n $json | nc -U /tmp/slirp4netns.sock {"return": {}} ``` Remarks: * Client needs to `shutdown(2)` the socket with `SHUT_WR` after sending every request. i.e. No support for keep-alive and timeout. * slirp4netns "stops the world" during processing API requests. * A request must be less than 4096 bytes. * JSON responses may contain `error` instead of `return`. # DEFINED NAMESPACE PATHS A user can define a network namespace path as opposed to the default process ID: ```console (host)$ slirp4netns --netns-type=path ... /path/to/netns tap0 ``` Currently, the `netns-type=TYPE` argument supports `path` or `pid` args with the default being `pid`. Additionally, a `--userns-path=PATH` argument can be included to override any user namespace path defaults ```console (host)$ slirp4netns --netns-type=path --userns-path=/path/to/userns /path/to/netns tap0 ``` # OUTBOUND ADDRESSES A user can defined preferred outbound ipv4 and ipv6 address in multi IP scenarios. ```console (host)$ slirp4netns --outbound-addr=10.2.2.10 --outbound-addr6=fe80::10 ... ``` Optionally you can use interface names instead of ip addresses. ```console (host)$ slirp4netns --outbound-addr=eth0 --outbound-addr6=eth0 ... ``` # INTER-NAMESPACE COMMUNICATION The easiest way to allow inter-namespace communication is to nest network namespaces inside the slirp4netns's network namespace. ```console (host)$ nsenter -t $(cat /tmp/pid) -U --preserve-credentials -n -m (namespace)$ mount -t tmpfs none /run (namespace)$ ip netns add foo (namespace)$ ip netns add bar (namespace)$ ip link add veth-foo type veth peer name veth-bar (namespace)$ ip link set veth-foo netns foo (namespace)$ ip link set veth-bar netns bar (namespace)$ ip netns exec foo ip link set veth-foo name eth0 (namespace)$ ip netns exec bar ip link set veth-bar name eth0 (namespace)$ ip netns exec foo ip link set lo up (namespace)$ ip netns exec bar ip link set lo up (namespace)$ ip netns exec foo ip link set eth0 up (namespace)$ ip netns exec bar ip link set eth0 up (namespace)$ ip netns exec foo ip addr add 192.168.42.100/24 dev eth0 (namespace)$ ip netns exec bar ip addr add 192.168.42.101/24 dev eth0 (namespace)$ ip netns exec bar ping 192.168.42.100 ``` However, this method does not work when you want to allow communication across multiple slirp4netns instances. To allow communication across multiple slirp4netns instances, you need to combine another network stack such as `vde_plug(1)` with slirp4netns. ```console (host)$ vde_plug --daemon switch:///tmp/switch null:// (host)$ nsenter -t $(cat /tmp/pid-instance0) -U --preserve-credentials -n (namespace-instance0)$ vde_plug --daemon vde:///tmp/switch tap://vde (namespace-instance0)$ ip link set vde up (namespace-instance0)$ ip addr add 192.168.42.100/24 dev vde (namespace-instance0)$ exit (host)$ nsenter -t $(cat /tmp/pid-instance1) -U --preserve-credentials -n (namespace-instance1)$ vde_plug --daemon vde:///tmp/switch tap://vde (namespace-instance1)$ ip link set vde up (namespace-instance1)$ ip addr add 192.168.42.101/24 dev vde (namespace-instance1)$ ping 192.168.42.100 ``` # INTER-HOST COMMUNICATION VXLAN is known to work. See Usernetes project for the example of multi-node rootless Kubernetes cluster with VXLAN: `https://github.com/rootless-containers/usernetes` # BESS MODE (FOR USER MODE LINUX) slirp4netns (since v1.2.0) can be also used as a BESS-compatible server to provide network connectivity to User Mode Linux. **Terminal 1**: Start slirp4netns ```console (host)$ slirp4netns --target-type=bess /tmp/bess.sock ``` **Terminal 2**: Start User Mode Linux ```console (host)$ linux.uml vec0:transport=bess,dst=/tmp/bess.sock,depth=128,gro=1 root=/dev/root rootfstype=hostfs init=/bin/bash mem=2G (UML)$ ip addr add 10.0.2.100/24 dev vec0 (UML)$ ip link set vec0 up (UML)$ ip route add default via 10.0.2.2 ``` Currently, only a single instance of User Mode Linux can be connected to the slirp4netns BESS server. See also User Mode Linux documentation: `https://www.kernel.org/doc/html/latest/virt/uml/user_mode_linux_howto_v2.html#bess-socket-transport` # BUGS Kernel 4.20 bumped up the default value of `/proc/sys/net/ipv4/tcp_rmem` from 87380 to 131072. This is known to slow down slirp4netns port forwarding: `https://github.com/rootless-containers/slirp4netns/issues/128`. As a workaround, you can adjust the value of `/proc/sys/net/ipv4/tcp_rmem` inside the namespace. No real root privilege is needed to modify the file since kernel 4.15. ```console (host)$ nsenter -t $(cat /tmp/pid) -U --preserve-credentials -n -m (namespace)$ c=$(cat /proc/sys/net/ipv4/tcp_rmem); echo $c | sed -e s/131072/87380/g > /proc/sys/net/ipv4/tcp_rmem ``` # SEE ALSO `network_namespaces(7)`, `user_namespaces(7)`, `veth(4)` # AVAILABILITY The slirp4netns command is available from `https://github.com/rootless-containers/slirp4netns` under GNU GENERAL PUBLIC LICENSE Version 2 (or later). slirp4netns-1.2.1/slirp4netns.c000066400000000000000000000273711447011446400164500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "api.h" #include "sandbox.h" #include "seccompfilter.h" #include "slirp4netns.h" /* opaque for SlirpCb */ struct libslirp_data { int tapfd; GSList *timers; }; /* implements SlirpCb.send_packet */ static ssize_t libslirp_send_packet(const void *pkt, size_t pkt_len, void *opaque) { struct libslirp_data *data = (struct libslirp_data *)opaque; return write(data->tapfd, pkt, pkt_len); } /* implements SlirpCb.guest_error */ static void libslirp_guest_error(const char *msg, void *opaque) { fprintf(stderr, "libslirp: %s\n", msg); } /* implements SlirpCb.clock_get_ns */ static int64_t libslirp_clock_get_ns(void *opaque) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000000000LL + ts.tv_nsec; } /* timer for SlirpCb */ struct timer { SlirpTimerCb cb; void *cb_opaque; int64_t expire_timer_msec; }; /* implements SlirpCb.timer_new */ static void *libslirp_timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) { struct libslirp_data *data = (struct libslirp_data *)opaque; struct timer *t = g_malloc0(sizeof(*t)); t->cb = cb; t->cb_opaque = cb_opaque; t->expire_timer_msec = -1; data->timers = g_slist_append(data->timers, t); return t; } /* implements SlirpCb.timer_free */ static void libslirp_timer_free(void *timer, void *opaque) { struct libslirp_data *data = (struct libslirp_data *)opaque; data->timers = g_slist_remove(data->timers, timer); g_free(timer); } /* implements SlirpCb.timer_mod */ static void libslirp_timer_mod(void *timer, int64_t expire_timer_msec, void *opaque) { struct timer *t = (struct timer *)timer; t->expire_timer_msec = expire_timer_msec; } /* implements SlirpCb.register_poll_fd */ static void libslirp_register_poll_fd(int fd, void *opaque) { /* * NOP * * This is NOP on QEMU@4c76137484878f42a2ce1ae1b888b6a7f66b4053 on Linux as * well, see: * * qemu/net/slirp.c: net_slirp_register_poll_fd (calls * qemu_fd_register) * * qemu/stubs/fd-register.c: qemu_fd_register (NOP on Linux) * * See also: * * qemu/util/main-loop.c: qemu_fd_register (Win32 only) */ } /* implements SlirpCb.unregister_poll_fd */ static void libslirp_unregister_poll_fd(int fd, void *opaque) { /* * NOP * * This is NOP on QEMU@4c76137484878f42a2ce1ae1b888b6a7f66b4053 as well, * see: * * qemu/net/slirp.c: net_slirp_unregister_poll_fd (NOP) */ } /* implements SlirpCb.notify */ static void libslirp_notify(void *opaque) { /* * NOP * * This can be NOP on QEMU@4c76137484878f42a2ce1ae1b888b6a7f66b4053 as well, * see: * * qemu/net/slirp.c: net_slirp_notify (calls qemu_notify_event) * * qemu/stubs/notify-event.c: qemu_notify_event (NOP) * * See also: * * qemu/util/main-loop.c: qemu_notify_event (NOP if * !qemu_aio_context) */ } static int libslirp_poll_to_gio(int events) { int ret = 0; if (events & SLIRP_POLL_IN) { ret |= G_IO_IN; } if (events & SLIRP_POLL_OUT) { ret |= G_IO_OUT; } if (events & SLIRP_POLL_PRI) { ret |= G_IO_PRI; } if (events & SLIRP_POLL_ERR) { ret |= G_IO_ERR; } if (events & SLIRP_POLL_HUP) { ret |= G_IO_HUP; } return ret; } /* * implements SlirpAddPollCb used in slirp_pollfds_fill. * originally from qemu/net/slirp.c:net_slirp_add_poll * (4c76137484878f42a2ce1ae1b888b6a7f66b4053) */ static int libslirp_add_poll(int fd, int events, void *opaque) { GArray *pollfds = opaque; GPollFD pfd = { .fd = fd, .events = libslirp_poll_to_gio(events), }; int idx = pollfds->len; g_array_append_val(pollfds, pfd); return idx; } static int libslirp_gio_to_poll(int events) { int ret = 0; if (events & G_IO_IN) { ret |= SLIRP_POLL_IN; } if (events & G_IO_OUT) { ret |= SLIRP_POLL_OUT; } if (events & G_IO_PRI) { ret |= SLIRP_POLL_PRI; } if (events & G_IO_ERR) { ret |= SLIRP_POLL_ERR; } if (events & G_IO_HUP) { ret |= SLIRP_POLL_HUP; } return ret; } /* * implements SlirpGetREventsCB used in slirp_pollfds_poll * originally from qemu/net/slirp.c:net_slirp_get_revents * (4c76137484878f42a2ce1ae1b888b6a7f66b4053) */ static int libslirp_get_revents(int idx, void *opaque) { GArray *pollfds = opaque; return libslirp_gio_to_poll(g_array_index(pollfds, GPollFD, idx).revents); } /* * updates timeout_msec for data->timers * originally from * https://github.com/rd235/libslirp/blob/d2b7032e29f3ba98e17414b32c9effffc90f2bb0/src/qemu2libslirp.c#L66 */ static void update_ra_timeout(uint32_t *timeout_msec, struct libslirp_data *data) { int64_t now_msec = libslirp_clock_get_ns(data) / 1000000; GSList *f; for (f = data->timers; f != NULL; f = f->next) { struct timer *t = f->data; if (t->expire_timer_msec != -1) { int64_t diff = t->expire_timer_msec - now_msec; if (diff < 0) diff = 0; if (diff < *timeout_msec) *timeout_msec = diff; } } } /* * calls SlirpTimerCb if timed out * originally from * https://github.com/rd235/libslirp/blob/d2b7032e29f3ba98e17414b32c9effffc90f2bb0/src/qemu2libslirp.c#L78 */ static void check_ra_timeout(struct libslirp_data *data) { int64_t now_msec = libslirp_clock_get_ns(data) / 1000000; GSList *f; for (f = data->timers; f != NULL; f = f->next) { struct timer *t = f->data; if (t->expire_timer_msec != -1) { int64_t diff = t->expire_timer_msec - now_msec; if (diff <= 0) { t->expire_timer_msec = -1; t->cb(t->cb_opaque); } } } } static const SlirpCb libslirp_cb = { .send_packet = libslirp_send_packet, .guest_error = libslirp_guest_error, .clock_get_ns = libslirp_clock_get_ns, .timer_new = libslirp_timer_new, .timer_free = libslirp_timer_free, .timer_mod = libslirp_timer_mod, .register_poll_fd = libslirp_register_poll_fd, .unregister_poll_fd = libslirp_unregister_poll_fd, .notify = libslirp_notify, }; Slirp *create_slirp(void *opaque, struct slirp4netns_config *s4nn) { Slirp *slirp = NULL; SlirpConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.version = 1; cfg.restricted = 0; cfg.in_enabled = 1; cfg.vnetwork = s4nn->vnetwork; cfg.vnetmask = s4nn->vnetmask; cfg.vhost = s4nn->vhost; cfg.in6_enabled = (int)(s4nn->enable_ipv6); inet_pton(AF_INET6, "fd00::", &cfg.vprefix_addr6); cfg.vprefix_len = 64; inet_pton(AF_INET6, "fd00::2", &cfg.vhost6); cfg.vhostname = NULL; cfg.tftp_server_name = NULL; cfg.tftp_path = NULL; cfg.bootfile = NULL; cfg.vdhcp_start = s4nn->vdhcp_start; cfg.vnameserver = s4nn->vnameserver; inet_pton(AF_INET6, "fd00::3", &cfg.vnameserver6); cfg.vdnssearch = NULL; cfg.vdomainname = NULL; cfg.if_mtu = s4nn->mtu; cfg.if_mru = s4nn->mtu; cfg.disable_host_loopback = s4nn->disable_host_loopback; #if SLIRP_CONFIG_VERSION_MAX >= 2 cfg.outbound_addr = NULL; cfg.outbound_addr6 = NULL; if (s4nn->enable_outbound_addr) { cfg.version = 2; cfg.outbound_addr = &s4nn->outbound_addr; } if (s4nn->enable_outbound_addr6) { cfg.version = 2; cfg.outbound_addr6 = &s4nn->outbound_addr6; } #endif #if SLIRP_CONFIG_VERSION_MAX >= 3 if (s4nn->disable_dns) { cfg.version = 3; cfg.disable_dns = true; } #endif slirp = slirp_new(&cfg, &libslirp_cb, opaque); if (slirp == NULL) { fprintf(stderr, "slirp_new failed\n"); } return slirp; } #define ETH_BUF_SIZE (65536) int do_slirp(int tapfd, int readyfd, int exitfd, const char *api_socket, struct slirp4netns_config *cfg) { int ret = -1; Slirp *slirp = NULL; uint8_t *buf = NULL; struct libslirp_data opaque = { .tapfd = tapfd, .timers = NULL }; int apifd = -1; struct api_ctx *apictx = NULL; GArray *pollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD)); int pollfds_exitfd_idx = -1; int pollfds_apifd_idx = -1; size_t n_fds = 1; GPollFD tap_pollfd = { .fd = tapfd, .events = G_IO_IN | G_IO_HUP, .revents = 0 }; GPollFD exit_pollfd = { .fd = exitfd, .events = G_IO_HUP, .revents = 0 }; GPollFD api_pollfd = { .fd = -1, .events = G_IO_IN | G_IO_HUP, .revents = 0 }; slirp = create_slirp((void *)&opaque, cfg); if (slirp == NULL) { fprintf(stderr, "create_slirp failed\n"); goto err; } buf = malloc(ETH_BUF_SIZE); if (buf == NULL) { goto err; } g_array_append_val(pollfds, tap_pollfd); if (exitfd >= 0) { n_fds++; g_array_append_val(pollfds, exit_pollfd); pollfds_exitfd_idx = n_fds - 1; } if (api_socket != NULL) { if ((apifd = api_bindlisten(api_socket)) < 0) { goto err; } if ((apictx = api_ctx_alloc(cfg)) == NULL) { fprintf(stderr, "api_ctx_alloc failed\n"); goto err; } api_pollfd.fd = apifd; n_fds++; g_array_append_val(pollfds, api_pollfd); pollfds_apifd_idx = n_fds - 1; } signal(SIGPIPE, SIG_IGN); if (cfg->enable_sandbox && create_sandbox() < 0) { fprintf(stderr, "create_sandbox failed\n"); goto err; } if (cfg->enable_seccomp && enable_seccomp() < 0) { fprintf(stderr, "enable_seccomp failed\n"); goto err; } if (readyfd >= 0) { int rc = -1; do rc = write(readyfd, "1", 1); while (rc < 0 && errno == EINTR); close(readyfd); } while (1) { int pollout; GPollFD *pollfds_data; uint32_t timeout = -1; /* msec */ g_array_set_size(pollfds, n_fds); slirp_pollfds_fill(slirp, &timeout, libslirp_add_poll, pollfds); update_ra_timeout(&timeout, &opaque); pollfds_data = (GPollFD *)pollfds->data; do { pollout = g_poll(pollfds_data, pollfds->len, timeout); } while (pollout < 0 && errno == EINTR); if (pollout < 0) { goto err; } if (pollfds_data[0].revents) { ssize_t rc = read(tapfd, buf, ETH_BUF_SIZE); if (rc < 0) { perror("do_slirp: read"); goto after_slirp_input; } slirp_input(slirp, buf, (int)rc); after_slirp_input: pollout = -1; } /* The exitfd is closed. */ if (pollfds_exitfd_idx >= 0 && pollfds_data[pollfds_exitfd_idx].revents) { fprintf(stderr, "exitfd event\n"); goto success; } if (pollfds_apifd_idx >= 0 && pollfds_data[pollfds_apifd_idx].revents) { int rc; fprintf(stderr, "apifd event\n"); if ((rc = api_handler(slirp, apifd, apictx)) < 0) { fprintf(stderr, "api_handler: rc=%d\n", rc); } } slirp_pollfds_poll(slirp, (pollout <= 0), libslirp_get_revents, pollfds); check_ra_timeout(&opaque); } success: ret = 0; err: fprintf(stderr, "do_slirp is exiting\n"); if (buf != NULL) { free(buf); } if (apictx != NULL) { api_ctx_free(apictx); unlink(api_socket); } g_array_free(pollfds, TRUE); return ret; } slirp4netns-1.2.1/slirp4netns.h000066400000000000000000000020551447011446400164450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef SLIRP4NETNS_H #define SLIRP4NETNS_H #include struct slirp4netns_config { unsigned int mtu; struct in_addr vnetwork; // 10.0.2.0 struct in_addr vnetmask; // 255.255.255.0 struct in_addr vhost; // 10.0.2.2 struct in_addr vdhcp_start; // 10.0.2.15 struct in_addr vnameserver; // 10.0.2.3 struct in_addr recommended_vguest; // 10.0.2.100 (slirp itself is unaware of vguest) bool enable_ipv6; bool disable_host_loopback; bool enable_sandbox; bool enable_seccomp; #if SLIRP_CONFIG_VERSION_MAX >= 2 bool enable_outbound_addr; struct sockaddr_in outbound_addr; bool enable_outbound_addr6; struct sockaddr_in6 outbound_addr6; #endif #if SLIRP_CONFIG_VERSION_MAX >= 3 bool disable_dns; #endif struct sockaddr vmacaddress; // MAC address of interface int vmacaddress_len; // MAC address byte length }; int do_slirp(int tapfd, int readyfd, int exitfd, const char *api_socket, struct slirp4netns_config *cfg); #endif slirp4netns-1.2.1/tests/000077500000000000000000000000001447011446400151475ustar00rootroot00000000000000slirp4netns-1.2.1/tests/common.sh000077500000000000000000000047201447011446400170010ustar00rootroot00000000000000#!/bin/bash # See https://www.gnu.org/software/automake/manual/html_node/Scripts_002dbased-Testsuites.html#Testsuite-progress-on-console TEST_EXIT_CODE_SKIP=77 function nsenter_flags { pid=$1 flags="--target=${pid}" userns="$(readlink /proc/${pid}/ns/user)" mntns="$(readlink /proc/${pid}/ns/mnt)" netns="$(readlink /proc/${pid}/ns/net)" self_userns="$(readlink /proc/self/ns/user)" self_mntns="$(readlink /proc/self/ns/mnt)" self_netns="$(readlink /proc/self/ns/net)" if [ "${userns}" != "${self_userns}" ]; then flags="$flags --preserve-credentials -U" fi if [ "${mntns}" != "${self_mntns}" ]; then flags="$flags -m" fi if [ "${netns}" != "${self_netns}" ]; then flags="$flags -n" fi echo "${flags}" } function wait_for_network_namespace { # Wait that the namespace is ready. COUNTER=0 while [ $COUNTER -lt 40 ]; do flags=$(nsenter_flags $1) if $(echo $flags | grep -qvw -- -n); then flags="$flags -n" fi if nsenter ${flags} true >/dev/null 2>&1; then return 0 else sleep 0.5 fi let COUNTER=COUNTER+1 done exit 1 } function wait_for_network_device { # Wait that the device appears. COUNTER=0 while [ $COUNTER -lt 40 ]; do if nsenter $(nsenter_flags $1) ip addr show $2; then return 0 else sleep 0.5 fi let COUNTER=COUNTER+1 done exit 1 } function wait_process_exits { COUNTER=0 while [ $COUNTER -lt 40 ]; do if kill -0 $1; then sleep 0.5 else return 0 fi let COUNTER=COUNTER+1 done exit 1 } function wait_for_ping_connectivity { COUNTER=0 while [ $COUNTER -lt 40 ]; do if nsenter $(nsenter_flags $1) ping -c 1 -w 1 $2; then return 0 else sleep 0.5 fi let COUNTER=COUNTER+1 done exit 1 } function wait_for_connectivity { COUNTER=0 while [ $COUNTER -lt 40 ]; do if echo "wait_for_connectivity" | nsenter $(nsenter_flags $1) ncat -v $2 $3; then return 0 else sleep 0.5 fi let COUNTER=COUNTER+1 done exit 1 } function wait_for_file_content { # Wait for a file to get the specified content. COUNTER=0 while [ $COUNTER -lt 20 ]; do if grep $1 $2; then return 0 else sleep 0.5 fi let COUNTER=COUNTER+1 done exit 1 } function expose_tcp() { apisock=$1 hostport=$2 guestport=$3 json="{\"execute\": \"add_hostfwd\", \"arguments\": {\"proto\": \"tcp\", \"host_addr\": \"0.0.0.0\", \"host_port\": $hostport, \"guest_addr\": \"10.0.2.100\", \"guest_port\": $guestport}}" echo -n $json | ncat -U $apisock echo -n "{\"execute\": \"list_hostfwd\"}" | ncat -U $apisock } slirp4netns-1.2.1/tests/test-slirp4netns-api-socket.sh000077500000000000000000000044521447011446400230120ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child tmpdir=$(mktemp -d /tmp/slirp4netns-bench.XXXXXXXXXX) apisocket=${tmpdir}/slirp4netns.sock apisocketlongpath=${tmpdir}/slirp4netns-TOO-LONG-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.sock if slirp4netns -c $child --api-socket $apisocketlongpath tap11; then echo "expected failure with apisocket path too long" >&2 kill -9 $child rm -rf $tmpdir exit 1 fi slirp4netns -c $child --api-socket $apisocket tap11 & slirp_pid=$! wait_for_network_device $child tap11 function cleanup() { kill -9 $child $slirp_pid rm -rf $tmpdir } trap cleanup EXIT result=$(echo 'badjson' | ncat -U $apisocket) echo $result | jq .error.desc | grep "bad request: cannot parse JSON" result=$(echo '{"unexpectedjson": 42}' | ncat -U $apisocket) echo $result | jq .error.desc | grep "bad request: no execute found" result=$(echo '{"execute": "bad"}' | ncat -U $apisocket) echo $result | jq .error.desc | grep "bad request: unknown execute" result=$(echo '{"execute": "add_hostfwd", "arguments":{"proto": "bad"}}' | ncat -U $apisocket) echo $result | jq .error.desc | grep "bad request: add_hostfwd: bad arguments.proto" set +e result=$(cat /dev/zero | ncat -U $apisocket || true) set set -e echo $result | jq .error.desc | grep "bad request: too large message" result=$(echo '{"execute": "add_hostfwd", "arguments":{"proto": "tcp","host_port":8080,"guest_port":80}}' | ncat -U $apisocket) [[ $(echo $result | jq .error) == null ]] id=$(echo $result | jq .return.id) [[ $id == 1 ]] result=$(echo '{"execute": "list_hostfwd"}' | ncat -U $apisocket) [[ $(echo $result | jq .error) == null ]] [[ $(echo $result | jq .return.entries[0].id) == $id ]] [[ $(echo $result | jq .return.entries[0].proto) == '"tcp"' ]] [[ $(echo $result | jq .return.entries[0].host_addr) == '"0.0.0.0"' ]] [[ $(echo $result | jq .return.entries[0].host_port) == 8080 ]] [[ $(echo $result | jq .return.entries[0].guest_addr) == '"10.0.2.100"' ]] [[ $(echo $result | jq .return.entries[0].guest_port) == 80 ]] result=$(echo '{"execute": "remove_hostfwd", "arguments":{"id": 1}}' | ncat -U $apisocket) [[ $(echo $result | jq .error) == null ]] # see also: benchmarks/benchmark-iperf3-reverse.sh slirp4netns-1.2.1/tests/test-slirp4netns-cidr.sh000077500000000000000000000020431447011446400216660ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child set +e result=$(slirp4netns -c --cidr 24 $child tap11 2>&1) set -e echo $result | grep "invalid CIDR" set +e result=$(slirp4netns -c --cidr foo $child tap11 2>&1) set -e echo $result | grep "invalid CIDR" set +e result=$(slirp4netns -c --cidr 10.0.2.0 $child tap11 2>&1) set -e echo $result | grep "invalid CIDR" set +e result=$(slirp4netns -c --cidr 10.0.2.100/24 $child tap11 2>&1) set -e echo $result | grep "CIDR needs to be a network address like 10.0.2.0/24, not like 10.0.2.100/24" set +e result=$(slirp4netns -c --cidr 10.0.2.100/26 $child tap11 2>&1) set -e echo $result | grep "prefix length needs to be 1-25" slirp4netns -c $child --cidr 10.0.135.128/25 tap11 & slirp_pid=$! wait_for_network_device $child tap11 function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT result="$(nsenter $(nsenter_flags $child) ip a show dev tap11)" echo "$result" | grep -o '^\s*inet .*/' | grep -F 10.0.135.228 slirp4netns-1.2.1/tests/test-slirp4netns-configure.sh000077500000000000000000000006271447011446400227340ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns -c $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT nsenter $(nsenter_flags $child) ip -a netconf | grep tap11 nsenter $(nsenter_flags $child) ip addr show tap11 | grep inet slirp4netns-1.2.1/tests/test-slirp4netns-dhcp.sh000077500000000000000000000015041447011446400216640ustar00rootroot00000000000000#!/bin/bash # This test should pass with libslirp < 4.6.0 and libslirp >= 4.6.1, # but should fail with libslirp == 4.6.0 . # https://gitlab.freedesktop.org/slirp/libslirp/-/issues/48 set -xeuo pipefail . $(dirname $0)/common.sh if ! command -v udhcpc 2>&1; then echo "udhcpc is missing, skipping the test" exit "$TEST_EXIT_CODE_SKIP" fi unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns $child tap0 & slirp_pid=$! udhcpc_log=$(mktemp /tmp/slirp4netns-test-udhcpc.XXXXXXXXXX) function cleanup { kill -9 $child $slirp_pid rm -f $udhcpc_log } trap cleanup EXIT nsenter $(nsenter_flags $child) ip link set lo up nsenter $(nsenter_flags $child) ip link set tap0 up nsenter $(nsenter_flags $child) timeout 10s udhcpc -q -i tap0 -s /bin/true 2>&1 | tee $udhcpc_log grep 10.0.2.15 $udhcpc_log slirp4netns-1.2.1/tests/test-slirp4netns-disable-dns.sh000077500000000000000000000015151447011446400231350ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh SLIRP_CONFIG_VERSION_MAX=$(slirp4netns -v | grep "SLIRP_CONFIG_VERSION_MAX: " | sed 's#SLIRP_CONFIG_VERSION_MAX: \(\)##') if [ "${SLIRP_CONFIG_VERSION_MAX:-0}" -lt 3 ]; then printf "'--disable-dns' requires SLIRP_CONFIG_VERSION_MAX 3 or newer. Test skipped..." exit "$TEST_EXIT_CODE_SKIP" fi port=53 unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child mtu=${MTU:=1500} slirp4netns -c --mtu $mtu --disable-dns $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 # ping to 10.0.2.2 wait_for_ping_connectivity $child 10.0.2.2 function cleanup() { kill -9 $child $slirp_pid } trap cleanup EXIT set +e err=$(echo "should fail" | nsenter $(nsenter_flags $child) ncat -v 10.0.2.3 $port 2>&1) set -e echo $err | grep 'Connection timed out\|TIMEOUT' slirp4netns-1.2.1/tests/test-slirp4netns-disable-host-loopback.sh000077500000000000000000000012151447011446400251130ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/../tests/common.sh port=12121 ncat -l 127.0.0.1 $port & nc_pid=$! unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child mtu=${MTU:=1500} slirp4netns -c --mtu $mtu --disable-host-loopback $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 # ping to 10.0.2.2 is possible even with --disable-host-loopback wait_for_ping_connectivity $child 10.0.2.2 function cleanup { kill -9 $nc_pid $child $slirp_pid } trap cleanup EXIT set +e err=$(echo "should fail" | nsenter $(nsenter_flags $child) ncat -v 10.0.2.2 $port 2>&1) set -e echo $err | grep "Network is unreachable" slirp4netns-1.2.1/tests/test-slirp4netns-exit-fd.sh000077500000000000000000000010051447011446400223020ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child touch keep_alive slirp4netns -e 10 $child tap11 10<(while test -e keep_alive; do sleep 0.1; done) & slirp_pid=$! function cleanup { set +xeuo pipefail kill -9 $child $slirp_pid rm -f keep_alive } trap cleanup EXIT # wait a while, check that slirp4netns is alive kill -0 $slirp_pid rm keep_alive wait_process_exits $slirp_pid if kill -0 $slirp_pid; then exit 1 fi exit 0 slirp4netns-1.2.1/tests/test-slirp4netns-macaddress.sh000077500000000000000000000010621447011446400230530ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh MACADDRESS="0e:d4:18:d9:38:fb" unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT slirp4netns -c --macaddress $MACADDRESS $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 result=$(nsenter $(nsenter_flags $child) ip addr show tap11 | grep -o "ether $MACADDRESS") if [ -z "$result" ]; then printf "expecting %s MAC address on the interface but didn't get it" "$MACADDRESS" exit 1 fi cleanup slirp4netns-1.2.1/tests/test-slirp4netns-nspath.sh000077500000000000000000000016361447011446400222510ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh # Test --userns-path= --netns-type=path unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns --userns-path=/proc/$child/ns/user --netns-type=path /proc/$child/ns/net tap11 & slirp_pid=$! function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT wait_for_network_device $child tap11 nsenter $(nsenter_flags $child) ip -a netconf | grep tap11 nsenter $(nsenter_flags $child) ip addr show tap11 | grep -v inet kill -9 $child $slirp_pid # Test --netns-type=path unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child nsenter --preserve-credentials -U --target=$child slirp4netns --netns-type=path /proc/$child/ns/net tap11 & slirp_pid=$! wait_for_network_device $child tap11 nsenter $(nsenter_flags $child) ip -a netconf | grep tap11 nsenter $(nsenter_flags $child) ip addr show tap11 | grep -v inet slirp4netns-1.2.1/tests/test-slirp4netns-outbound-addr.sh000077500000000000000000000022751447011446400235230ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh SLIRP_CONFIG_VERSION_MAX=$(slirp4netns -v | grep "SLIRP_CONFIG_VERSION_MAX: " | sed 's#SLIRP_CONFIG_VERSION_MAX: \(\)##') if [ "${SLIRP_CONFIG_VERSION_MAX:-0}" -lt 2 ]; then printf "'--disable-dns' requires SLIRP_CONFIG_VERSION_MAX 2 or newer. Test skipped..." exit "$TEST_EXIT_CODE_SKIP" fi IPv4_1="127.0.0.1" IPv4_2=$(ip a | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p' | head -n 1) # For future ipv6 tests #IPv6_1="::1" #IPv6_2=$(ip a | sed -En 's/::1\/128//;s/.*inet6 (addr:)?([^ ]*)\/.*$/\2/p' | head -n 1) function cleanup() { rm -rf ncat.log kill -9 $child $slirp_pid || exit 0 } trap cleanup EXIT port=12122 mtu=${MTU:=1500} IPs=("$IPv4_1" "$IPv4_2") for ip in "${IPs[@]}"; do ncat -l $port -v >ncat.log 2>&1 & ncat1=$! unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns -c --mtu $mtu --outbound-addr="$ip" $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 wait_for_connectivity $child 10.0.2.2 $port wait_process_exits $ncat1 if ! grep "$ip" ncat.log; then printf "%s not found in ncat.log" "$ip" exit 1 fi cleanup let port=port+1 done slirp4netns-1.2.1/tests/test-slirp4netns-ready-fd.sh000077500000000000000000000006361447011446400224460ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child touch keep_alive slirp4netns -c -r 10 $child tap11 10>configured & slirp_pid=$! function cleanup { set +xeuo pipefail kill -9 $child $slirp_pid rm -f configured keep_alive } trap cleanup EXIT wait_for_network_device $child tap11 wait_for_file_content 1 configured exit 0 slirp4netns-1.2.1/tests/test-slirp4netns-sandbox-no-unmount.sh000077500000000000000000000010501447011446400245150ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail if [[ ! -v "CHILD" ]]; then # reexec in a new mount namespace export CHILD=1 exec unshare -rm "$0" "$@" fi . $(dirname $0)/common.sh mount -t tmpfs tmpfs /run mkdir /run/foo mount -t tmpfs tmpfs /run/foo mount --make-rshared /run unshare -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns --enable-sandbox --netns-type=path /proc/$child/ns/net tap11 & slirp_pid=$! function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT wait_for_network_device $child tap11 findmnt /run/foo slirp4netns-1.2.1/tests/test-slirp4netns-sandbox.sh000077500000000000000000000015401447011446400224040ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns --ready-fd=3 --enable-sandbox $child tap11 3>ready.file & slirp_pid=$! # Wait that the sandbox is created wait_for_file_content 1 ready.file rm ready.file # Check there are no capabilities left in slirp4netns getpcaps $slirp_pid 2>&1 | tail -n1 >slirp.caps grep cap_net_bind_service slirp.caps grep -v cap_sys_admin slirp.caps rm slirp.caps test -e /proc/$slirp_pid/root/etc test -e /proc/$slirp_pid/root/run test \! -e /proc/$slirp_pid/root/home test \! -e /proc/$slirp_pid/root/root test \! -e /proc/$slirp_pid/root/var function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT nsenter $(nsenter_flags $child) ip -a netconf | grep tap11 nsenter $(nsenter_flags $child) ip addr show tap11 | grep -v inet slirp4netns-1.2.1/tests/test-slirp4netns-seccomp.sh000077500000000000000000000007131447011446400224000ustar00rootroot00000000000000#!/bin/bash set -xeuo pipefail . $(dirname $0)/common.sh unshare -r -n sleep infinity & child=$! wait_for_network_namespace $child slirp4netns -c --enable-seccomp --userns-path=/proc/$child/ns/user $child tap11 & slirp_pid=$! wait_for_network_device $child tap11 function cleanup { kill -9 $child $slirp_pid } trap cleanup EXIT nsenter $(nsenter_flags $child) ip -a netconf | grep tap11 nsenter $(nsenter_flags $child) ip addr show tap11 | grep inet slirp4netns-1.2.1/vendor.sh000077500000000000000000000016651447011446400156510ustar00rootroot00000000000000#!/bin/bash set -eux -o pipefail # v1.5.2 (May 21, 2023) PARSON_COMMIT=60c37844d7a1c97547812cac3423d458c73e60f9 PARSON_REPO=https://github.com/kgabis/parson.git # prepare slirp4netns_root=$(realpath $(dirname $0)) tmp=$(mktemp -d /tmp/slirp4netns-vendor.XXXXXXXXXX) tmp_git=$tmp/git tmp_vendor=$tmp/vendor mkdir -p $tmp_git $tmp_vendor # vendor parson git clone $PARSON_REPO $tmp_git/parson ( cd $tmp_git/parson git checkout $PARSON_COMMIT mkdir -p $tmp_vendor/parson cp -a LICENSE README.md parson.c parson.h $tmp_vendor/parson ) # write vendor/README.md cat <$tmp_vendor/README.md # DO NOT EDIT MANUALLY Vendored components: * parson: $PARSON_REPO (\`$PARSON_COMMIT\`) EOF cat <>$tmp_vendor/README.md Please do not edit the contents under this directory manually. Use [\`../vendor.sh\`](../vendor.sh) to update the contents. EOF # fix up rm -rf $slirp4netns_root/vendor mv $tmp_vendor $slirp4netns_root/vendor rm -rf $tmp slirp4netns-1.2.1/vendor/000077500000000000000000000000001447011446400153025ustar00rootroot00000000000000slirp4netns-1.2.1/vendor/README.md000066400000000000000000000004051447011446400165600ustar00rootroot00000000000000# DO NOT EDIT MANUALLY Vendored components: * parson: https://github.com/kgabis/parson.git (`60c37844d7a1c97547812cac3423d458c73e60f9`) Please do not edit the contents under this directory manually. Use [`../vendor.sh`](../vendor.sh) to update the contents. slirp4netns-1.2.1/vendor/parson/000077500000000000000000000000001447011446400166045ustar00rootroot00000000000000slirp4netns-1.2.1/vendor/parson/LICENSE000066400000000000000000000020671447011446400176160ustar00rootroot00000000000000MIT License Copyright (c) 2012 - 2022 Krzysztof Gabis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. slirp4netns-1.2.1/vendor/parson/README.md000066400000000000000000000116741447011446400200740ustar00rootroot00000000000000## About Parson is a lightweight [json](http://json.org) library written in C. ## Features * Lightweight (only 2 files) * Simple API * Addressing json values with dot notation (similar to C structs or objects in most OO languages, e.g. "objectA.objectB.value") * C89 compatible * Test suites ## Installation Run: ``` git clone https://github.com/kgabis/parson.git ``` and copy parson.h and parson.c to you source code tree. Run ```make test``` to compile and run tests. ## Examples ### Parsing JSON Here is a function, which prints basic commit info (date, sha and author) from a github repository. ```c void print_commits_info(const char *username, const char *repo) { JSON_Value *root_value; JSON_Array *commits; JSON_Object *commit; size_t i; char curl_command[512]; char cleanup_command[256]; char output_filename[] = "commits.json"; /* it ain't pretty, but it's not a libcurl tutorial */ sprintf(curl_command, "curl -s \"https://api.github.com/repos/%s/%s/commits\" > %s", username, repo, output_filename); sprintf(cleanup_command, "rm -f %s", output_filename); system(curl_command); /* parsing json and validating output */ root_value = json_parse_file(output_filename); if (json_value_get_type(root_value) != JSONArray) { system(cleanup_command); return; } /* getting array from root value and printing commit info */ commits = json_value_get_array(root_value); printf("%-10.10s %-10.10s %s\n", "Date", "SHA", "Author"); for (i = 0; i < json_array_get_count(commits); i++) { commit = json_array_get_object(commits, i); printf("%.10s %.10s %s\n", json_object_dotget_string(commit, "commit.author.date"), json_object_get_string(commit, "sha"), json_object_dotget_string(commit, "commit.author.name")); } /* cleanup code */ json_value_free(root_value); system(cleanup_command); } ``` Calling ```print_commits_info("torvalds", "linux");``` prints: ``` Date SHA Author 2012-10-15 dd8e8c4a2c David Rientjes 2012-10-15 3ce9e53e78 Michal Marek 2012-10-14 29bb4cc5e0 Randy Dunlap 2012-10-15 325adeb55e Ralf Baechle 2012-10-14 68687c842c Russell King 2012-10-14 ddffeb8c4d Linus Torvalds ... ``` ### Persistence In this example I'm using parson to save user information to a file and then load it and validate later. ```c void persistence_example(void) { JSON_Value *schema = json_parse_string("{\"name\":\"\"}"); JSON_Value *user_data = json_parse_file("user_data.json"); char buf[256]; const char *name = NULL; if (user_data == NULL || json_validate(schema, user_data) != JSONSuccess) { puts("Enter your name:"); scanf("%s", buf); user_data = json_value_init_object(); json_object_set_string(json_object(user_data), "name", buf); json_serialize_to_file(user_data, "user_data.json"); } name = json_object_get_string(json_object(user_data), "name"); printf("Hello, %s.", name); json_value_free(schema); json_value_free(user_data); return; } ``` ### Serialization Creating JSON values is very simple thanks to the dot notation. Object hierarchy is automatically created when addressing specific fields. In the following example I create a simple JSON value containing basic information about a person. ```c void serialization_example(void) { JSON_Value *root_value = json_value_init_object(); JSON_Object *root_object = json_value_get_object(root_value); char *serialized_string = NULL; json_object_set_string(root_object, "name", "John Smith"); json_object_set_number(root_object, "age", 25); json_object_dotset_string(root_object, "address.city", "Cupertino"); json_object_dotset_value(root_object, "contact.emails", json_parse_string("[\"email@example.com\",\"email2@example.com\"]")); serialized_string = json_serialize_to_string_pretty(root_value); puts(serialized_string); json_free_serialized_string(serialized_string); json_value_free(root_value); } ``` Output: ``` { "name": "John Smith", "age": 25, "address": { "city": "Cupertino" }, "contact": { "emails": [ "email@example.com", "email2@example.com" ] } } ``` ## Contributing I will always merge *working* bug fixes. However, if you want to add something new to the API, please create an "issue" on github for this first so we can discuss if it should end up in the library before you start implementing it. Remember to follow parson's code style and write appropriate tests. ## My other projects * [ape](https://github.com/kgabis/ape) - simple programming language implemented in C library * [kgflags](https://github.com/kgabis/kgflags) - easy to use command-line flag parsing library * [agnes](https://github.com/kgabis/agnes) - header-only NES emulation library ## License [The MIT License (MIT)](http://opensource.org/licenses/mit-license.php) slirp4netns-1.2.1/vendor/parson/parson.c000066400000000000000000002414511447011446400202610ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT Parson 1.5.2 (https://github.com/kgabis/parson) Copyright (c) 2012 - 2023 Krzysztof Gabis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif /* _CRT_SECURE_NO_WARNINGS */ #endif /* _MSC_VER */ #include "parson.h" #define PARSON_IMPL_VERSION_MAJOR 1 #define PARSON_IMPL_VERSION_MINOR 5 #define PARSON_IMPL_VERSION_PATCH 2 #if (PARSON_VERSION_MAJOR != PARSON_IMPL_VERSION_MAJOR)\ || (PARSON_VERSION_MINOR != PARSON_IMPL_VERSION_MINOR)\ || (PARSON_VERSION_PATCH != PARSON_IMPL_VERSION_PATCH) #error "parson version mismatch between parson.c and parson.h" #endif #include #include #include #include #include #include /* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you * don't have to. */ #ifdef sscanf #undef sscanf #define sscanf THINK_TWICE_ABOUT_USING_SSCANF #endif /* strcpy is unsafe */ #ifdef strcpy #undef strcpy #endif #define strcpy USE_MEMCPY_INSTEAD_OF_STRCPY #define STARTING_CAPACITY 16 #define MAX_NESTING 2048 #ifndef PARSON_DEFAULT_FLOAT_FORMAT #define PARSON_DEFAULT_FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */ #endif #ifndef PARSON_NUM_BUF_SIZE #define PARSON_NUM_BUF_SIZE 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and use 64 */ #endif #ifndef PARSON_INDENT_STR #define PARSON_INDENT_STR " " #endif #define SIZEOF_TOKEN(a) (sizeof(a) - 1) #define SKIP_CHAR(str) ((*str)++) #define SKIP_WHITESPACES(str) while (isspace((unsigned char)(**str))) { SKIP_CHAR(str); } #define MAX(a, b) ((a) > (b) ? (a) : (b)) #undef malloc #undef free #if defined(isnan) && defined(isinf) #define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x))) #else #define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0) #endif #define OBJECT_INVALID_IX ((size_t)-1) static JSON_Malloc_Function parson_malloc = malloc; static JSON_Free_Function parson_free = free; static int parson_escape_slashes = 1; static char *parson_float_format = NULL; static JSON_Number_Serialization_Function parson_number_serialization_function = NULL; #define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ typedef int parson_bool_t; #define PARSON_TRUE 1 #define PARSON_FALSE 0 typedef struct json_string { char *chars; size_t length; } JSON_String; /* Type definitions */ typedef union json_value_value { JSON_String string; double number; JSON_Object *object; JSON_Array *array; int boolean; int null; } JSON_Value_Value; struct json_value_t { JSON_Value *parent; JSON_Value_Type type; JSON_Value_Value value; }; struct json_object_t { JSON_Value *wrapping_value; size_t *cells; unsigned long *hashes; char **names; JSON_Value **values; size_t *cell_ixs; size_t count; size_t item_capacity; size_t cell_capacity; }; struct json_array_t { JSON_Value *wrapping_value; JSON_Value **items; size_t count; size_t capacity; }; /* Various */ static char * read_file(const char *filename); static void remove_comments(char *string, const char *start_token, const char *end_token); static char * parson_strndup(const char *string, size_t n); static char * parson_strdup(const char *string); static int hex_char_to_int(char c); static JSON_Status parse_utf16_hex(const char *string, unsigned int *result); static int num_bytes_in_utf8_sequence(unsigned char c); static JSON_Status verify_utf8_sequence(const unsigned char *string, int *len); static parson_bool_t is_valid_utf8(const char *string, size_t string_len); static parson_bool_t is_decimal(const char *string, size_t length); static unsigned long hash_string(const char *string, size_t n); /* JSON Object */ static JSON_Object * json_object_make(JSON_Value *wrapping_value); static JSON_Status json_object_init(JSON_Object *object, size_t capacity); static void json_object_deinit(JSON_Object *object, parson_bool_t free_keys, parson_bool_t free_values); static JSON_Status json_object_grow_and_rehash(JSON_Object *object); static size_t json_object_get_cell_ix(const JSON_Object *object, const char *key, size_t key_len, unsigned long hash, parson_bool_t *out_found); static JSON_Status json_object_add(JSON_Object *object, char *name, JSON_Value *value); static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len); static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, parson_bool_t free_value); static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, parson_bool_t free_value); static void json_object_free(JSON_Object *object); /* JSON Array */ static JSON_Array * json_array_make(JSON_Value *wrapping_value); static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); static void json_array_free(JSON_Array *array); /* JSON Value */ static JSON_Value * json_value_init_string_no_copy(char *string, size_t length); static const JSON_String * json_value_get_string_desc(const JSON_Value *value); /* Parser */ static JSON_Status skip_quotes(const char **string); static JSON_Status parse_utf16(const char **unprocessed, char **processed); static char * process_string(const char *input, size_t input_len, size_t *output_len); static char * get_quoted_string(const char **string, size_t *output_string_len); static JSON_Value * parse_object_value(const char **string, size_t nesting); static JSON_Value * parse_array_value(const char **string, size_t nesting); static JSON_Value * parse_string_value(const char **string); static JSON_Value * parse_boolean_value(const char **string); static JSON_Value * parse_number_value(const char **string); static JSON_Value * parse_null_value(const char **string); static JSON_Value * parse_value(const char **string, size_t nesting); /* Serialization */ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, parson_bool_t is_pretty, char *num_buf); static int json_serialize_string(const char *string, size_t len, char *buf); /* Various */ static char * read_file(const char * filename) { FILE *fp = fopen(filename, "r"); size_t size_to_read = 0; size_t size_read = 0; long pos; char *file_contents; if (!fp) { return NULL; } fseek(fp, 0L, SEEK_END); pos = ftell(fp); if (pos < 0) { fclose(fp); return NULL; } size_to_read = pos; rewind(fp); file_contents = (char*)parson_malloc(sizeof(char) * (size_to_read + 1)); if (!file_contents) { fclose(fp); return NULL; } size_read = fread(file_contents, 1, size_to_read, fp); if (size_read == 0 || ferror(fp)) { fclose(fp); parson_free(file_contents); return NULL; } fclose(fp); file_contents[size_read] = '\0'; return file_contents; } static void remove_comments(char *string, const char *start_token, const char *end_token) { parson_bool_t in_string = PARSON_FALSE, escaped = PARSON_FALSE; size_t i; char *ptr = NULL, current_char; size_t start_token_len = strlen(start_token); size_t end_token_len = strlen(end_token); if (start_token_len == 0 || end_token_len == 0) { return; } while ((current_char = *string) != '\0') { if (current_char == '\\' && !escaped) { escaped = PARSON_TRUE; string++; continue; } else if (current_char == '\"' && !escaped) { in_string = !in_string; } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { for(i = 0; i < start_token_len; i++) { string[i] = ' '; } string = string + start_token_len; ptr = strstr(string, end_token); if (!ptr) { return; } for (i = 0; i < (ptr - string) + end_token_len; i++) { string[i] = ' '; } string = ptr + end_token_len - 1; } escaped = PARSON_FALSE; string++; } } static char * parson_strndup(const char *string, size_t n) { /* We expect the caller has validated that 'n' fits within the input buffer. */ char *output_string = (char*)parson_malloc(n + 1); if (!output_string) { return NULL; } output_string[n] = '\0'; memcpy(output_string, string, n); return output_string; } static char * parson_strdup(const char *string) { return parson_strndup(string, strlen(string)); } static int hex_char_to_int(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; } static JSON_Status parse_utf16_hex(const char *s, unsigned int *result) { int x1, x2, x3, x4; if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') { return JSONFailure; } x1 = hex_char_to_int(s[0]); x2 = hex_char_to_int(s[1]); x3 = hex_char_to_int(s[2]); x4 = hex_char_to_int(s[3]); if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) { return JSONFailure; } *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4); return JSONSuccess; } static int num_bytes_in_utf8_sequence(unsigned char c) { if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { return 0; } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ return 1; } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ return 2; } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ return 3; } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ return 4; } return 0; /* won't happen */ } static JSON_Status verify_utf8_sequence(const unsigned char *string, int *len) { unsigned int cp = 0; *len = num_bytes_in_utf8_sequence(string[0]); if (*len == 1) { cp = string[0]; } else if (*len == 2 && IS_CONT(string[1])) { cp = string[0] & 0x1F; cp = (cp << 6) | (string[1] & 0x3F); } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { cp = ((unsigned char)string[0]) & 0xF; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { cp = string[0] & 0x7; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); cp = (cp << 6) | (string[3] & 0x3F); } else { return JSONFailure; } /* overlong encodings */ if ((cp < 0x80 && *len > 1) || (cp < 0x800 && *len > 2) || (cp < 0x10000 && *len > 3)) { return JSONFailure; } /* invalid unicode */ if (cp > 0x10FFFF) { return JSONFailure; } /* surrogate halves */ if (cp >= 0xD800 && cp <= 0xDFFF) { return JSONFailure; } return JSONSuccess; } static int is_valid_utf8(const char *string, size_t string_len) { int len = 0; const char *string_end = string + string_len; while (string < string_end) { if (verify_utf8_sequence((const unsigned char*)string, &len) != JSONSuccess) { return PARSON_FALSE; } string += len; } return PARSON_TRUE; } static parson_bool_t is_decimal(const char *string, size_t length) { if (length > 1 && string[0] == '0' && string[1] != '.') { return PARSON_FALSE; } if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') { return PARSON_FALSE; } while (length--) { if (strchr("xX", string[length])) { return PARSON_FALSE; } } return PARSON_TRUE; } static unsigned long hash_string(const char *string, size_t n) { #ifdef PARSON_FORCE_HASH_COLLISIONS (void)string; (void)n; return 0; #else unsigned long hash = 5381; unsigned char c; size_t i = 0; for (i = 0; i < n; i++) { c = string[i]; if (c == '\0') { break; } hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; #endif } /* JSON Object */ static JSON_Object * json_object_make(JSON_Value *wrapping_value) { JSON_Status res = JSONFailure; JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); if (new_obj == NULL) { return NULL; } new_obj->wrapping_value = wrapping_value; res = json_object_init(new_obj, 0); if (res != JSONSuccess) { parson_free(new_obj); return NULL; } return new_obj; } static JSON_Status json_object_init(JSON_Object *object, size_t capacity) { unsigned int i = 0; object->cells = NULL; object->names = NULL; object->values = NULL; object->cell_ixs = NULL; object->hashes = NULL; object->count = 0; object->cell_capacity = capacity; object->item_capacity = (unsigned int)(capacity * 7/10); if (capacity == 0) { return JSONSuccess; } object->cells = (size_t*)parson_malloc(object->cell_capacity * sizeof(*object->cells)); object->names = (char**)parson_malloc(object->item_capacity * sizeof(*object->names)); object->values = (JSON_Value**)parson_malloc(object->item_capacity * sizeof(*object->values)); object->cell_ixs = (size_t*)parson_malloc(object->item_capacity * sizeof(*object->cell_ixs)); object->hashes = (unsigned long*)parson_malloc(object->item_capacity * sizeof(*object->hashes)); if (object->cells == NULL || object->names == NULL || object->values == NULL || object->cell_ixs == NULL || object->hashes == NULL) { goto error; } for (i = 0; i < object->cell_capacity; i++) { object->cells[i] = OBJECT_INVALID_IX; } return JSONSuccess; error: parson_free(object->cells); parson_free(object->names); parson_free(object->values); parson_free(object->cell_ixs); parson_free(object->hashes); return JSONFailure; } static void json_object_deinit(JSON_Object *object, parson_bool_t free_keys, parson_bool_t free_values) { unsigned int i = 0; for (i = 0; i < object->count; i++) { if (free_keys) { parson_free(object->names[i]); } if (free_values) { json_value_free(object->values[i]); } } object->count = 0; object->item_capacity = 0; object->cell_capacity = 0; parson_free(object->cells); parson_free(object->names); parson_free(object->values); parson_free(object->cell_ixs); parson_free(object->hashes); object->cells = NULL; object->names = NULL; object->values = NULL; object->cell_ixs = NULL; object->hashes = NULL; } static JSON_Status json_object_grow_and_rehash(JSON_Object *object) { JSON_Value *wrapping_value = NULL; JSON_Object new_object; char *key = NULL; JSON_Value *value = NULL; unsigned int i = 0; size_t new_capacity = MAX(object->cell_capacity * 2, STARTING_CAPACITY); JSON_Status res = json_object_init(&new_object, new_capacity); if (res != JSONSuccess) { return JSONFailure; } wrapping_value = json_object_get_wrapping_value(object); new_object.wrapping_value = wrapping_value; for (i = 0; i < object->count; i++) { key = object->names[i]; value = object->values[i]; res = json_object_add(&new_object, key, value); if (res != JSONSuccess) { json_object_deinit(&new_object, PARSON_FALSE, PARSON_FALSE); return JSONFailure; } value->parent = wrapping_value; } json_object_deinit(object, PARSON_FALSE, PARSON_FALSE); *object = new_object; return JSONSuccess; } static size_t json_object_get_cell_ix(const JSON_Object *object, const char *key, size_t key_len, unsigned long hash, parson_bool_t *out_found) { size_t cell_ix = hash & (object->cell_capacity - 1); size_t cell = 0; size_t ix = 0; unsigned int i = 0; unsigned long hash_to_check = 0; const char *key_to_check = NULL; size_t key_to_check_len = 0; *out_found = PARSON_FALSE; for (i = 0; i < object->cell_capacity; i++) { ix = (cell_ix + i) & (object->cell_capacity - 1); cell = object->cells[ix]; if (cell == OBJECT_INVALID_IX) { return ix; } hash_to_check = object->hashes[cell]; if (hash != hash_to_check) { continue; } key_to_check = object->names[cell]; key_to_check_len = strlen(key_to_check); if (key_to_check_len == key_len && strncmp(key, key_to_check, key_len) == 0) { *out_found = PARSON_TRUE; return ix; } } return OBJECT_INVALID_IX; } static JSON_Status json_object_add(JSON_Object *object, char *name, JSON_Value *value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; JSON_Status res = JSONFailure; if (!object || !name || !value) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (found) { return JSONFailure; } if (object->count >= object->item_capacity) { res = json_object_grow_and_rehash(object); if (res != JSONSuccess) { return JSONFailure; } cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); } object->names[object->count] = name; object->cells[cell_ix] = object->count; object->values[object->count] = value; object->cell_ixs[object->count] = cell_ix; object->hashes[object->count] = hash; object->count++; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; size_t item_ix = 0; if (!object || !name) { return NULL; } hash = hash_string(name, name_len); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, name_len, hash, &found); if (!found) { return NULL; } item_ix = object->cells[cell_ix]; return object->values[item_ix]; } static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, parson_bool_t free_value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell = 0; size_t item_ix = 0; size_t last_item_ix = 0; size_t i = 0; size_t j = 0; size_t x = 0; size_t k = 0; JSON_Value *val = NULL; if (object == NULL) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (!found) { return JSONFailure; } item_ix = object->cells[cell]; if (free_value) { val = object->values[item_ix]; json_value_free(val); val = NULL; } parson_free(object->names[item_ix]); last_item_ix = object->count - 1; if (item_ix < last_item_ix) { object->names[item_ix] = object->names[last_item_ix]; object->values[item_ix] = object->values[last_item_ix]; object->cell_ixs[item_ix] = object->cell_ixs[last_item_ix]; object->hashes[item_ix] = object->hashes[last_item_ix]; object->cells[object->cell_ixs[item_ix]] = item_ix; } object->count--; i = cell; j = i; for (x = 0; x < (object->cell_capacity - 1); x++) { j = (j + 1) & (object->cell_capacity - 1); if (object->cells[j] == OBJECT_INVALID_IX) { break; } k = object->hashes[object->cells[j]] & (object->cell_capacity - 1); if ((j > i && (k <= i || k > j)) || (j < i && (k <= i && k > j))) { object->cell_ixs[object->cells[j]] = i; object->cells[i] = object->cells[j]; i = j; } } object->cells[i] = OBJECT_INVALID_IX; return JSONSuccess; } static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, parson_bool_t free_value) { JSON_Value *temp_value = NULL; JSON_Object *temp_object = NULL; const char *dot_pos = strchr(name, '.'); if (!dot_pos) { return json_object_remove_internal(object, name, free_value); } temp_value = json_object_getn_value(object, name, dot_pos - name); if (json_value_get_type(temp_value) != JSONObject) { return JSONFailure; } temp_object = json_value_get_object(temp_value); return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value); } static void json_object_free(JSON_Object *object) { json_object_deinit(object, PARSON_TRUE, PARSON_TRUE); parson_free(object); } /* JSON Array */ static JSON_Array * json_array_make(JSON_Value *wrapping_value) { JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); if (new_array == NULL) { return NULL; } new_array->wrapping_value = wrapping_value; new_array->items = (JSON_Value**)NULL; new_array->capacity = 0; new_array->count = 0; return new_array; } static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { if (array->count >= array->capacity) { size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); if (json_array_resize(array, new_capacity) != JSONSuccess) { return JSONFailure; } } value->parent = json_array_get_wrapping_value(array); array->items[array->count] = value; array->count++; return JSONSuccess; } static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { JSON_Value **new_items = NULL; if (new_capacity == 0) { return JSONFailure; } new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); if (new_items == NULL) { return JSONFailure; } if (array->items != NULL && array->count > 0) { memcpy(new_items, array->items, array->count * sizeof(JSON_Value*)); } parson_free(array->items); array->items = new_items; array->capacity = new_capacity; return JSONSuccess; } static void json_array_free(JSON_Array *array) { size_t i; for (i = 0; i < array->count; i++) { json_value_free(array->items[i]); } parson_free(array->items); parson_free(array); } /* JSON Value */ static JSON_Value * json_value_init_string_no_copy(char *string, size_t length) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONString; new_value->value.string.chars = string; new_value->value.string.length = length; return new_value; } /* Parser */ static JSON_Status skip_quotes(const char **string) { if (**string != '\"') { return JSONFailure; } SKIP_CHAR(string); while (**string != '\"') { if (**string == '\0') { return JSONFailure; } else if (**string == '\\') { SKIP_CHAR(string); if (**string == '\0') { return JSONFailure; } } SKIP_CHAR(string); } SKIP_CHAR(string); return JSONSuccess; } static JSON_Status parse_utf16(const char **unprocessed, char **processed) { unsigned int cp, lead, trail; char *processed_ptr = *processed; const char *unprocessed_ptr = *unprocessed; JSON_Status status = JSONFailure; unprocessed_ptr++; /* skips u */ status = parse_utf16_hex(unprocessed_ptr, &cp); if (status != JSONSuccess) { return JSONFailure; } if (cp < 0x80) { processed_ptr[0] = (char)cp; /* 0xxxxxxx */ } else if (cp < 0x800) { processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 1; } else if (cp < 0xD800 || cp > 0xDFFF) { processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 2; } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ lead = cp; unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') { return JSONFailure; } status = parse_utf16_hex(unprocessed_ptr, &trail); if (status != JSONSuccess || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ return JSONFailure; } cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000; processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr += 3; } else { /* trail surrogate before lead surrogate */ return JSONFailure; } unprocessed_ptr += 3; *processed = processed_ptr; *unprocessed = unprocessed_ptr; return JSONSuccess; } /* Copies and processes passed string up to supplied length. Example: "\u006Corem ipsum" -> lorem ipsum */ static char* process_string(const char *input, size_t input_len, size_t *output_len) { const char *input_ptr = input; size_t initial_size = (input_len + 1) * sizeof(char); size_t final_size = 0; char *output = NULL, *output_ptr = NULL, *resized_output = NULL; output = (char*)parson_malloc(initial_size); if (output == NULL) { goto error; } output_ptr = output; while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < input_len) { if (*input_ptr == '\\') { input_ptr++; switch (*input_ptr) { case '\"': *output_ptr = '\"'; break; case '\\': *output_ptr = '\\'; break; case '/': *output_ptr = '/'; break; case 'b': *output_ptr = '\b'; break; case 'f': *output_ptr = '\f'; break; case 'n': *output_ptr = '\n'; break; case 'r': *output_ptr = '\r'; break; case 't': *output_ptr = '\t'; break; case 'u': if (parse_utf16(&input_ptr, &output_ptr) != JSONSuccess) { goto error; } break; default: goto error; } } else if ((unsigned char)*input_ptr < 0x20) { goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ } else { *output_ptr = *input_ptr; } output_ptr++; input_ptr++; } *output_ptr = '\0'; /* resize to new length */ final_size = (size_t)(output_ptr-output) + 1; /* todo: don't resize if final_size == initial_size */ resized_output = (char*)parson_malloc(final_size); if (resized_output == NULL) { goto error; } memcpy(resized_output, output, final_size); *output_len = final_size - 1; parson_free(output); return resized_output; error: parson_free(output); return NULL; } /* Return processed contents of a string between quotes and skips passed argument to a matching quote. */ static char * get_quoted_string(const char **string, size_t *output_string_len) { const char *string_start = *string; size_t input_string_len = 0; JSON_Status status = skip_quotes(string); if (status != JSONSuccess) { return NULL; } input_string_len = *string - string_start - 2; /* length without quotes */ return process_string(string_start + 1, input_string_len, output_string_len); } static JSON_Value * parse_value(const char **string, size_t nesting) { if (nesting > MAX_NESTING) { return NULL; } SKIP_WHITESPACES(string); switch (**string) { case '{': return parse_object_value(string, nesting + 1); case '[': return parse_array_value(string, nesting + 1); case '\"': return parse_string_value(string); case 'f': case 't': return parse_boolean_value(string); case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parse_number_value(string); case 'n': return parse_null_value(string); default: return NULL; } } static JSON_Value * parse_object_value(const char **string, size_t nesting) { JSON_Status status = JSONFailure; JSON_Value *output_value = NULL, *new_value = NULL; JSON_Object *output_object = NULL; char *new_key = NULL; output_value = json_value_init_object(); if (output_value == NULL) { return NULL; } if (**string != '{') { json_value_free(output_value); return NULL; } output_object = json_value_get_object(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == '}') { /* empty object */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { size_t key_len = 0; new_key = get_quoted_string(string, &key_len); /* We do not support key names with embedded \0 chars */ if (!new_key) { json_value_free(output_value); return NULL; } if (key_len != strlen(new_key)) { parson_free(new_key); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ':') { parson_free(new_key); json_value_free(output_value); return NULL; } SKIP_CHAR(string); new_value = parse_value(string, nesting); if (new_value == NULL) { parson_free(new_key); json_value_free(output_value); return NULL; } status = json_object_add(output_object, new_key, new_value); if (status != JSONSuccess) { parson_free(new_key); json_value_free(new_value); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ',') { break; } SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == '}') { break; } } SKIP_WHITESPACES(string); if (**string != '}') { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_array_value(const char **string, size_t nesting) { JSON_Value *output_value = NULL, *new_array_value = NULL; JSON_Array *output_array = NULL; output_value = json_value_init_array(); if (output_value == NULL) { return NULL; } if (**string != '[') { json_value_free(output_value); return NULL; } output_array = json_value_get_array(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == ']') { /* empty array */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { new_array_value = parse_value(string, nesting); if (new_array_value == NULL) { json_value_free(output_value); return NULL; } if (json_array_add(output_array, new_array_value) != JSONSuccess) { json_value_free(new_array_value); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ',') { break; } SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == ']') { break; } } SKIP_WHITESPACES(string); if (**string != ']' || /* Trim array after parsing is over */ json_array_resize(output_array, json_array_get_count(output_array)) != JSONSuccess) { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_string_value(const char **string) { JSON_Value *value = NULL; size_t new_string_len = 0; char *new_string = get_quoted_string(string, &new_string_len); if (new_string == NULL) { return NULL; } value = json_value_init_string_no_copy(new_string, new_string_len); if (value == NULL) { parson_free(new_string); return NULL; } return value; } static JSON_Value * parse_boolean_value(const char **string) { size_t true_token_size = SIZEOF_TOKEN("true"); size_t false_token_size = SIZEOF_TOKEN("false"); if (strncmp("true", *string, true_token_size) == 0) { *string += true_token_size; return json_value_init_boolean(1); } else if (strncmp("false", *string, false_token_size) == 0) { *string += false_token_size; return json_value_init_boolean(0); } return NULL; } static JSON_Value * parse_number_value(const char **string) { char *end; double number = 0; errno = 0; number = strtod(*string, &end); if (errno == ERANGE && (number <= -HUGE_VAL || number >= HUGE_VAL)) { return NULL; } if ((errno && errno != ERANGE) || !is_decimal(*string, end - *string)) { return NULL; } *string = end; return json_value_init_number(number); } static JSON_Value * parse_null_value(const char **string) { size_t token_size = SIZEOF_TOKEN("null"); if (strncmp("null", *string, token_size) == 0) { *string += token_size; return json_value_init_null(); } return NULL; } /* Serialization */ /* APPEND_STRING() is only called on string literals. It's a bit hacky because it makes plenty of assumptions about the external state and should eventually be tidied up into a function (same goes for APPEND_INDENT) */ #define APPEND_STRING(str) do {\ written = SIZEOF_TOKEN((str));\ if (buf != NULL) {\ memcpy(buf, (str), written);\ buf[written] = '\0';\ buf += written;\ }\ written_total += written;\ } while (0) #define APPEND_INDENT(level) do {\ int level_i = 0;\ for (level_i = 0; level_i < (level); level_i++) {\ APPEND_STRING(PARSON_INDENT_STR);\ }\ } while (0) static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, parson_bool_t is_pretty, char *num_buf) { const char *key = NULL, *string = NULL; JSON_Value *temp_value = NULL; JSON_Array *array = NULL; JSON_Object *object = NULL; size_t i = 0, count = 0; double num = 0.0; int written = -1, written_total = 0; size_t len = 0; switch (json_value_get_type(value)) { case JSONArray: array = json_value_get_array(value); count = json_array_get_count(array); APPEND_STRING("["); if (count > 0 && is_pretty) { APPEND_STRING("\n"); } for (i = 0; i < count; i++) { if (is_pretty) { APPEND_INDENT(level+1); } temp_value = json_array_get_value(array, i); written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; if (i < (count - 1)) { APPEND_STRING(","); } if (is_pretty) { APPEND_STRING("\n"); } } if (count > 0 && is_pretty) { APPEND_INDENT(level); } APPEND_STRING("]"); return written_total; case JSONObject: object = json_value_get_object(value); count = json_object_get_count(object); APPEND_STRING("{"); if (count > 0 && is_pretty) { APPEND_STRING("\n"); } for (i = 0; i < count; i++) { key = json_object_get_name(object, i); if (key == NULL) { return -1; } if (is_pretty) { APPEND_INDENT(level+1); } /* We do not support key names with embedded \0 chars */ written = json_serialize_string(key, strlen(key), buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; APPEND_STRING(":"); if (is_pretty) { APPEND_STRING(" "); } temp_value = json_object_get_value_at(object, i); written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; if (i < (count - 1)) { APPEND_STRING(","); } if (is_pretty) { APPEND_STRING("\n"); } } if (count > 0 && is_pretty) { APPEND_INDENT(level); } APPEND_STRING("}"); return written_total; case JSONString: string = json_value_get_string(value); if (string == NULL) { return -1; } len = json_value_get_string_len(value); written = json_serialize_string(string, len, buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; return written_total; case JSONBoolean: if (json_value_get_boolean(value)) { APPEND_STRING("true"); } else { APPEND_STRING("false"); } return written_total; case JSONNumber: num = json_value_get_number(value); if (buf != NULL) { num_buf = buf; } if (parson_number_serialization_function) { written = parson_number_serialization_function(num, num_buf); } else if (parson_float_format) { written = sprintf(num_buf, parson_float_format, num); } else { written = sprintf(num_buf, PARSON_DEFAULT_FLOAT_FORMAT, num); } if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; return written_total; case JSONNull: APPEND_STRING("null"); return written_total; case JSONError: return -1; default: return -1; } } static int json_serialize_string(const char *string, size_t len, char *buf) { size_t i = 0; char c = '\0'; int written = -1, written_total = 0; APPEND_STRING("\""); for (i = 0; i < len; i++) { c = string[i]; switch (c) { case '\"': APPEND_STRING("\\\""); break; case '\\': APPEND_STRING("\\\\"); break; case '\b': APPEND_STRING("\\b"); break; case '\f': APPEND_STRING("\\f"); break; case '\n': APPEND_STRING("\\n"); break; case '\r': APPEND_STRING("\\r"); break; case '\t': APPEND_STRING("\\t"); break; case '\x00': APPEND_STRING("\\u0000"); break; case '\x01': APPEND_STRING("\\u0001"); break; case '\x02': APPEND_STRING("\\u0002"); break; case '\x03': APPEND_STRING("\\u0003"); break; case '\x04': APPEND_STRING("\\u0004"); break; case '\x05': APPEND_STRING("\\u0005"); break; case '\x06': APPEND_STRING("\\u0006"); break; case '\x07': APPEND_STRING("\\u0007"); break; /* '\x08' duplicate: '\b' */ /* '\x09' duplicate: '\t' */ /* '\x0a' duplicate: '\n' */ case '\x0b': APPEND_STRING("\\u000b"); break; /* '\x0c' duplicate: '\f' */ /* '\x0d' duplicate: '\r' */ case '\x0e': APPEND_STRING("\\u000e"); break; case '\x0f': APPEND_STRING("\\u000f"); break; case '\x10': APPEND_STRING("\\u0010"); break; case '\x11': APPEND_STRING("\\u0011"); break; case '\x12': APPEND_STRING("\\u0012"); break; case '\x13': APPEND_STRING("\\u0013"); break; case '\x14': APPEND_STRING("\\u0014"); break; case '\x15': APPEND_STRING("\\u0015"); break; case '\x16': APPEND_STRING("\\u0016"); break; case '\x17': APPEND_STRING("\\u0017"); break; case '\x18': APPEND_STRING("\\u0018"); break; case '\x19': APPEND_STRING("\\u0019"); break; case '\x1a': APPEND_STRING("\\u001a"); break; case '\x1b': APPEND_STRING("\\u001b"); break; case '\x1c': APPEND_STRING("\\u001c"); break; case '\x1d': APPEND_STRING("\\u001d"); break; case '\x1e': APPEND_STRING("\\u001e"); break; case '\x1f': APPEND_STRING("\\u001f"); break; case '/': if (parson_escape_slashes) { APPEND_STRING("\\/"); /* to make json embeddable in xml\/html */ } else { APPEND_STRING("/"); } break; default: if (buf != NULL) { buf[0] = c; buf += 1; } written_total += 1; break; } } APPEND_STRING("\""); return written_total; } #undef APPEND_STRING #undef APPEND_INDENT /* Parser API */ JSON_Value * json_parse_file(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) { return NULL; } output_value = json_parse_string(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_file_with_comments(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) { return NULL; } output_value = json_parse_string_with_comments(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_string(const char *string) { if (string == NULL) { return NULL; } if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') { string = string + 3; /* Support for UTF-8 BOM */ } return parse_value((const char**)&string, 0); } JSON_Value * json_parse_string_with_comments(const char *string) { JSON_Value *result = NULL; char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; string_mutable_copy = parson_strdup(string); if (string_mutable_copy == NULL) { return NULL; } remove_comments(string_mutable_copy, "/*", "*/"); remove_comments(string_mutable_copy, "//", "\n"); string_mutable_copy_ptr = string_mutable_copy; result = parse_value((const char**)&string_mutable_copy_ptr, 0); parson_free(string_mutable_copy); return result; } /* JSON Object API */ JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { if (object == NULL || name == NULL) { return NULL; } return json_object_getn_value(object, name, strlen(name)); } const char * json_object_get_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_get_value(object, name)); } size_t json_object_get_string_len(const JSON_Object *object, const char *name) { return json_value_get_string_len(json_object_get_value(object, name)); } double json_object_get_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_get_value(object, name)); } JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_get_value(object, name)); } JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_get_value(object, name)); } int json_object_get_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_get_value(object, name)); } JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { const char *dot_position = strchr(name, '.'); if (!dot_position) { return json_object_get_value(object, name); } object = json_value_get_object(json_object_getn_value(object, name, dot_position - name)); return json_object_dotget_value(object, dot_position + 1); } const char * json_object_dotget_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_dotget_value(object, name)); } size_t json_object_dotget_string_len(const JSON_Object *object, const char *name) { return json_value_get_string_len(json_object_dotget_value(object, name)); } double json_object_dotget_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_dotget_value(object, name)); } JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_dotget_value(object, name)); } JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_dotget_value(object, name)); } int json_object_dotget_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_dotget_value(object, name)); } size_t json_object_get_count(const JSON_Object *object) { return object ? object->count : 0; } const char * json_object_get_name(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) { return NULL; } return object->names[index]; } JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) { return NULL; } return object->values[index]; } JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) { if (!object) { return NULL; } return object->wrapping_value; } int json_object_has_value (const JSON_Object *object, const char *name) { return json_object_get_value(object, name) != NULL; } int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_get_value(object, name); return val != NULL && json_value_get_type(val) == type; } int json_object_dothas_value (const JSON_Object *object, const char *name) { return json_object_dotget_value(object, name) != NULL; } int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_dotget_value(object, name); return val != NULL && json_value_get_type(val) == type; } /* JSON Array API */ JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { if (array == NULL || index >= json_array_get_count(array)) { return NULL; } return array->items[index]; } const char * json_array_get_string(const JSON_Array *array, size_t index) { return json_value_get_string(json_array_get_value(array, index)); } size_t json_array_get_string_len(const JSON_Array *array, size_t index) { return json_value_get_string_len(json_array_get_value(array, index)); } double json_array_get_number(const JSON_Array *array, size_t index) { return json_value_get_number(json_array_get_value(array, index)); } JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { return json_value_get_object(json_array_get_value(array, index)); } JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { return json_value_get_array(json_array_get_value(array, index)); } int json_array_get_boolean(const JSON_Array *array, size_t index) { return json_value_get_boolean(json_array_get_value(array, index)); } size_t json_array_get_count(const JSON_Array *array) { return array ? array->count : 0; } JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) { if (!array) { return NULL; } return array->wrapping_value; } /* JSON Value API */ JSON_Value_Type json_value_get_type(const JSON_Value *value) { return value ? value->type : JSONError; } JSON_Object * json_value_get_object(const JSON_Value *value) { return json_value_get_type(value) == JSONObject ? value->value.object : NULL; } JSON_Array * json_value_get_array(const JSON_Value *value) { return json_value_get_type(value) == JSONArray ? value->value.array : NULL; } static const JSON_String * json_value_get_string_desc(const JSON_Value *value) { return json_value_get_type(value) == JSONString ? &value->value.string : NULL; } const char * json_value_get_string(const JSON_Value *value) { const JSON_String *str = json_value_get_string_desc(value); return str ? str->chars : NULL; } size_t json_value_get_string_len(const JSON_Value *value) { const JSON_String *str = json_value_get_string_desc(value); return str ? str->length : 0; } double json_value_get_number(const JSON_Value *value) { return json_value_get_type(value) == JSONNumber ? value->value.number : 0; } int json_value_get_boolean(const JSON_Value *value) { return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; } JSON_Value * json_value_get_parent (const JSON_Value *value) { return value ? value->parent : NULL; } void json_value_free(JSON_Value *value) { switch (json_value_get_type(value)) { case JSONObject: json_object_free(value->value.object); break; case JSONString: parson_free(value->value.string.chars); break; case JSONArray: json_array_free(value->value.array); break; default: break; } parson_free(value); } JSON_Value * json_value_init_object(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONObject; new_value->value.object = json_object_make(new_value); if (!new_value->value.object) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_init_array(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONArray; new_value->value.array = json_array_make(new_value); if (!new_value->value.array) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_init_string(const char *string) { if (string == NULL) { return NULL; } return json_value_init_string_with_len(string, strlen(string)); } JSON_Value * json_value_init_string_with_len(const char *string, size_t length) { char *copy = NULL; JSON_Value *value; if (string == NULL) { return NULL; } if (!is_valid_utf8(string, length)) { return NULL; } copy = parson_strndup(string, length); if (copy == NULL) { return NULL; } value = json_value_init_string_no_copy(copy, length); if (value == NULL) { parson_free(copy); } return value; } JSON_Value * json_value_init_number(double number) { JSON_Value *new_value = NULL; if (IS_NUMBER_INVALID(number)) { return NULL; } new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (new_value == NULL) { return NULL; } new_value->parent = NULL; new_value->type = JSONNumber; new_value->value.number = number; return new_value; } JSON_Value * json_value_init_boolean(int boolean) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONBoolean; new_value->value.boolean = boolean ? 1 : 0; return new_value; } JSON_Value * json_value_init_null(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONNull; return new_value; } JSON_Value * json_value_deep_copy(const JSON_Value *value) { size_t i = 0; JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; const JSON_String *temp_string = NULL; const char *temp_key = NULL; char *temp_string_copy = NULL; JSON_Array *temp_array = NULL, *temp_array_copy = NULL; JSON_Object *temp_object = NULL, *temp_object_copy = NULL; JSON_Status res = JSONFailure; char *key_copy = NULL; switch (json_value_get_type(value)) { case JSONArray: temp_array = json_value_get_array(value); return_value = json_value_init_array(); if (return_value == NULL) { return NULL; } temp_array_copy = json_value_get_array(return_value); for (i = 0; i < json_array_get_count(temp_array); i++) { temp_value = json_array_get_value(temp_array, i); temp_value_copy = json_value_deep_copy(temp_value); if (temp_value_copy == NULL) { json_value_free(return_value); return NULL; } if (json_array_add(temp_array_copy, temp_value_copy) != JSONSuccess) { json_value_free(return_value); json_value_free(temp_value_copy); return NULL; } } return return_value; case JSONObject: temp_object = json_value_get_object(value); return_value = json_value_init_object(); if (!return_value) { return NULL; } temp_object_copy = json_value_get_object(return_value); for (i = 0; i < json_object_get_count(temp_object); i++) { temp_key = json_object_get_name(temp_object, i); temp_value = json_object_get_value(temp_object, temp_key); temp_value_copy = json_value_deep_copy(temp_value); if (!temp_value_copy) { json_value_free(return_value); return NULL; } key_copy = parson_strdup(temp_key); if (!key_copy) { json_value_free(temp_value_copy); json_value_free(return_value); return NULL; } res = json_object_add(temp_object_copy, key_copy, temp_value_copy); if (res != JSONSuccess) { parson_free(key_copy); json_value_free(temp_value_copy); json_value_free(return_value); return NULL; } } return return_value; case JSONBoolean: return json_value_init_boolean(json_value_get_boolean(value)); case JSONNumber: return json_value_init_number(json_value_get_number(value)); case JSONString: temp_string = json_value_get_string_desc(value); if (temp_string == NULL) { return NULL; } temp_string_copy = parson_strndup(temp_string->chars, temp_string->length); if (temp_string_copy == NULL) { return NULL; } return_value = json_value_init_string_no_copy(temp_string_copy, temp_string->length); if (return_value == NULL) { parson_free(temp_string_copy); } return return_value; case JSONNull: return json_value_init_null(); case JSONError: return NULL; default: return NULL; } } size_t json_serialization_size(const JSON_Value *value) { char num_buf[PARSON_NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, PARSON_FALSE, num_buf); return res < 0 ? 0 : (size_t)(res) + 1; } JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { return JSONFailure; } written = json_serialize_to_buffer_r(value, buf, 0, PARSON_FALSE, NULL); if (written < 0) { return JSONFailure; } return JSONSuccess; } JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string(value); if (serialized_string == NULL) { return JSONFailure; } fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) { return_code = JSONFailure; } if (fclose(fp) == EOF) { return_code = JSONFailure; } json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size(value); char *buf = NULL; if (buf_size_bytes == 0) { return NULL; } buf = (char*)parson_malloc(buf_size_bytes); if (buf == NULL) { return NULL; } serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); if (serialization_result != JSONSuccess) { json_free_serialized_string(buf); return NULL; } return buf; } size_t json_serialization_size_pretty(const JSON_Value *value) { char num_buf[PARSON_NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, PARSON_TRUE, num_buf); return res < 0 ? 0 : (size_t)(res) + 1; } JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size_pretty(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { return JSONFailure; } written = json_serialize_to_buffer_r(value, buf, 0, PARSON_TRUE, NULL); if (written < 0) { return JSONFailure; } return JSONSuccess; } JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string_pretty(value); if (serialized_string == NULL) { return JSONFailure; } fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) { return_code = JSONFailure; } if (fclose(fp) == EOF) { return_code = JSONFailure; } json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string_pretty(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size_pretty(value); char *buf = NULL; if (buf_size_bytes == 0) { return NULL; } buf = (char*)parson_malloc(buf_size_bytes); if (buf == NULL) { return NULL; } serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); if (serialization_result != JSONSuccess) { json_free_serialized_string(buf); return NULL; } return buf; } void json_free_serialized_string(char *string) { parson_free(string); } JSON_Status json_array_remove(JSON_Array *array, size_t ix) { size_t to_move_bytes = 0; if (array == NULL || ix >= json_array_get_count(array)) { return JSONFailure; } json_value_free(json_array_get_value(array, ix)); to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value*); memmove(array->items + ix, array->items + ix + 1, to_move_bytes); array->count -= 1; return JSONSuccess; } JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) { return JSONFailure; } json_value_free(json_array_get_value(array, ix)); value->parent = json_array_get_wrapping_value(array); array->items[ix] = value; return JSONSuccess; } JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_string_with_len(JSON_Array *array, size_t i, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_clear(JSON_Array *array) { size_t i = 0; if (array == NULL) { return JSONFailure; } for (i = 0; i < json_array_get_count(array); i++) { json_value_free(json_array_get_value(array, i)); } array->count = 0; return JSONSuccess; } JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL) { return JSONFailure; } return json_array_add(array, value); } JSON_Status json_array_append_string(JSON_Array *array, const char *string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_string_with_len(JSON_Array *array, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_number(JSON_Array *array, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_null(JSON_Array *array) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; size_t item_ix = 0; JSON_Value *old_value = NULL; char *key_copy = NULL; if (!object || !name || !value || value->parent) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (found) { item_ix = object->cells[cell_ix]; old_value = object->values[item_ix]; json_value_free(old_value); object->values[item_ix] = value; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } if (object->count >= object->item_capacity) { JSON_Status res = json_object_grow_and_rehash(object); if (res != JSONSuccess) { return JSONFailure; } cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); } key_copy = parson_strdup(name); if (!key_copy) { return JSONFailure; } object->names[object->count] = key_copy; object->cells[cell_ix] = object->count; object->values[object->count] = value; object->cell_ixs[object->count] = cell_ix; object->hashes[object->count] = hash; object->count++; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { JSON_Value *value = json_value_init_string(string); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { JSON_Value *value = json_value_init_number(number); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_null(JSON_Object *object, const char *name) { JSON_Value *value = json_value_init_null(); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { const char *dot_pos = NULL; JSON_Value *temp_value = NULL, *new_value = NULL; JSON_Object *temp_object = NULL, *new_object = NULL; JSON_Status status = JSONFailure; size_t name_len = 0; char *name_copy = NULL; if (object == NULL || name == NULL || value == NULL) { return JSONFailure; } dot_pos = strchr(name, '.'); if (dot_pos == NULL) { return json_object_set_value(object, name, value); } name_len = dot_pos - name; temp_value = json_object_getn_value(object, name, name_len); if (temp_value) { /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be changed at this point) */ if (json_value_get_type(temp_value) != JSONObject) { return JSONFailure; } temp_object = json_value_get_object(temp_value); return json_object_dotset_value(temp_object, dot_pos + 1, value); } new_value = json_value_init_object(); if (new_value == NULL) { return JSONFailure; } new_object = json_value_get_object(new_value); status = json_object_dotset_value(new_object, dot_pos + 1, value); if (status != JSONSuccess) { json_value_free(new_value); return JSONFailure; } name_copy = parson_strndup(name, name_len); if (!name_copy) { json_object_dotremove_internal(new_object, dot_pos + 1, 0); json_value_free(new_value); return JSONFailure; } status = json_object_add(object, name_copy, new_value); if (status != JSONSuccess) { parson_free(name_copy); json_object_dotremove_internal(new_object, dot_pos + 1, 0); json_value_free(new_value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_remove(JSON_Object *object, const char *name) { return json_object_remove_internal(object, name, PARSON_TRUE); } JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { return json_object_dotremove_internal(object, name, PARSON_TRUE); } JSON_Status json_object_clear(JSON_Object *object) { size_t i = 0; if (object == NULL) { return JSONFailure; } for (i = 0; i < json_object_get_count(object); i++) { parson_free(object->names[i]); object->names[i] = NULL; json_value_free(object->values[i]); object->values[i] = NULL; } object->count = 0; for (i = 0; i < object->cell_capacity; i++) { object->cells[i] = OBJECT_INVALID_IX; } return JSONSuccess; } JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { JSON_Value *temp_schema_value = NULL, *temp_value = NULL; JSON_Array *schema_array = NULL, *value_array = NULL; JSON_Object *schema_object = NULL, *value_object = NULL; JSON_Value_Type schema_type = JSONError, value_type = JSONError; const char *key = NULL; size_t i = 0, count = 0; if (schema == NULL || value == NULL) { return JSONFailure; } schema_type = json_value_get_type(schema); value_type = json_value_get_type(value); if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */ return JSONFailure; } switch (schema_type) { case JSONArray: schema_array = json_value_get_array(schema); value_array = json_value_get_array(value); count = json_array_get_count(schema_array); if (count == 0) { return JSONSuccess; /* Empty array allows all types */ } /* Get first value from array, rest is ignored */ temp_schema_value = json_array_get_value(schema_array, 0); for (i = 0; i < json_array_get_count(value_array); i++) { temp_value = json_array_get_value(value_array, i); if (json_validate(temp_schema_value, temp_value) != JSONSuccess) { return JSONFailure; } } return JSONSuccess; case JSONObject: schema_object = json_value_get_object(schema); value_object = json_value_get_object(value); count = json_object_get_count(schema_object); if (count == 0) { return JSONSuccess; /* Empty object allows all objects */ } else if (json_object_get_count(value_object) < count) { return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ } for (i = 0; i < count; i++) { key = json_object_get_name(schema_object, i); temp_schema_value = json_object_get_value(schema_object, key); temp_value = json_object_get_value(value_object, key); if (temp_value == NULL) { return JSONFailure; } if (json_validate(temp_schema_value, temp_value) != JSONSuccess) { return JSONFailure; } } return JSONSuccess; case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: return JSONSuccess; /* equality already tested before switch */ case JSONError: default: return JSONFailure; } } int json_value_equals(const JSON_Value *a, const JSON_Value *b) { JSON_Object *a_object = NULL, *b_object = NULL; JSON_Array *a_array = NULL, *b_array = NULL; const JSON_String *a_string = NULL, *b_string = NULL; const char *key = NULL; size_t a_count = 0, b_count = 0, i = 0; JSON_Value_Type a_type, b_type; a_type = json_value_get_type(a); b_type = json_value_get_type(b); if (a_type != b_type) { return PARSON_FALSE; } switch (a_type) { case JSONArray: a_array = json_value_get_array(a); b_array = json_value_get_array(b); a_count = json_array_get_count(a_array); b_count = json_array_get_count(b_array); if (a_count != b_count) { return PARSON_FALSE; } for (i = 0; i < a_count; i++) { if (!json_value_equals(json_array_get_value(a_array, i), json_array_get_value(b_array, i))) { return PARSON_FALSE; } } return PARSON_TRUE; case JSONObject: a_object = json_value_get_object(a); b_object = json_value_get_object(b); a_count = json_object_get_count(a_object); b_count = json_object_get_count(b_object); if (a_count != b_count) { return PARSON_FALSE; } for (i = 0; i < a_count; i++) { key = json_object_get_name(a_object, i); if (!json_value_equals(json_object_get_value(a_object, key), json_object_get_value(b_object, key))) { return PARSON_FALSE; } } return PARSON_TRUE; case JSONString: a_string = json_value_get_string_desc(a); b_string = json_value_get_string_desc(b); if (a_string == NULL || b_string == NULL) { return PARSON_FALSE; /* shouldn't happen */ } return a_string->length == b_string->length && memcmp(a_string->chars, b_string->chars, a_string->length) == 0; case JSONBoolean: return json_value_get_boolean(a) == json_value_get_boolean(b); case JSONNumber: return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ case JSONError: return PARSON_TRUE; case JSONNull: return PARSON_TRUE; default: return PARSON_TRUE; } } JSON_Value_Type json_type(const JSON_Value *value) { return json_value_get_type(value); } JSON_Object * json_object (const JSON_Value *value) { return json_value_get_object(value); } JSON_Array * json_array(const JSON_Value *value) { return json_value_get_array(value); } const char * json_string(const JSON_Value *value) { return json_value_get_string(value); } size_t json_string_len(const JSON_Value *value) { return json_value_get_string_len(value); } double json_number(const JSON_Value *value) { return json_value_get_number(value); } int json_boolean(const JSON_Value *value) { return json_value_get_boolean(value); } void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { parson_malloc = malloc_fun; parson_free = free_fun; } void json_set_escape_slashes(int escape_slashes) { parson_escape_slashes = escape_slashes; } void json_set_float_serialization_format(const char *format) { if (parson_float_format) { parson_free(parson_float_format); parson_float_format = NULL; } if (!format) { parson_float_format = NULL; return; } parson_float_format = parson_strdup(format); } void json_set_number_serialization_function(JSON_Number_Serialization_Function func) { parson_number_serialization_function = func; } slirp4netns-1.2.1/vendor/parson/parson.h000066400000000000000000000336101447011446400202620ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT Parson 1.5.2 (https://github.com/kgabis/parson) Copyright (c) 2012 - 2023 Krzysztof Gabis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef parson_parson_h #define parson_parson_h #ifdef __cplusplus extern "C" { #endif #if 0 } /* unconfuse xcode */ #endif #define PARSON_VERSION_MAJOR 1 #define PARSON_VERSION_MINOR 5 #define PARSON_VERSION_PATCH 2 #define PARSON_VERSION_STRING "1.5.2" #include /* size_t */ /* Types and enums */ typedef struct json_object_t JSON_Object; typedef struct json_array_t JSON_Array; typedef struct json_value_t JSON_Value; enum json_value_type { JSONError = -1, JSONNull = 1, JSONString = 2, JSONNumber = 3, JSONObject = 4, JSONArray = 5, JSONBoolean = 6 }; typedef int JSON_Value_Type; enum json_result_t { JSONSuccess = 0, JSONFailure = -1 }; typedef int JSON_Status; typedef void * (*JSON_Malloc_Function)(size_t); typedef void (*JSON_Free_Function)(void *); /* A function used for serializing numbers (see json_set_number_serialization_function). If 'buf' is null then it should return number of bytes that would've been written (but not more than PARSON_NUM_BUF_SIZE). */ typedef int (*JSON_Number_Serialization_Function)(double num, char *buf); /* Call only once, before calling any other function from parson API. If not called, malloc and free from stdlib will be used for all allocations */ void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); /* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped. This function sets a global setting and is not thread safe. */ void json_set_escape_slashes(int escape_slashes); /* Sets float format used for serialization of numbers. Make sure it can't serialize to a string longer than PARSON_NUM_BUF_SIZE. If format is null then the default format is used. */ void json_set_float_serialization_format(const char *format); /* Sets a function that will be used for serialization of numbers. If function is null then the default serialization function is used. */ void json_set_number_serialization_function(JSON_Number_Serialization_Function fun); /* Parses first JSON value in a file, returns NULL in case of error */ JSON_Value * json_parse_file(const char *filename); /* Parses first JSON value in a file and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value * json_parse_file_with_comments(const char *filename); /* Parses first JSON value in a string, returns NULL in case of error */ JSON_Value * json_parse_string(const char *string); /* Parses first JSON value in a string and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value * json_parse_string_with_comments(const char *string); /* Serialization */ size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); char * json_serialize_to_string(const JSON_Value *value); /* Pretty serialization */ size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); char * json_serialize_to_string_pretty(const JSON_Value *value); void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ /* Comparing */ int json_value_equals(const JSON_Value *a, const JSON_Value *b); /* Validation This is *NOT* JSON Schema. It validates json by checking if object have identically named fields with matching types. For example schema {"name":"", "age":0} will validate {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. In case of arrays, only first value in schema is checked against all values in tested array. Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, null validates values of every type. */ JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); /* * JSON Object */ JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); const char * json_object_get_string (const JSON_Object *object, const char *name); size_t json_object_get_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* dotget functions enable addressing values with dot notation in nested objects, just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). Because valid names in JSON can contain dots, some values may be inaccessible this way. */ JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); const char * json_object_dotget_string (const JSON_Object *object, const char *name); size_t json_object_dotget_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* Functions to get available names */ size_t json_object_get_count (const JSON_Object *object); const char * json_object_get_name (const JSON_Object *object, size_t index); JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index); JSON_Value * json_object_get_wrapping_value(const JSON_Object *object); /* Functions to check if object has a value with a specific name. Returned value is 1 if object has * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ int json_object_has_value (const JSON_Object *object, const char *name); int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); int json_object_dothas_value (const JSON_Object *object, const char *name); int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); /* Creates new name-value pair or frees and replaces old value with a new one. * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_set_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_set_null(JSON_Object *object, const char *name); /* Works like dotget functions, but creates whole hierarchy if necessary. * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_dotset_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); /* Frees and removes name-value pair */ JSON_Status json_object_remove(JSON_Object *object, const char *name); /* Works like dotget function, but removes name-value pair only on exact match. */ JSON_Status json_object_dotremove(JSON_Object *object, const char *key); /* Removes all name-value pairs in object */ JSON_Status json_object_clear(JSON_Object *object); /* *JSON Array */ JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); const char * json_array_get_string (const JSON_Array *array, size_t index); size_t json_array_get_string_len(const JSON_Array *array, size_t index); /* doesn't account for last null character */ JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ size_t json_array_get_count (const JSON_Array *array); JSON_Value * json_array_get_wrapping_value(const JSON_Array *array); /* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. * Order of values in array may change during execution. */ JSON_Status json_array_remove(JSON_Array *array, size_t i); /* Frees and removes from array value at given index and replaces it with given one. * Does nothing and returns JSONFailure if index doesn't exist. * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); JSON_Status json_array_replace_string_with_len(JSON_Array *array, size_t i, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); JSON_Status json_array_replace_null(JSON_Array *array, size_t i); /* Frees and removes all values from array */ JSON_Status json_array_clear(JSON_Array *array); /* Appends new value at the end of array. * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); JSON_Status json_array_append_string(JSON_Array *array, const char *string); JSON_Status json_array_append_string_with_len(JSON_Array *array, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_array_append_number(JSON_Array *array, double number); JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); JSON_Status json_array_append_null(JSON_Array *array); /* *JSON Value */ JSON_Value * json_value_init_object (void); JSON_Value * json_value_init_array (void); JSON_Value * json_value_init_string (const char *string); /* copies passed string */ JSON_Value * json_value_init_string_with_len(const char *string, size_t length); /* copies passed string, length shouldn't include last null character */ JSON_Value * json_value_init_number (double number); JSON_Value * json_value_init_boolean(int boolean); JSON_Value * json_value_init_null (void); JSON_Value * json_value_deep_copy (const JSON_Value *value); void json_value_free (JSON_Value *value); JSON_Value_Type json_value_get_type (const JSON_Value *value); JSON_Object * json_value_get_object (const JSON_Value *value); JSON_Array * json_value_get_array (const JSON_Value *value); const char * json_value_get_string (const JSON_Value *value); size_t json_value_get_string_len(const JSON_Value *value); /* doesn't account for last null character */ double json_value_get_number (const JSON_Value *value); int json_value_get_boolean(const JSON_Value *value); JSON_Value * json_value_get_parent (const JSON_Value *value); /* Same as above, but shorter */ JSON_Value_Type json_type (const JSON_Value *value); JSON_Object * json_object (const JSON_Value *value); JSON_Array * json_array (const JSON_Value *value); const char * json_string (const JSON_Value *value); size_t json_string_len(const JSON_Value *value); /* doesn't account for last null character */ double json_number (const JSON_Value *value); int json_boolean(const JSON_Value *value); #ifdef __cplusplus } #endif #endif