diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..a3d28d4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,111 @@ +image: "git.mesalab.cn:7443/mesa_platform/build-env:master" +variables: + GIT_STRATEGY: "clone" + BUILD_PADDING_PREFIX: /tmp/padding_for_CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX/ + INSTALL_PREFIX: "/opt/tsg/packetadapter" + TESTING_VERSION_BUILD: 0 + +stages: +- build + +.build_by_travis: + before_script: + - mkdir -p $BUILD_PADDING_PREFIX/$CI_PROJECT_NAMESPACE/ + - ln -s $CI_PROJECT_DIR $BUILD_PADDING_PREFIX/$CI_PROJECT_PATH + - cd $BUILD_PADDING_PREFIX/$CI_PROJECT_PATH + - chmod +x ./ci/travis.sh + script: + - yum makecache + - ./ci/travis.sh + tags: + - share + +branch_build_debug: + stage: build + extends: .build_by_travis + variables: + BUILD_TYPE: Debug + except: + - /^develop-.*$/i + - /^release-.*$/i + - tags + +branch_build_release: + stage: build + variables: + BUILD_TYPE: RelWithDebInfo + extends: .build_by_travis + except: + - /^develop-.*$/i + - /^release-.*$/i + - tags + +develop_build_debug: + stage: build + extends: .build_by_travis + variables: + TESTING_VERSION_BUILD: 1 + UPLOAD_SYMBOL_FILES: 1 + BUILD_TYPE: Debug + ASAN_OPTION: ADDRESS + PACKAGE: 1 + PULP3_REPO_NAME: tsg-testing-x86_64.el7 + PULP3_DIST_NAME: tsg-testing-x86_64.el7 + artifacts: + name: "packetadapter-develop-$CI_COMMIT_REF_NAME-debug" + paths: + - build/*.rpm + only: + - /^develop-.*$/i + - /^release-.*$/i + +develop_build_release: + stage: build + extends: .build_by_travis + variables: + TESTING_VERSION_BUILD: 1 + UPLOAD_SYMBOL_FILES: 1 + # ASAN_OPTION: ADDRESS + BUILD_TYPE: RelWithDebInfo + PACKAGE: 1 + PULP3_REPO_NAME: tsg-testing-x86_64.el7 + PULP3_DIST_NAME: tsg-testing-x86_64.el7 + artifacts: + name: "packetadapter-develop-$CI_COMMIT_REF_NAME-release" + paths: + - build/*.rpm + only: + - /^develop-.*$/i + - /^release-.*$/i + +release_build_debug: + stage: build + variables: + UPLOAD_SYMBOL_FILES: 1 + BUILD_TYPE: Debug + PACKAGE: 1 + PULP3_REPO_NAME: tsg-stable-x86_64.el7 + PULP3_DIST_NAME: tsg-stable-x86_64.el7 + extends: .build_by_travis + artifacts: + name: "packetadapter-install-$CI_COMMIT_REF_NAME-debug" + paths: + - build/*.rpm + only: + - tags + +release_build_release: + stage: build + variables: + BUILD_TYPE: RelWithDebInfo + UPLOAD_SYMBOL_FILES: 1 + PACKAGE: 1 + PULP3_REPO_NAME: tsg-stable-x86_64.el7 + PULP3_DIST_NAME: tsg-stable-x86_64.el7 + extends: .build_by_travis + artifacts: + name: "packetadapter-install-$CI_COMMIT_REF_NAME-release" + paths: + - build/*.rpm + only: + - tags diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cadc605 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.5) +project(packetadapter) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +include(Version) +include(Package) + +add_definitions(-D_GNU_SOURCE) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 11) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo) +endif() + +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set (CMAKE_INSTALL_PREFIX "/opt/tsg/packetadapter" CACHE PATH "default install path" FORCE ) +endif() + +# Global compile options +option(ENABLE_PIC "Generate position independent code (necessary for shared libraries)" TRUE) +option(ENABLE_WARNING_ALL "Enable all optional warnings which are desirable for normal code" TRUE) + +if(NOT ASAN_OPTION) + option(ENABLE_SANITIZE_ADDRESS "Enable AddressSanitizer" FALSE) +else() + option(ENABLE_SANITIZE_ADDRESS "Enable AddressSanitizer" TRUE) +endif() + +option(ENABLE_SANITIZE_THREAD "Enable ThreadSanitizer" FALSE) + +if(ENABLE_PIC) + set(CMAKE_POSITION_INDEPENDENT_CODE 1) +endif() + +if(ENABLE_WARNING_ALL) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +if(ENABLE_SANITIZE_ADDRESS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") +elseif(ENABLE_SANITIZE_THREAD) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fno-omit-frame-pointer") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") +endif() + +if(ENABLE_SANITIZE_ADDRESS AND ENABLE_SANITIZE_THREAD) + message(WARNING "Both ENABLE_SANITIZE_ADDRESS and ENABLE_SANITIZE_THREAD set, only ENABLE_SANITIZE_ADDRESS effected.") +endif() + +add_custom_target("install-program" COMMAND ${CMAKE_COMMAND} ARGS -DCOMPONENT=Program -P cmake_install.cmake) +add_custom_target("install-profile" COMMAND ${CMAKE_COMMAND} ARGS -DCOMPONENT=Profile -P cmake_install.cmake) + +enable_testing() +add_subdirectory(common) +add_subdirectory(platform) +add_subdirectory(script) diff --git a/Makefile b/Makefile deleted file mode 100644 index 0a179c8..0000000 --- a/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - gcc -Wall -o nfq_filter_gtp *.c -lnfnetlink -lnetfilter_queue \ No newline at end of file diff --git a/README.md b/README.md index 0fdb4cf..124065c 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,73 @@ -## 功能 +# PacketAdapter -- Packet Filtering & Adaptation tools -并联环境下实现GTP路由封堵功能。 +## 简介 -**实现** +PacketAdapter 是一个基于 iptables 的数据包过滤,转换/适配工具。 +PacketAdapter 并不会凭空产生数据包,而是将 iptables 过滤的数据包重新转换/适配后再回注到网络中。 +可用于 Overlay networks 中 Packet encapsulation and decapsulation,屏蔽端到端协议层之间的差异。 -将 “=> MAC => IPv4/IPv6 => UDP => GTP1 => IPv4/IPv6 => TCP/UDP“ 中的 "IPv4/IPv6 => UDP => GTP1" 协议层剥离。 +## 应用 -- 实现 GTP Overlay 数据包的解封装 -**例子** +PacketAdapter 通过 iptables 将 Firewall 发送的 GTP RST 包进行过滤,然后将 GTP 数据解封装后回注到网络中。 +例如:将 “MAC/IPv4 or IPv6/UDP/GTP1/IPv4 or IPv6/TCP or UDP“ 中的 "/IPv4 or IPv6/UDP/GTP1" 协议层剥离。 -原数据包为: - -``` shel -Frame 1: 90 bytes on wire (720 bits), 90 bytes captured (720 bits) -Ethernet II, Src: JuniperN_4d:d3:51 (08:81:f4:4d:d3:51), Dst: c8:67:d9:18:80:c3 (c8:67:d9:18:80:c3) -Internet Protocol Version 4, Src: 10.166.20.10, Dst: 10.2.3.35 -User Datagram Protocol, Src Port: 2152, Dst Port: 2152 -GPRS Tunneling Protocol -Internet Protocol Version 4, Src: 10.58.121.62, Dst: 217.76.78.112 -Transmission Control Protocol, Src Port: 52144, Dst Port: 443, Seq: 1, Ack: 1, Len: 0 +``` ++-----------+ +-----------+ +| TCP/UDP | | TCP/UDP | ++-----------+ +-----------+ +| IPv4/IPv6 | | IPv4/IPv6 | ++-----------+ +-----------+ +| GTP1 | | | ++-----------+ | | +| UDP | ==> | | ++-----------+ | | +| IPv4/IPv6 | | | ++-----------+ | | +| MAC | | MAC | ++-----------+ +-----------+ ``` -经过 NFQ 过滤后变为: +注意: +* /MAC/IPv6 的 first next header 必须为 UDP。 +* 目前不支持 GTP 扩展头。 -``` shel -Frame 1: 90 bytes on wire (720 bits), 90 bytes captured (720 bits) -Ethernet II, Src: JuniperN_4d:d3:51 (08:81:f4:4d:d3:51), Dst: c8:67:d9:18:80:c3 (c8:67:d9:18:80:c3) -Internet Protocol Version 4, Src: 10.58.121.62, Dst: 217.76.78.112 -Transmission Control Protocol, Src Port: 52144, Dst Port: 443, Seq: 1, Ack: 1, Len: 0 -``` - -## 构造测试环境 +## 运行环境 ``` shell +# yum install --downloadonly --downloaddir=./ libnetfilter_queue.x86_64 +# yum install --downloadonly --downloaddir=./ libnetfilter_queue-devel.x86_64 +# 安装 libnetfilter_queue +yum install -y libnetfilter_queue + # 清空 iptables -iptables -F -t nat -iptables -F -t filter -iptables -F -t mangle -iptables -F -t raw +iptables -F -t nat +iptables -F -t filter +iptables -F -t mangle +iptables -F -t raw + ip6tables -F -t nat ip6tables -F -t filter ip6tables -F -t mangle ip6tables -F -t raw # 增加 iptables -/usr/sbin/iptables -A OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 +/usr/sbin/iptables -A OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 /usr/sbin/ip6tables -A OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 # 删除 iptables -/usr/sbin/iptables -D OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 +/usr/sbin/iptables -D OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 /usr/sbin/ip6tables -D OUTPUT -o eno2 -p udp --dport 2152 -j NFQUEUE --queue-num 1 # 调试 iptables -/usr/sbin/iptables -A OUTPUT -o eno2 -j LOG -/usr/sbin/ip6tables -A OUTPUT -o eno2 -j LOG +# /usr/sbin/iptables -A OUTPUT -o eno2 -j LOG +# /usr/sbin/ip6tables -A OUTPUT -o eno2 -j LOG -# make -yum install -y libnetfilter_queue-devel -make -./nfq_filter_gtp +# 启动服务 +systemctl enable nfq_filter_gtp +systemctl start nfq_filter_gtp ``` -## 局限 - -/MAC/IPv6 的 first next header 必须为 UDP,否则跳过。 - ## TODO -// support service -// support filestat +* support service +* support filestat \ No newline at end of file diff --git a/autorevision.sh b/autorevision.sh new file mode 100644 index 0000000..595690b --- /dev/null +++ b/autorevision.sh @@ -0,0 +1,1268 @@ +#!/bin/sh + +# Copyright (c) 2012 - 2016 dak180 and contributors. See +# https://opensource.org/licenses/mit-license.php or the included +# COPYING.md for licence terms. +# +# autorevision - extracts metadata about the head version from your +# repository. + +# Usage message. +arUsage() { + cat > "/dev/stderr" << EOF +usage: autorevision {-t output-type | -s symbol} [-o cache-file [-f] ] [-V] + Options include: + -t output-type = specify output type + -s symbol = specify symbol output + -o cache-file = specify cache file location + -f = force the use of cache data + -U = check for untracked files in svn + -V = emit version and exit + -? = help message + +The following are valid output types: + clojure = clojure file + c = C/C++ file + h = Header for use with c/c++ + hpp = Alternate C++ header strings with namespace + ini = INI file + java = Java file + javaprop = Java properties file + js = javascript file + json = JSON file + lua = Lua file + m4 = m4 file + matlab = matlab file + octave = octave file + php = PHP file + pl = Perl file + py = Python file + rpm = rpm file + scheme = scheme file + sh = Bash sytax + swift = Swift file + tex = (La)TeX file + xcode = Header useful for populating info.plist files + cmake = CMake file + + +The following are valid symbols: + VCS_TYPE + VCS_BASENAME + VCS_UUID + VCS_NUM + VCS_DATE + VCS_BRANCH + VCS_TAG + VCS_TICK + VCS_EXTRA + VCS_FULL_HASH + VCS_SHORT_HASH + VCS_WC_MODIFIED + VCS_ACTION_STAMP +EOF + exit 1 +} + +# Config +ARVERSION="&&ARVERSION&&" +TARGETFILE="/dev/stdout" +while getopts ":t:o:s:VfU" OPTION; do + case "${OPTION}" in + t) + AFILETYPE="${OPTARG}" + ;; + o) + CACHEFILE="${OPTARG}" + ;; + f) + CACHEFORCE="1" + ;; + s) + VAROUT="${OPTARG}" + ;; + U) + UNTRACKEDFILES="1" + ;; + V) + echo "autorevision ${ARVERSION}" + exit 0 + ;; + ?) + # If an unknown flag is used (or -?): + arUsage + ;; + esac +done + +if [ ! -z "${VAROUT}" ] && [ ! -z "${AFILETYPE}" ]; then + # If both -s and -t are specified: + echo "error: Improper argument combination." 1>&2 + exit 1 +elif [ -z "${VAROUT}" ] && [ -z "${AFILETYPE}" ]; then + # If neither -s or -t are specified: + arUsage +elif [ -z "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then + # If -f is specified without -o: + arUsage +elif [ ! -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then + # If we are forced to use the cache but it does not exist. + echo "error: Cache forced but no cache found." 1>&2 + exit 1 +fi + +# Make sure that the path we are given is one we can source +# (dash, we are looking at you). +if [ ! -z "${CACHEFILE}" ] && ! echo "${CACHEFILE}" | grep -q '^\.*/'; then + CACHEFILE="./${CACHEFILE}" +fi + +GENERATED_HEADER="Generated by autorevision - do not hand-hack!" + +# Functions to extract data from different repo types. +# For git repos +# shellcheck disable=SC2039,SC2164,SC2155 +gitRepo() { + local oldPath="${PWD}" + + cd "$(git rev-parse --show-toplevel)" + + VCS_TYPE="git" + + VCS_BASENAME="$(basename "${PWD}")" + + VCS_UUID="$(git rev-list --max-parents=0 --date-order --reverse HEAD 2>/dev/null | sed -n 1p)" + if [ -z "${VCS_UUID}" ]; then + VCS_UUID="$(git rev-list --topo-order HEAD | tail -n 1)" + fi + + # Is the working copy clean? + test -z "$(git status --untracked-files=normal --porcelain)" + VCS_WC_MODIFIED="${?}" + + # Enumeration of changesets + VCS_NUM="$(git rev-list --count HEAD 2>/dev/null)" + if [ -z "${VCS_NUM}" ]; then + echo "warning: Counting the number of revisions may be slower due to an outdated git version less than 1.7.2.3. If something breaks, please update it." 1>&2 + VCS_NUM="$(git rev-list HEAD | wc -l)" + fi + + # This may be a git-svn remote. If so, report the Subversion revision. + if [ -z "$(git config svn-remote.svn.url 2>/dev/null)" ]; then + # The full revision hash + VCS_FULL_HASH="$(git rev-parse HEAD)" + + # The short hash + VCS_SHORT_HASH="$(echo "${VCS_FULL_HASH}" | cut -b 1-7)" + else + # The git-svn revision number + VCS_FULL_HASH="$(git svn find-rev HEAD)" + VCS_SHORT_HASH="${VCS_FULL_HASH}" + fi + + # Current branch + VCS_BRANCH="$(git rev-parse --symbolic-full-name --verify "$(git name-rev --name-only --no-undefined HEAD 2>/dev/null)" 2>/dev/null | sed -e 's:refs/heads/::' | sed -e 's:refs/::')" + + # Cache the description + local DESCRIPTION="$(git describe --long --tags 2>/dev/null)" + + # Current or last tag ancestor (empty if no tags) + VCS_TAG="$(echo "${DESCRIPTION}" | sed -e "s:-g${VCS_SHORT_HASH}\$::" -e 's:-[0-9]*$::')" + + # Distance to last tag or an alias of VCS_NUM if there is no tag + if [ ! -z "${DESCRIPTION}" ]; then + VCS_TICK="$(echo "${DESCRIPTION}" | sed -e "s:${VCS_TAG}-::" -e "s:-g${VCS_SHORT_HASH}::")" + else + VCS_TICK="${VCS_NUM}" + fi + + # Date of the current commit + VCS_DATE="$(TZ=UTC git show -s --date=iso-strict-local --pretty=format:%ad | sed -e 's|+00:00|Z|')" + if [ -z "${VCS_DATE}" ]; then + echo "warning: Action stamps require git version 2.7+." 1>&2 + VCS_DATE="$(git log -1 --pretty=format:%ci | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')" + local ASdis="1" + fi + + # Action Stamp + if [ -z "${ASdis}" ]; then + VCS_ACTION_STAMP="${VCS_DATE}!$(git show -s --pretty=format:%cE)" + else + VCS_ACTION_STAMP="" + fi + + cd "${oldPath}" +} + +# For hg repos +# shellcheck disable=SC2039,SC2164 +hgRepo() { + local oldPath="${PWD}" + + cd "$(hg root)" + + VCS_TYPE="hg" + + VCS_BASENAME="$(basename "${PWD}")" + + VCS_UUID="$(hg log -r "0" -l 1 --template '{node}\n')" + + # Is the working copy clean? + test -z "$(hg status -duram)" + VCS_WC_MODIFIED="${?}" + + # Enumeration of changesets + VCS_NUM="$(hg id -n | tr -d '+')" + + # The full revision hash + VCS_FULL_HASH="$(hg log -r "${VCS_NUM}" -l 1 --template '{node}\n')" + + # The short hash + VCS_SHORT_HASH="$(hg id -i | tr -d '+')" + + # Current bookmark (bookmarks are roughly equivalent to git's branches) + # or branch if no bookmark + VCS_BRANCH="$(hg id -B | cut -d ' ' -f 1)" + # Fall back to the branch if there are no bookmarks + if [ -z "${VCS_BRANCH}" ]; then + VCS_BRANCH="$(hg id -b)" + fi + + # Current or last tag ancestor (excluding auto tags, empty if no tags) + VCS_TAG="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttag}\n' 2>/dev/null | sed -e 's:qtip::' -e 's:tip::' -e 's:qbase::' -e 's:qparent::' -e "s:$(hg --config 'extensions.color=' --config 'extensions.mq=' --color never qtop 2>/dev/null)::" | cut -d ' ' -f 1)" + + # Distance to last tag or an alias of VCS_NUM if there is no tag + if [ ! -z "${VCS_TAG}" ]; then + VCS_TICK="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttagdistance}\n' 2>/dev/null)" + else + VCS_TICK="${VCS_NUM}" + fi + + # Date of the current commit + VCS_DATE="$(hg log -r "${VCS_NUM}" -l 1 --template '{date|isodatesec}\n' 2>/dev/null | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')" + + # Action Stamp + VCS_ACTION_STAMP="$(TZ=UTC hg log -r "${VCS_NUM}" -l 1 --template '{date|localdate|rfc3339date}\n' 2>/dev/null | sed -e 's|+00:00|Z|')!$(hg log -r "${VCS_NUM}" -l 1 --template '{author|email}\n' 2>/dev/null)" + + cd "${oldPath}" +} + +# For bzr repos +# shellcheck disable=SC2039,SC2164 +bzrRepo() { + local oldPath="${PWD}" + + cd "$(bzr root)" + + VCS_TYPE="bzr" + + VCS_BASENAME="$(basename "${PWD}")" + + # Currently unimplemented because more investigation is needed. + VCS_UUID="" + + # Is the working copy clean? + bzr version-info --custom --template='{clean}\n' | grep -q '1' + VCS_WC_MODIFIED="${?}" + + # Enumeration of changesets + VCS_NUM="$(bzr revno)" + + # The full revision hash + VCS_FULL_HASH="$(bzr version-info --custom --template='{revision_id}\n')" + + # The short hash + VCS_SHORT_HASH="${VCS_NUM}" + + # Nick of the current branch + VCS_BRANCH="$(bzr nick)" + + # Current or last tag ancestor (excluding auto tags, empty if no tags) + VCS_TAG="$(bzr tags --sort=time | sed '/?$/d' | tail -n1 | cut -d ' ' -f1)" + + # Distance to last tag or an alias of VCS_NUM if there is no tag + if [ ! -z "${VCS_TAG}" ]; then + VCS_TICK="$(bzr log --line -r "tag:${VCS_TAG}.." | tail -n +2 | wc -l | sed -e 's:^ *::')" + else + VCS_TICK="${VCS_NUM}" + fi + + # Date of the current commit + VCS_DATE="$(bzr version-info --custom --template='{date}\n' | sed -e 's: :T:' -e 's: ::')" + + # Action Stamp + # Currently unimplemented because more investigation is needed. + VCS_ACTION_STAMP="" + + cd "${oldPath}" +} + +# For svn repos +# shellcheck disable=SC2039,SC2164,SC2155 +svnRepo() { + local oldPath="${PWD}" + + VCS_TYPE="svn" + + case "${PWD}" in + /*trunk*|/*branches*|/*tags*) + local fn="${PWD}" + while [ "$(basename "${fn}")" != 'trunk' ] && [ "$(basename "${fn}")" != 'branches' ] && [ "$(basename "${fn}")" != 'tags' ] && [ "$(basename "${fn}")" != '/' ]; do + local fn="$(dirname "${fn}")" + done + local fn="$(dirname "${fn}")" + if [ "${fn}" = '/' ]; then + VCS_BASENAME="$(basename "${PWD}")" + else + VCS_BASENAME="$(basename "${fn}")" + fi + ;; + *) VCS_BASENAME="$(basename "${PWD}")" ;; + esac + + VCS_UUID="$(svn info --xml | sed -n -e 's:::' -e 's:::p')" + + # Cache svnversion output + local SVNVERSION="$(svnversion)" + + # Is the working copy clean? + echo "${SVNVERSION}" | grep -q "M" + case "${?}" in + 0) + VCS_WC_MODIFIED="1" + ;; + 1) + if [ ! -z "${UNTRACKEDFILES}" ]; then + # `svnversion` does not detect untracked files and `svn status` is really slow, so only run it if we really have to. + if [ -z "$(svn status)" ]; then + VCS_WC_MODIFIED="0" + else + VCS_WC_MODIFIED="1" + fi + else + VCS_WC_MODIFIED="0" + fi + ;; + esac + + # Enumeration of changesets + VCS_NUM="$(echo "${SVNVERSION}" | cut -d : -f 1 | sed -e 's:M::' -e 's:S::' -e 's:P::')" + + # The full revision hash + VCS_FULL_HASH="${SVNVERSION}" + + # The short hash + VCS_SHORT_HASH="${VCS_NUM}" + + # Current branch + case "${PWD}" in + /*trunk*|/*branches*|/*tags*) + local lastbase="" + local fn="${PWD}" + while : + do + base="$(basename "${fn}")" + if [ "${base}" = 'trunk' ]; then + VCS_BRANCH='trunk' + break + elif [ "${base}" = 'branches' ] || [ "${base}" = 'tags' ]; then + VCS_BRANCH="${lastbase}" + break + elif [ "${base}" = '/' ]; then + VCS_BRANCH="" + break + fi + local lastbase="${base}" + local fn="$(dirname "${fn}")" + done + ;; + *) VCS_BRANCH="" ;; + esac + + # Current or last tag ancestor (empty if no tags). But "current + # tag" can't be extracted reliably because Subversion doesn't + # have tags the way other VCSes do. + VCS_TAG="" + VCS_TICK="" + + # Date of the current commit + VCS_DATE="$(svn info --xml | sed -n -e 's:::' -e 's:::p')" + + # Action Stamp + VCS_ACTION_STAMP="${VCS_DATE}!$(svn log --xml -l 1 -r "${VCS_SHORT_HASH}" | sed -n -e 's:::' -e 's:::p')" + + cd "${oldPath}" +} + + +# Functions to output data in different formats. +# For bash output +shOutput() { + cat > "${TARGETFILE}" << EOF +# ${GENERATED_HEADER} + +VCS_TYPE="${VCS_TYPE}" +VCS_BASENAME="${VCS_BASENAME}" +VCS_UUID="${VCS_UUID}" +VCS_NUM="${VCS_NUM}" +VCS_DATE="${VCS_DATE}" +VCS_BRANCH="${VCS_BRANCH}" +VCS_TAG="${VCS_TAG}" +VCS_TICK="${VCS_TICK}" +VCS_EXTRA="${VCS_EXTRA}" + +VCS_ACTION_STAMP="${VCS_ACTION_STAMP}" +VCS_FULL_HASH="${VCS_FULL_HASH}" +VCS_SHORT_HASH="${VCS_SHORT_HASH}" + +VCS_WC_MODIFIED="${VCS_WC_MODIFIED}" + +# end +EOF +} + +# For source C output +cOutput() { + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ + +const char *VCS_TYPE = "${VCS_TYPE}"; +const char *VCS_BASENAME = "${VCS_BASENAME}"; +const char *VCS_UUID = "${VCS_UUID}"; +const int VCS_NUM = ${VCS_NUM}; +const char *VCS_DATE = "${VCS_DATE}"; +const char *VCS_BRANCH = "${VCS_BRANCH}"; +const char *VCS_TAG = "${VCS_TAG}"; +const int VCS_TICK = ${VCS_TICK}; +const char *VCS_EXTRA = "${VCS_EXTRA}"; + +const char *VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"; +const char *VCS_FULL_HASH = "${VCS_FULL_HASH}"; +const char *VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; + +const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; + +/* end */ +EOF +} + +# For header output +hOutput() { + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ +#ifndef AUTOREVISION_H +#define AUTOREVISION_H + +#define VCS_TYPE "${VCS_TYPE}" +#define VCS_BASENAME "${VCS_BASENAME}" +#define VCS_UUID "${VCS_UUID}" +#define VCS_NUM ${VCS_NUM} +#define VCS_DATE "${VCS_DATE}" +#define VCS_BRANCH "${VCS_BRANCH}" +#define VCS_TAG "${VCS_TAG}" +#define VCS_TICK ${VCS_TICK} +#define VCS_EXTRA "${VCS_EXTRA}" + +#define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}" +#define VCS_FULL_HASH "${VCS_FULL_HASH}" +#define VCS_SHORT_HASH "${VCS_SHORT_HASH}" + +#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED} + +#endif + +/* end */ +EOF +} + +# A header output for use with xcode to populate info.plist strings +xcodeOutput() { + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ +#ifndef AUTOREVISION_H +#define AUTOREVISION_H + +#define VCS_TYPE ${VCS_TYPE} +#define VCS_BASENAME ${VCS_BASENAME} +#define VCS_UUID ${VCS_UUID} +#define VCS_NUM ${VCS_NUM} +#define VCS_DATE ${VCS_DATE} +#define VCS_BRANCH ${VCS_BRANCH} +#define VCS_TAG ${VCS_TAG} +#define VCS_TICK ${VCS_TICK} +#define VCS_EXTRA ${VCS_EXTRA} + +#define VCS_ACTION_STAMP ${VCS_ACTION_STAMP} +#define VCS_FULL_HASH ${VCS_FULL_HASH} +#define VCS_SHORT_HASH ${VCS_SHORT_HASH} + +#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED} + +#endif + +/* end */ +EOF +} + +# For Swift output +swiftOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + # For values that may not exist depending on the type of repo we + # have read from, set them to `nil` when they are empty. + if [ -z "${VCS_UUID}" ]; then + VCS_UUID="nil" + else + VCS_UUID="\"${VCS_UUID}\"" + fi + if [ -z "${VCS_TAG}" ]; then + VCS_TAG="nil" + else + VCS_TAG="\"${VCS_TAG}\"" + fi + : "${VCS_TICK:="nil"}" + if [ -z "${VCS_EXTRA}" ]; then + VCS_EXTRA="nil" + else + VCS_EXTRA="\"${VCS_EXTRA}\"" + fi + if [ -z "${VCS_ACTION_STAMP}" ]; then + VCS_ACTION_STAMP="nil" + else + VCS_ACTION_STAMP="\"${VCS_ACTION_STAMP}\"" + fi + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ + +let VCS_TYPE = "${VCS_TYPE}" +let VCS_BASENAME = "${VCS_BASENAME}" +let VCS_UUID: String? = ${VCS_UUID} +let VCS_NUM: Int = ${VCS_NUM} +let VCS_DATE = "${VCS_DATE}" +let VCS_BRANCH: String = "${VCS_BRANCH}" +let VCS_TAG: String? = ${VCS_TAG} +let VCS_TICK: Int? = ${VCS_TICK} +let VCS_EXTRA: String? = ${VCS_EXTRA} + +let VCS_ACTION_STAMP: String? = ${VCS_ACTION_STAMP} +let VCS_FULL_HASH: String = "${VCS_FULL_HASH}" +let VCS_SHORT_HASH: String = "${VCS_SHORT_HASH}" + +let VCS_WC_MODIFIED: Bool = ${VCS_WC_MODIFIED} + +/* end */ +EOF +} + +# For Python output +pyOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="False" ;; + 1) VCS_WC_MODIFIED="True" ;; + esac + cat > "${TARGETFILE}" << EOF +# ${GENERATED_HEADER} + +VCS_TYPE = "${VCS_TYPE}" +VCS_BASENAME = "${VCS_BASENAME}" +VCS_UUID = "${VCS_UUID}" +VCS_NUM = ${VCS_NUM} +VCS_DATE = "${VCS_DATE}" +VCS_BRANCH = "${VCS_BRANCH}" +VCS_TAG = "${VCS_TAG}" +VCS_TICK = ${VCS_TICK} +VCS_EXTRA = "${VCS_EXTRA}" + +VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}" +VCS_FULL_HASH = "${VCS_FULL_HASH}" +VCS_SHORT_HASH = "${VCS_SHORT_HASH}" + +VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} + +# end +EOF +} + +# For Perl output +plOutput() { + cat << EOF +# ${GENERATED_HEADER} + +\$VCS_TYPE = '${VCS_TYPE}'; +\$VCS_BASENAME = '${VCS_BASENAME}'; +\$VCS_UUID = '${VCS_UUID}'; +\$VCS_NUM = ${VCS_NUM}; +\$VCS_DATE = '${VCS_DATE}'; +\$VCS_BRANCH = '${VCS_BRANCH}'; +\$VCS_TAG = '${VCS_TAG}'; +\$VCS_TICK = ${VCS_TICK}; +\$VCS_EXTRA = '${VCS_EXTRA}'; + +\$VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}'; +\$VCS_FULL_HASH = '${VCS_FULL_HASH}'; +\$VCS_SHORT_HASH = '${VCS_SHORT_HASH}'; + +\$VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; + +# end +1; +EOF +} + +# For lua output +luaOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + cat > "${TARGETFILE}" << EOF +-- ${GENERATED_HEADER} + +VCS_TYPE = "${VCS_TYPE}" +VCS_BASENAME = "${VCS_BASENAME}" +VCS_UUID = "${VCS_UUID}" +VCS_NUM = ${VCS_NUM} +VCS_DATE = "${VCS_DATE}" +VCS_BRANCH = "${VCS_BRANCH}" +VCS_TAG = "${VCS_TAG}" +VCS_TICK = ${VCS_TICK} +VCS_EXTRA = "${VCS_EXTRA}" + +VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}" +VCS_FULL_HASH = "${VCS_FULL_HASH}" +VCS_SHORT_HASH = "${VCS_SHORT_HASH}" + +VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} + +-- end +EOF +} + +# For php output +phpOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + cat > "${TARGETFILE}" << EOF + "${VCS_TYPE}", + "VCS_BASENAME" => "${VCS_BASENAME}", + "VCS_UUID" => "${VCS_UUID}", + "VCS_NUM" => ${VCS_NUM}, + "VCS_DATE" => "${VCS_DATE}", + "VCS_BRANCH" => "${VCS_BRANCH}", + "VCS_TAG" => "${VCS_TAG}", + "VCS_TICK" => ${VCS_TICK}, + "VCS_EXTRA" => "${VCS_EXTRA}", + "VCS_ACTION_STAMP" => "${VCS_ACTION_STAMP}", + "VCS_FULL_HASH" => "${VCS_FULL_HASH}", + "VCS_SHORT_HASH" => "${VCS_SHORT_HASH}", + "VCS_WC_MODIFIED" => ${VCS_WC_MODIFIED} +); + +# end +?> +EOF +} + +# For ini output +iniOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + cat > "${TARGETFILE}" << EOF +; ${GENERATED_HEADER} +[VCS] +VCS_TYPE = "${VCS_TYPE}" +VCS_BASENAME = "${VCS_BASENAME}" +VCS_UUID = "${VCS_UUID}" +VCS_NUM = ${VCS_NUM} +VCS_DATE = "${VCS_DATE}" +VCS_BRANCH = "${VCS_BRANCH}" +VCS_TAG = "${VCS_TAG}" +VCS_TICK = ${VCS_TICK} +VCS_EXTRA = "${VCS_EXTRA}" +VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}" +VCS_FULL_HASH = "${VCS_FULL_HASH}" +VCS_SHORT_HASH = "${VCS_SHORT_HASH}" +VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} +; end +EOF +} + +# For javascript output +jsOutput() { + case "${VCS_WC_MODIFIED}" in + 1) VCS_WC_MODIFIED="true" ;; + 0) VCS_WC_MODIFIED="false" ;; + esac + cat > "${TARGETFILE}" << EOF +/** ${GENERATED_HEADER} */ + +var autorevision = { + VCS_TYPE: "${VCS_TYPE}", + VCS_BASENAME: "${VCS_BASENAME}", + VCS_UUID: "${VCS_UUID}", + VCS_NUM: ${VCS_NUM}, + VCS_DATE: "${VCS_DATE}", + VCS_BRANCH: "${VCS_BRANCH}", + VCS_TAG: "${VCS_TAG}", + VCS_TICK: ${VCS_TICK}, + VCS_EXTRA: "${VCS_EXTRA}", + + VCS_ACTION_STAMP: "${VCS_ACTION_STAMP}", + VCS_FULL_HASH: "${VCS_FULL_HASH}", + VCS_SHORT_HASH: "${VCS_SHORT_HASH}", + + VCS_WC_MODIFIED: ${VCS_WC_MODIFIED} +}; + +/** Node.js compatibility */ +if (typeof module !== 'undefined') { + module.exports = autorevision; +} + +/** end */ +EOF +} + +# For JSON output +jsonOutput() { + case "${VCS_WC_MODIFIED}" in + 1) VCS_WC_MODIFIED="true" ;; + 0) VCS_WC_MODIFIED="false" ;; + esac + cat > "${TARGETFILE}" << EOF +{ + "_comment": "${GENERATED_HEADER}", + "VCS_TYPE": "${VCS_TYPE}", + "VCS_BASENAME": "${VCS_BASENAME}", + "VCS_UUID": "${VCS_UUID}", + "VCS_NUM": ${VCS_NUM}, + "VCS_DATE": "${VCS_DATE}", + "VCS_BRANCH":"${VCS_BRANCH}", + "VCS_TAG": "${VCS_TAG}", + "VCS_TICK": ${VCS_TICK}, + "VCS_EXTRA": "${VCS_EXTRA}", + + "VCS_ACTION_STAMP": "${VCS_ACTION_STAMP}", + "VCS_FULL_HASH": "${VCS_FULL_HASH}", + "VCS_SHORT_HASH": "${VCS_SHORT_HASH}", + + "VCS_WC_MODIFIED": ${VCS_WC_MODIFIED} +} +EOF +} + +# For Java output +javaOutput() { + case "${VCS_WC_MODIFIED}" in + 1) VCS_WC_MODIFIED="true" ;; + 0) VCS_WC_MODIFIED="false" ;; + esac + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ + +public class autorevision { + public static final String VCS_TYPE = "${VCS_TYPE}"; + public static final String VCS_BASENAME = "${VCS_BASENAME}"; + public static final String VCS_UUID = "${VCS_UUID}"; + public static final long VCS_NUM = ${VCS_NUM}; + public static final String VCS_DATE = "${VCS_DATE}"; + public static final String VCS_BRANCH = "${VCS_BRANCH}"; + public static final String VCS_TAG = "${VCS_TAG}"; + public static final long VCS_TICK = ${VCS_TICK}; + public static final String VCS_EXTRA = "${VCS_EXTRA}"; + + public static final String VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"; + public static final String VCS_FULL_HASH = "${VCS_FULL_HASH}"; + public static final String VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; + + public static final boolean VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; +} +EOF +} + +# For Java properties output +javapropOutput() { + case "${VCS_WC_MODIFIED}" in + 1) VCS_WC_MODIFIED="true" ;; + 0) VCS_WC_MODIFIED="false" ;; + esac + cat > "${TARGETFILE}" << EOF +# ${GENERATED_HEADER} + +VCS_TYPE=${VCS_TYPE} +VCS_BASENAME=${VCS_BASENAME} +VCS_UUID=${VCS_UUID} +VCS_NUM=${VCS_NUM} +VCS_DATE=${VCS_DATE} +VCS_BRANCH=${VCS_BRANCH} +VCS_TAG=${VCS_TAG} +VCS_TICK=${VCS_TICK} +VCS_EXTRA=${VCS_EXTRA} + +VCS_ACTION_STAMP=${VCS_ACTION_STAMP} +VCS_FULL_HASH=${VCS_FULL_HASH} +VCS_SHORT_HASH=${VCS_SHORT_HASH} + +VCS_WC_MODIFIED=${VCS_WC_MODIFIED} +EOF +} + +# For m4 output +m4Output() { + cat > "${TARGETFILE}" << EOF +dnl ${GENERATED_HEADER} +define(\`VCS_TYPE', \`${VCS_TYPE}')dnl +define(\`VCS_BASENAME', \`${VCS_BASENAME}')dnl +define(\`VCS_UUID', \`${VCS_UUID}')dnl +define(\`VCS_NUM', \`${VCS_NUM}')dnl +define(\`VCS_DATE', \`${VCS_DATE}')dnl +define(\`VCS_BRANCH', \`${VCS_BRANCH}')dnl +define(\`VCS_TAG', \`${VCS_TAG}')dnl +define(\`VCS_TICK', \`${VCS_TICK}')dnl +define(\`VCS_EXTRA', \`${VCS_EXTRA}')dnl +define(\`VCS_ACTIONSTAMP', \`${VCS_ACTION_STAMP}')dnl +define(\`VCS_FULLHASH', \`${VCS_FULL_HASH}')dnl +define(\`VCS_SHORTHASH', \`${VCS_SHORT_HASH}')dnl +define(\`VCS_WC_MODIFIED', \`${VCS_WC_MODIFIED}')dnl +EOF +} + +# For (La)TeX output +texOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + cat > "${TARGETFILE}" << EOF +% ${GENERATED_HEADER} +\def \vcsType {${VCS_TYPE}} +\def \vcsBasename {${VCS_BASENAME}} +\def \vcsUUID {${VCS_UUID}} +\def \vcsNum {${VCS_NUM}} +\def \vcsDate {${VCS_DATE}} +\def \vcsBranch {${VCS_BRANCH}} +\def \vcsTag {${VCS_TAG}} +\def \vcsTick {${VCS_TICK}} +\def \vcsExtra {${VCS_EXTRA}} +\def \vcsACTIONSTAMP {${VCS_ACTION_STAMP}} +\def \vcsFullHash {${VCS_FULL_HASH}} +\def \vcsShortHash {${VCS_SHORT_HASH}} +\def \vcsWCModified {${VCS_WC_MODIFIED}} +\endinput +EOF +} + +# For scheme output +schemeOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="#f" ;; + 1) VCS_WC_MODIFIED="#t" ;; + esac + cat > "${TARGETFILE}" << EOF +;; ${GENERATED_HEADER} +(define VCS_TYPE "${VCS_TYPE}") +(define VCS_BASENAME "${VCS_BASENAME}") +(define VCS_UUID "${VCS_UUID}") +(define VCS_NUM ${VCS_NUM}) +(define VCS_DATE "${VCS_DATE}") +(define VCS_BRANCH "${VCS_BRANCH}") +(define VCS_TAG "${VCS_TAG}") +(define VCS_TICK ${VCS_TICK}) +(define VCS_EXTRA "${VCS_EXTRA}") + +(define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}") +(define VCS_FULL_HASH "${VCS_FULL_HASH}") +(define VCS_SHORT_HASH "${VCS_SHORT_HASH}") + +(define VCS_WC_MODIFIED ${VCS_WC_MODIFIED}) +;; end +EOF +} + +# For clojure output +clojureOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="false" ;; + 1) VCS_WC_MODIFIED="true" ;; + esac + cat > "${TARGETFILE}" << EOF +;; ${GENERATED_HEADER} +(def VCS_TYPE "${VCS_TYPE}") +(def VCS_BASENAME "${VCS_BASENAME}") +(def VCS_UUID "${VCS_UUID}") +(def VCS_NUM ${VCS_NUM}) +(def VCS_DATE "${VCS_DATE}") +(def VCS_BRANCH "${VCS_BRANCH}") +(def VCS_TAG "${VCS_TAG}") +(def VCS_TICK ${VCS_TICK}) +(def VCS_EXTRA "${VCS_EXTRA}") + +(def VCS_ACTION_STAMP "${VCS_ACTION_STAMP}") +(def VCS_FULL_HASH "${VCS_FULL_HASH}") +(def VCS_SHORT_HASH "${VCS_SHORT_HASH}") + +(def VCS_WC_MODIFIED ${VCS_WC_MODIFIED}) +;; end +EOF +} + +# For rpm spec file output +rpmOutput() { + cat > "${TARGETFILE}" << EOF +# ${GENERATED_HEADER} +$([ "${VCS_TYPE}" ] && echo "%define vcs_type ${VCS_TYPE}") +$([ "${VCS_BASENAME}" ] && echo "%define vcs_basename ${VCS_BASENAME}") +$([ "${VCS_UUID}" ] && echo "%define vcs_uuid ${VCS_UUID}") +$([ "${VCS_NUM}" ] && echo "%define vcs_num ${VCS_NUM}") +$([ "${VCS_DATE}" ] && echo "%define vcs_date ${VCS_DATE}") +$([ "${VCS_BRANCH}" ] && echo "%define vcs_branch ${VCS_BRANCH}") +$([ "${VCS_TAG}" ] && echo "%define vcs_tag ${VCS_TAG}") +$([ "${VCS_TICK}" ] && echo "%define vcs_tick ${VCS_TICK}") +$([ "${VCS_EXTRA}" ] && echo "%define vcs_extra ${VCS_EXTRA}") + +$([ "${VCS_ACTION_STAMP}" ] && echo "%define vcs_action_stamp ${VCS_ACTION_STAMP}") +$([ "${VCS_FULL_HASH}" ] && echo "%define vcs_full_hash ${VCS_FULL_HASH}") +$([ "${VCS_SHORT_HASH}" ] && echo "%define vcs_short_hash ${VCS_SHORT_HASH}") + +$([ "${VCS_WC_MODIFIED}" ] && echo "%define vcs_wc_modified ${VCS_WC_MODIFIED}") +# end +EOF +} + +# shellcheck disable=SC2155,SC2039 +hppOutput() { + local NAMESPACE="$(echo "${VCS_BASENAME}" | sed -e 's:_::g' | tr '[:lower:]' '[:upper:]')" + cat > "${TARGETFILE}" << EOF +/* ${GENERATED_HEADER} */ + +#ifndef ${NAMESPACE}_AUTOREVISION_H +#define ${NAMESPACE}_AUTOREVISION_H + +#include + +namespace $(echo "${NAMESPACE}" | tr '[:upper:]' '[:lower:]') +{ + const std::string VCS_TYPE = "${VCS_TYPE}"; + const std::string VCS_BASENAME = "${VCS_BASENAME}"; + const std::string VCS_UUID = "${VCS_UUID}"; + const int VCS_NUM = ${VCS_NUM}; + const std::string VCS_DATE = "${VCS_DATE}"; + const std::string VCS_BRANCH = "${VCS_BRANCH}"; + const std::string VCS_TAG = "${VCS_TAG}"; + const int VCS_TICK = ${VCS_TICK}; + const std::string VCS_EXTRA = "${VCS_EXTRA}"; + + const std::string VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"; + const std::string VCS_FULL_HASH = "${VCS_FULL_HASH}"; + const std::string VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; + + const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; +} + +#endif + +/* end */ +EOF +} + +matlabOutput() { + case "${VCS_WC_MODIFIED}" in + 0) VCS_WC_MODIFIED="FALSE" ;; + 1) VCS_WC_MODIFIED="TRUE" ;; + esac + cat > "${TARGETFILE}" << EOF +% ${GENERATED_HEADER} + +VCS_TYPE = '${VCS_TYPE}'; +VCS_BASENAME = '${VCS_BASENAME}'; +VCS_UUID = '${VCS_UUID}'; +VCS_NUM = ${VCS_NUM}; +VCS_DATE = '${VCS_DATE}'; +VCS_BRANCH = '${VCS_BRANCH}'; +VCS_TAG = '${VCS_TAG}'; +VCS_TICK = ${VCS_TICK}; +VCS_EXTRA = '${VCS_EXTRA}'; + +VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}'; +VCS_FULL_HASH = '${VCS_FULL_HASH}'; +VCS_SHORT_HASH = '${VCS_SHORT_HASH}'; + +VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; + +% end +EOF +} + +octaveOutput() { + cat > "${TARGETFILE}" << EOF +% ${GENERATED_HEADER} + +VCS_TYPE = '${VCS_TYPE}'; +VCS_BASENAME = '${VCS_BASENAME}'; +VCS_UUID = '${VCS_UUID}'; +VCS_NUM = ${VCS_NUM}; +VCS_DATE = '${VCS_DATE}'; +VCS_BRANCH = '${VCS_BRANCH}'; +VCS_TAG = '${VCS_TAG}'; +VCS_TICK = ${VCS_TICK}; +VCS_EXTRA = '${VCS_EXTRA}'; + +VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}'; +VCS_FULL_HASH = '${VCS_FULL_HASH}'; +VCS_SHORT_HASH = '${VCS_SHORT_HASH}'; + +VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; + +% end +EOF +} + +cmakeOutput() { + cat > "${TARGETFILE}" << EOF +# ${GENERATED_HEADER} + +set(VCS_TYPE ${VCS_TYPE}) +set(VCS_BASENAME ${VCS_BASENAME}) +set(VCS_UUID ${VCS_UUID}) +set(VCS_NUM ${VCS_NUM}) +set(VCS_DATE ${VCS_DATE}) +set(VCS_BRANCH ${VCS_BRANCH}) +set(VCS_TAG ${VCS_TAG}) +set(VCS_TICK ${VCS_TICK}) +set(VCS_EXTRA ${VCS_EXTRA}) + +set(VCS_ACTION_STAMP ${VCS_ACTION_STAMP}) +set(VCS_FULL_HASH ${VCS_FULL_HASH}) +set(VCS_SHORT_HASH ${VCS_SHORT_HASH}) + +set(VCS_WC_MODIFIED ${VCS_WC_MODIFIED}) + +# end +EOF +} + + +# Helper functions +# Count path segments +# shellcheck disable=SC2039 +pathSegment() { + local pathz="${1}" + local depth="0" + + if [ ! -z "${pathz}" ]; then + # Continue until we are at / or there are no path separators left. + while [ ! "${pathz}" = "/" ] && [ ! "${pathz}" = "$(echo "${pathz}" | sed -e 's:/::')" ]; do + pathz="$(dirname "${pathz}")" + depth="$((depth+1))" + done + fi + echo "${depth}" +} + +# Largest of four numbers +# shellcheck disable=SC2039 +multiCompare() { + local larger="${1}" + local numA="${2}" + local numB="${3}" + local numC="${4}" + + [ "${numA}" -gt "${larger}" ] && larger="${numA}" + [ "${numB}" -gt "${larger}" ] && larger="${numB}" + [ "${numC}" -gt "${larger}" ] && larger="${numC}" + echo "${larger}" +} + +# Test for repositories +# shellcheck disable=SC2155,SC2039 +repoTest() { + REPONUM="0" + if [ ! -z "$(git rev-parse HEAD 2>/dev/null)" ]; then + local gitPath="$(git rev-parse --show-toplevel)" + local gitDepth="$(pathSegment "${gitPath}")" + REPONUM="$((REPONUM+1))" + else + local gitDepth="0" + fi + if [ ! -z "$(hg root 2>/dev/null)" ]; then + local hgPath="$(hg root 2>/dev/null)" + local hgDepth="$(pathSegment "${hgPath}")" + REPONUM="$((REPONUM+1))" + else + local hgDepth="0" + fi + if [ ! -z "$(bzr root 2>/dev/null)" ]; then + local bzrPath="$(bzr root 2>/dev/null)" + local bzrDepth="$(pathSegment "${bzrPath}")" + REPONUM="$((REPONUM+1))" + else + local bzrDepth="0" + fi + if [ ! -z "$(svn info 2>/dev/null)" ]; then + local stringz="" + local stringx="" + local svnPath="$(svn info --xml | sed -n -e "s:${stringz}::" -e "s:${stringx}::p")" + # An old enough svn will not be able give us a path; default + # to 1 for that case. + if [ -z "${svnPath}" ]; then + local svnDepth="1" + else + local svnDepth="$(pathSegment "${svnPath}")" + fi + REPONUM="$((REPONUM+1))" + else + local svnDepth="0" + fi + + # Do not do more work then we have to. + if [ "${REPONUM}" = "0" ]; then + return + fi + + # Figure out which repo is the deepest and use it. + local wonRepo="$(multiCompare "${gitDepth}" "${hgDepth}" "${bzrDepth}" "${svnDepth}")" + if [ "${wonRepo}" = "${gitDepth}" ]; then + gitRepo + elif [ "${wonRepo}" = "${hgDepth}" ]; then + hgRepo + elif [ "${wonRepo}" = "${bzrDepth}" ]; then + bzrRepo + elif [ "${wonRepo}" = "${svnDepth}" ]; then + svnRepo + fi +} + + + +# Detect which repos we are in and gather data. +# shellcheck source=/dev/null +if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then + # When requested only read from the cache to populate our symbols. + . "${CACHEFILE}" +else + # If a value is not set through the environment set VCS_EXTRA to nothing. + : "${VCS_EXTRA:=""}" + repoTest + + if [ -f "${CACHEFILE}" ] && [ "${REPONUM}" = "0" ]; then + # We are not in a repo; try to use a previously generated cache to populate our symbols. + . "${CACHEFILE}" + # Do not overwrite the cache if we know we are not going to write anything new. + CACHEFORCE="1" + elif [ "${REPONUM}" = "0" ]; then + echo "error: No repo or cache detected." 1>&2 + exit 1 + fi +fi + + +# -s output is handled here. +if [ ! -z "${VAROUT}" ]; then + if [ "${VAROUT}" = "VCS_TYPE" ]; then + echo "${VCS_TYPE}" + elif [ "${VAROUT}" = "VCS_BASENAME" ]; then + echo "${VCS_BASENAME}" + elif [ "${VAROUT}" = "VCS_NUM" ]; then + echo "${VCS_NUM}" + elif [ "${VAROUT}" = "VCS_DATE" ]; then + echo "${VCS_DATE}" + elif [ "${VAROUT}" = "VCS_BRANCH" ]; then + echo "${VCS_BRANCH}" + elif [ "${VAROUT}" = "VCS_TAG" ]; then + echo "${VCS_TAG}" + elif [ "${VAROUT}" = "VCS_TICK" ]; then + echo "${VCS_TICK}" + elif [ "${VAROUT}" = "VCS_FULL_HASH" ]; then + echo "${VCS_FULL_HASH}" + elif [ "${VAROUT}" = "VCS_SHORT_HASH" ]; then + echo "${VCS_SHORT_HASH}" + elif [ "${VAROUT}" = "VCS_WC_MODIFIED" ]; then + echo "${VCS_WC_MODIFIED}" + elif [ "${VAROUT}" = "VCS_ACTION_STAMP" ]; then + echo "${VCS_ACTION_STAMP}" + else + echo "error: Not a valid output symbol." 1>&2 + exit 1 + fi +fi + + +# Detect requested output type and use it. +if [ ! -z "${AFILETYPE}" ]; then + if [ "${AFILETYPE}" = "c" ]; then + cOutput + elif [ "${AFILETYPE}" = "h" ]; then + hOutput + elif [ "${AFILETYPE}" = "xcode" ]; then + xcodeOutput + elif [ "${AFILETYPE}" = "swift" ]; then + swiftOutput + elif [ "${AFILETYPE}" = "sh" ]; then + shOutput + elif [ "${AFILETYPE}" = "py" ] || [ "${AFILETYPE}" = "python" ]; then + pyOutput + elif [ "${AFILETYPE}" = "pl" ] || [ "${AFILETYPE}" = "perl" ]; then + plOutput + elif [ "${AFILETYPE}" = "lua" ]; then + luaOutput + elif [ "${AFILETYPE}" = "php" ]; then + phpOutput + elif [ "${AFILETYPE}" = "ini" ]; then + iniOutput + elif [ "${AFILETYPE}" = "js" ]; then + jsOutput + elif [ "${AFILETYPE}" = "json" ]; then + jsonOutput + elif [ "${AFILETYPE}" = "java" ]; then + javaOutput + elif [ "${AFILETYPE}" = "javaprop" ]; then + javapropOutput + elif [ "${AFILETYPE}" = "tex" ]; then + texOutput + elif [ "${AFILETYPE}" = "m4" ]; then + m4Output + elif [ "${AFILETYPE}" = "scheme" ]; then + schemeOutput + elif [ "${AFILETYPE}" = "clojure" ]; then + clojureOutput + elif [ "${AFILETYPE}" = "rpm" ]; then + rpmOutput + elif [ "${AFILETYPE}" = "hpp" ]; then + hppOutput + elif [ "${AFILETYPE}" = "matlab" ]; then + matlabOutput + elif [ "${AFILETYPE}" = "octave" ]; then + octaveOutput + elif [ "${AFILETYPE}" = "cmake" ]; then + cmakeOutput + else + echo "error: Not a valid output type." 1>&2 + exit 1 + fi +fi + + +# If requested, make a cache file. +if [ ! -z "${CACHEFILE}" ] && [ ! "${CACHEFORCE}" = "1" ]; then + TARGETFILE="${CACHEFILE}.tmp" + shOutput + + # Check to see if there have been any actual changes. + if [ ! -f "${CACHEFILE}" ]; then + mv -f "${CACHEFILE}.tmp" "${CACHEFILE}" + elif cmp -s "${CACHEFILE}.tmp" "${CACHEFILE}"; then + rm -f "${CACHEFILE}.tmp" + else + mv -f "${CACHEFILE}.tmp" "${CACHEFILE}" + fi +fi diff --git a/ci/get-nprocessors.sh b/ci/get-nprocessors.sh new file mode 100644 index 0000000..43635e7 --- /dev/null +++ b/ci/get-nprocessors.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Copyright 2017 Google Inc. +# All Rights Reserved. +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This file is typically sourced by another script. +# if possible, ask for the precise number of processors, +# otherwise take 2 processors as reasonable default; see +# https://docs.travis-ci.com/user/speeding-up-the-build/#Makefile-optimization +if [ -x /usr/bin/getconf ]; then + NPROCESSORS=$(/usr/bin/getconf _NPROCESSORS_ONLN) +else + NPROCESSORS=2 +fi + +# as of 2017-09-04 Travis CI reports 32 processors, but GCC build +# crashes if parallelized too much (maybe memory consumption problem), +# so limit to 4 processors for the time being. +if [ $NPROCESSORS -gt 4 ] ; then + echo "$0:Note: Limiting processors to use by make from $NPROCESSORS to 4." + NPROCESSORS=4 +fi diff --git a/ci/perpare_pulp3_netrc.sh b/ci/perpare_pulp3_netrc.sh new file mode 100644 index 0000000..8414bbb --- /dev/null +++ b/ci/perpare_pulp3_netrc.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -evx +echo "machine ${PULP3_SERVER_URL}\nlogin ${PULP3_SERVER_LOGIN}\npassword ${PULP3_SERVER_PASSWORD}\n" > ~/.netrc diff --git a/ci/travis.sh b/ci/travis.sh new file mode 100644 index 0000000..29324f6 --- /dev/null +++ b/ci/travis.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env sh +set -evx + +chmod +x ci/get-nprocessors.sh +. ci/get-nprocessors.sh + +# if possible, ask for the precise number of processors, +# otherwise take 2 processors as reasonable default; see +# https://docs.travis-ci.com/user/speeding-up-the-build/#Makefile-optimization +if [ -x /usr/bin/getconf ]; then + NPROCESSORS=$(/usr/bin/getconf _NPROCESSORS_ONLN) +else + NPROCESSORS=2 +fi + +# as of 2017-09-04 Travis CI reports 32 processors, but GCC build +# crashes if parallelized too much (maybe memory consumption problem), +# so limit to 4 processors for the time being. +if [ $NPROCESSORS -gt 4 ] ; then + echo "$0:Note: Limiting processors to use by make from $NPROCESSORS to 4." + NPROCESSORS=4 +fi + +# Tell make to use the processors. No preceding '-' required. +MAKEFLAGS="j${NPROCESSORS}" +export MAKEFLAGS + +env | sort + +# Set default values to OFF for these variables if not specified. +: "${NO_EXCEPTION:=OFF}" +: "${NO_RTTI:=OFF}" +: "${COMPILER_IS_GNUCXX:=OFF}" + +# Install dependency from YUM +yum install -y libasan +#yum install -y libmnl-devel libnfnetlink-devel +yum install -y libnetfilter_queue-devel + +if [ $ASAN_OPTION ];then + source /opt/rh/devtoolset-7/enable +fi + +mkdir build || true +cd build + +cmake3 -DCMAKE_CXX_FLAGS=$CXX_FLAGS \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DASAN_OPTION=$ASAN_OPTION \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ + -DVERSION_DAILY_BUILD=$TESTING_VERSION_BUILD \ + .. +make + +if [ -n "${PACKAGE}" ]; then + make package + cp ~/rpm_upload_tools.py ./ + python3 rpm_upload_tools.py ${PULP3_REPO_NAME} ${PULP3_DIST_NAME} *.rpm +fi + +if [ -n "${UPLOAD_SYMBOL_FILES}" ]; then + rpm -i packetadapter*debuginfo*.rpm + cp /usr/lib/debug/opt/tsg/packetadapter/bin/packetadapter.debug /tmp/packetadapter.debuginfo.${CI_COMMIT_SHORT_SHA} + sentry-cli upload-dif -t elf /tmp/packetadapter.debuginfo.${CI_COMMIT_SHORT_SHA} +fi diff --git a/cmake/FindNFNETLINK.cmake b/cmake/FindNFNETLINK.cmake new file mode 100644 index 0000000..e0e7742 --- /dev/null +++ b/cmake/FindNFNETLINK.cmake @@ -0,0 +1,39 @@ +# - Find nfnetlinkDaemon +# Find the nfnetlink daemon library +# +# This module defines the following variables: +# NFNETLINK_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# NFNETLINK_INCLUDE_DIRS - The directory where to find the header file +# NFNETLINK_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# NFNETLINK_LIBRARY +# NFNETLINK_INCLUDE_DIR +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(NFNETLINK libnfnetlink) + +if(NOT NFNETLINK_FOUND) + find_path(NFNETLINK_INCLUDE_DIRS NAMES nlibnfnetlink/libnfnetlink.h + DOC "The nfnetlink include directory") + + find_library(NFNETLINK_LIBRARIES NAMES libnfnetlink + DOC "The nfnetlink library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set NFNETLINK_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NFNETLINK REQUIRED_VARS NFNETLINK_LIBRARIES NFNETLINK_INCLUDE_DIRS) + + if(NFNETLINK_FOUND) + set(NFNETLINK_LIBRARY ${NFNETLINK_LIBRARIES}) + set(NFNETLINK_INCLUDE_DIR ${NFNETLINK_INCLUDE_DIRS}) + endif() +endif() + +mark_as_advanced(NFNETLINK_INCLUDE_DIRS NFNETLINK_LIBRARIES) \ No newline at end of file diff --git a/cmake/Package.cmake b/cmake/Package.cmake new file mode 100644 index 0000000..154cfcd --- /dev/null +++ b/cmake/Package.cmake @@ -0,0 +1,31 @@ +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CPACK_PACKAGE_NAME "packetadapter-debug") +else() + set(CPACK_PACKAGE_NAME "packetadapter") +endif() + +message(STATUS "Package: ${CPACK_PACKAGE_NAME}") + +set(CPACK_PACKAGE_VENDOR "MESASOFT") +set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}.${DESCRIBE}") +set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) + +# RPM Build +set(CPACK_GENERATOR "RPM") +set(CPACK_RPM_AUTO_GENERATED_FILE_NAME ON) +set(CPACK_RPM_FILE_NAME "RPM-DEFAULT") +set(CPACK_RPM_PACKAGE_AUTOREQPROV "no") +set(CPACK_RPM_PACKAGE_RELEASE_DIST on) +set(CPACK_RPM_DEBUGINFO_PACKAGE on) +set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PostInstall.in) +set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PostUninstall.in) +set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PreUninstall.in) + +# Must uninstall the debug package before install release package +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CPACK_RPM_PACKAGE_CONFLICTS "packetadapter") +else() + set(CPACK_RPM_PACKAGE_CONFLICTS "packetadapter-debug") +endif() diff --git a/cmake/PostInstall.in b/cmake/PostInstall.in new file mode 100644 index 0000000..c261a95 --- /dev/null +++ b/cmake/PostInstall.in @@ -0,0 +1,2 @@ +%systemd_post packetadapter.service +/sbin/ldconfig \ No newline at end of file diff --git a/cmake/PostUninstall.in b/cmake/PostUninstall.in new file mode 100644 index 0000000..f8a26ad --- /dev/null +++ b/cmake/PostUninstall.in @@ -0,0 +1,2 @@ +%systemd_postun_with_restart packetadapter.service +/sbin/ldconfig \ No newline at end of file diff --git a/cmake/PreUninstall.in b/cmake/PreUninstall.in new file mode 100644 index 0000000..306e90a --- /dev/null +++ b/cmake/PreUninstall.in @@ -0,0 +1 @@ +%systemd_preun packetadapter.service \ No newline at end of file diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..1fb595b --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,49 @@ + +# Using autorevision.sh to generate version information + +set(__SOURCE_AUTORESIVISION ${CMAKE_SOURCE_DIR}/autorevision.sh) +set(__AUTORESIVISION ${CMAKE_BINARY_DIR}/autorevision.sh) +set(__VERSION_CACHE ${CMAKE_SOURCE_DIR}/version.txt) +set(__VERSION_CONFIG ${CMAKE_BINARY_DIR}/version.cmake) + +file(COPY ${__SOURCE_AUTORESIVISION} DESTINATION ${CMAKE_BINARY_DIR} + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + +# execute autorevision.sh to generate version information +execute_process(COMMAND ${__AUTORESIVISION} -t cmake -o ${__VERSION_CACHE} OUTPUT_FILE ${__VERSION_CONFIG}) +include(${__VERSION_CONFIG}) + +# extract major, minor, patch version from git tag +string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" VERSION_MAJOR "${VCS_TAG}") +string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${VCS_TAG}") +string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${VCS_TAG}") +string(REGEX REPLACE "[T\\:\\+\\-]" "" VERSION_DATE "${VCS_DATE}") + +if(VERSION_DAILY_BUILD) + set(VERSION_PATCH ${VERSION_PATCH}.${VERSION_DATE}) +endif() + +if(NOT VERSION_MAJOR) + set(VERSION_MAJOR 1) +endif() + +if(NOT VERSION_MINOR) + set(VERSION_MINOR 0) +endif() + +if(NOT VERSION_PATCH) + set(VERSION_PATCH 0) +endif() + +set(DESCRIBE "${VCS_SHORT_HASH}") +set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") +set(GIT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${DESCRIBE}") + +# Replace .- with _ +string(REGEX REPLACE "[\\.\\-]" "_" VAR_VERSION "${GIT_VERSION}") + +# print information +message(STATUS "Welcome to Packet Adapter, Version: ${GIT_VERSION}") +add_definitions(-DGIT_VERSION=\"${GIT_VERSION}\") +add_definitions(-DVAR_VERSION=${VAR_VERSION}) \ No newline at end of file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..c9d83f3 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(common src/decode_ipv4.c src/decode_ipv6.c src/decode_tcp.c src/decode_udp.c src/decode_gtp.c) +target_include_directories(common PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) \ No newline at end of file diff --git a/common/include/decode_gtp.h b/common/include/decode_gtp.h new file mode 100644 index 0000000..87d9fd9 --- /dev/null +++ b/common/include/decode_gtp.h @@ -0,0 +1,36 @@ +#ifndef _DECODE_GTP_H +#define _DECODE_GTP_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + + /* According to 3GPP TS 29.060. */ + typedef struct gtp1_header_s + { + uint8_t flags; + uint8_t type; + uint16_t length; + uint32_t tid; + } __attribute__((packed)) gtp1_header_t; + + typedef struct gtp_info_s + { + gtp1_header_t *hdr; + uint8_t *payload; + + uint32_t hdr_len; + uint32_t payload_len; + } gtp_info_t; + + int decode_gtp(gtp_info_t *packet, const uint8_t *data, uint32_t len); + void dump_gtp_info(uint32_t pkt_id, gtp_info_t *packet); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/decode_ipv4.h b/common/include/decode_ipv4.h new file mode 100644 index 0000000..f3e3fa8 --- /dev/null +++ b/common/include/decode_ipv4.h @@ -0,0 +1,55 @@ +#ifndef _DECODE_IPV4_H +#define _DECODE_IPV4_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + +#define IPV4_HEADER_LEN 20 + + typedef struct ipv4_header_s + { + uint8_t ip_verhl; // version & header length + uint8_t ip_tos; + uint16_t ip_len; + uint16_t ip_id; + uint16_t ip_off; + uint8_t ip_ttl; + uint8_t ip_proto; + uint16_t ip_csum; + union + { + struct + { + struct in_addr ip_src; + struct in_addr ip_dst; + } ip4_un1; + uint16_t ip_addrs[4]; + } ip4_hdrun1; + } __attribute__((__packed__)) ipv4_header_t; + + typedef struct ipv4_info_s + { + char src_addr[INET_ADDRSTRLEN]; + char dst_addr[INET_ADDRSTRLEN]; + + ipv4_header_t *hdr; + uint8_t *payload; + uint8_t next_protocol; + + uint32_t hdr_len; + uint32_t opts_len; + uint32_t payload_len; + } ipv4_info_t; + + int decode_ipv4(ipv4_info_t *packet, const uint8_t *data, uint32_t len); + void dump_ipv4_info(uint32_t pkt_id, ipv4_info_t *packet); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/decode_ipv6.h b/common/include/decode_ipv6.h new file mode 100644 index 0000000..9bb0d36 --- /dev/null +++ b/common/include/decode_ipv6.h @@ -0,0 +1,58 @@ +#ifndef _DECODE_IPV6_H +#define _DECODE_IPV6_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + +#define IPV6_HEADER_LEN 40 + + typedef struct ipv6_header_s + { + union + { + struct ip6_un1_ + { + uint32_t ip6_un1_flow; /* 20 bits of flow-ID */ + uint16_t ip6_un1_plen; /* payload length */ + uint8_t ip6_un1_nxt; /* next header */ + uint8_t ip6_un1_hlim; /* hop limit */ + } ip6_un1; + uint8_t ip6_un2_vfc; /* 4 bits version, top 4 bits class */ + } ip6_hdrun; + + union + { + struct + { + uint32_t ip6_src[4]; + uint32_t ip6_dst[4]; + } ip6_un2; + uint16_t ip6_addrs[16]; + } ip6_hdrun2; + } __attribute__((__packed__)) ipv6_header_t; + + typedef struct ipv6_info_s + { + char src_addr[INET6_ADDRSTRLEN]; + char dst_addr[INET6_ADDRSTRLEN]; + + ipv6_header_t *hdr; + uint8_t *payload; + uint8_t next_protocol; + + uint32_t hdr_len; + uint32_t payload_len; + } ipv6_info_t; + + int decode_ipv6(ipv6_info_t *packet, const uint8_t *data, uint32_t len); + void dump_ipv6_info(uint32_t pkt_id, ipv6_info_t *packet); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/decode_tcp.h b/common/include/decode_tcp.h new file mode 100644 index 0000000..27cc14b --- /dev/null +++ b/common/include/decode_tcp.h @@ -0,0 +1,46 @@ +#ifndef _DECODE_TCP_H +#define _DECODE_TCP_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + +#define TCP_HEADER_LEN 20 + + typedef struct tcp_header_s + { + uint16_t th_sport; /**< source port */ + uint16_t th_dport; /**< destination port */ + uint32_t th_seq; /**< sequence number */ + uint32_t th_ack; /**< acknowledgement number */ + uint8_t th_offx2; /**< offset and reserved */ + uint8_t th_flags; /**< pkt flags */ + uint16_t th_win; /**< pkt window */ + uint16_t th_sum; /**< checksum */ + uint16_t th_urp; /**< urgent pointer */ + } __attribute__((__packed__)) tcp_header_t; + + typedef struct tcp_info_s + { + uint16_t src_port; + uint16_t dst_port; + + tcp_header_t *hdr; + uint8_t *payload; + + uint32_t opt_len; + uint32_t hdr_len; + uint32_t payload_len; + } tcp_info_t; + + int decode_tcp(tcp_info_t *packet, const uint8_t *data, uint32_t len); + void dump_tcp_info(uint32_t pkt_id, tcp_info_t *packet); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/common/include/decode_udp.h b/common/include/decode_udp.h new file mode 100644 index 0000000..7f8ba45 --- /dev/null +++ b/common/include/decode_udp.h @@ -0,0 +1,40 @@ +#ifndef _DECODE_UDP_H +#define _DECODE_UDP_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + +#define UDP_HEADER_LEN 8 + + typedef struct udp_header_s + { + uint16_t udp_src_port; + uint16_t udp_dst_port; + uint16_t udp_len; + uint16_t udp_sum; + } __attribute__((__packed__)) udp_header_t; + + typedef struct udp_info_s + { + uint16_t src_port; + uint16_t dst_port; + + udp_header_t *hdr; + uint8_t *payload; + + uint32_t hdr_len; + uint32_t payload_len; + } udp_info_t; + + int decode_udp(udp_info_t *packet, const uint8_t *data, uint32_t len); + void dump_udp_info(uint32_t pkt_id, udp_info_t *packet); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/log.h b/common/include/public.h similarity index 72% rename from log.h rename to common/include/public.h index bd033e6..60f3048 100644 --- a/log.h +++ b/common/include/public.h @@ -1,5 +1,5 @@ -#ifndef _LOG_H -#define _LOG_H +#ifndef _PUBLIC_H +#define _PUBLIC_H #ifdef __cpluscplus extern "C" @@ -7,8 +7,16 @@ extern "C" #endif #include +#include +#include +#include +#include +#include +#include +#include #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define IP_GET_RAW_VER(raw_pkt) ((((raw_pkt)[0] & 0xf0) >> 4)) #define PRINT_FILE_INFO 0 diff --git a/common/src/decode_gtp.c b/common/src/decode_gtp.c new file mode 100644 index 0000000..92dc528 --- /dev/null +++ b/common/src/decode_gtp.c @@ -0,0 +1,56 @@ +#include "decode_gtp.h" + +#define GTP_TPDU 255 +#define GTP1U_PORT 2152 +#define GTP1_F_MASK 0x07 + +#define GTP1_GET_TYPE(gtp1_hdr) ((gtp1_hdr)->type) +#define GTP1_GET_FLAGS(gtp1_hdr) ((gtp1_hdr)->flags >> 5) +#define GTP1_GET_HLEN(gtp1_hdr) (((gtp1_hdr)->flags & GTP1_F_MASK) > 0 ? 12 : 8) + +enum gtp_version_e +{ + GTP_V0 = 0, + GTP_V1, +}; + +int decode_gtp(gtp_info_t *packet, const uint8_t *data, uint32_t len) +{ + if (len < sizeof(gtp1_header_t)) + { + LOG_ERROR("Parser GTP Header: packet length too small %d", len); + return -1; + } + + packet->hdr = (gtp1_header_t *)data; + if (GTP1_GET_FLAGS(packet->hdr) != GTP_V1) + { + LOG_ERROR("Parser GTP Header: invalid gtp flags %d", GTP1_GET_FLAGS(packet->hdr)); + return -1; + } + + if (GTP1_GET_TYPE(packet->hdr) != GTP_TPDU) + { + LOG_ERROR("Parser GTP Header: invalid gtp type %d", GTP1_GET_TYPE(packet->hdr)); + return -1; + } + + /* From 29.060: "This field shall be present if and only if any one or + * more of the S, PN and E flags are set.". + * + * If any of the bit is set, then the remaining ones also have to be set. + */ + packet->hdr_len = GTP1_GET_HLEN(packet->hdr); + packet->payload = (uint8_t *)data + packet->hdr_len; + packet->payload_len = len - packet->hdr_len; + + return 0; +} + +void dump_gtp_info(uint32_t pkt_id, gtp_info_t *packet) +{ + LOG_DEBUG("id: %u, gtp_info: {hdr_len: %u, data_len: %u}", + pkt_id, + packet->hdr_len, + packet->payload_len); +} \ No newline at end of file diff --git a/common/src/decode_ipv4.c b/common/src/decode_ipv4.c new file mode 100644 index 0000000..8b98975 --- /dev/null +++ b/common/src/decode_ipv4.c @@ -0,0 +1,68 @@ +#include "decode_ipv4.h" + +#define IPV4_GET_HLEN(ip4_hdr) (((ip4_hdr)->ip_verhl & 0x0f) << 2) +#define IPV4_GET_IPPROTO(ip4_hdr) ((ip4_hdr)->ip_proto) +#define IPV4_GET_IPLEN(ip4_hdr) ((uint16_t)ntohs((ip4_hdr)->ip_len)) +#define IPV4_GET_SRC_ADDR(ip4_hdr) ((ip4_hdr)->ip4_hdrun1.ip4_un1.ip_src) +#define IPV4_GET_DST_ADDR(ip4_hdr) ((ip4_hdr)->ip4_hdrun1.ip4_un1.ip_dst) + +int decode_ipv4(ipv4_info_t *packet, const uint8_t *data, uint32_t len) +{ + // 检查包长是否大于 IPv4 header + if (len < IPV4_HEADER_LEN) + { + LOG_ERROR("Parser IPv4 Header: packet length too small %d", len); + return -1; + } + + // 检查 IPv4 header version + if (IP_GET_RAW_VER(data) != 4) + { + LOG_ERROR("Parser IPv4 Header: invalid IP version %d", IP_GET_RAW_VER(data)); + return -1; + } + + packet->hdr = (ipv4_header_t *)data; + // 检查 IPv4 header length + if (IPV4_GET_HLEN(packet->hdr) < IPV4_HEADER_LEN) + { + LOG_ERROR("Parser IPv4 Header: invalid IP header length %d", IPV4_GET_HLEN(packet->hdr)); + return -1; + } + + // 检查 IPv4 header total length + if (IPV4_GET_IPLEN(packet->hdr) < IPV4_GET_HLEN(packet->hdr)) + { + LOG_ERROR("Parser IPv4 Header: invalid IP header total length %d", IPV4_GET_IPLEN(packet->hdr)); + return -1; + } + + // 检查是否 IP 分片 + if (len < IPV4_GET_IPLEN(packet->hdr)) + { + LOG_ERROR("Parser IPv4 Header: trunc packet"); + return -1; + } + + inet_ntop(AF_INET, &IPV4_GET_SRC_ADDR(packet->hdr), packet->src_addr, sizeof(packet->src_addr)); + inet_ntop(AF_INET, &IPV4_GET_DST_ADDR(packet->hdr), packet->dst_addr, sizeof(packet->dst_addr)); + + packet->next_protocol = IPV4_GET_IPPROTO(packet->hdr); + packet->hdr_len = IPV4_GET_HLEN(packet->hdr); + packet->opts_len = packet->hdr_len - IPV4_HEADER_LEN; + packet->payload_len = len - packet->hdr_len; + packet->payload = (uint8_t *)data + packet->hdr_len; + + return 0; +} + +void dump_ipv4_info(uint32_t pkt_id, ipv4_info_t *packet) +{ + LOG_DEBUG("id: %u, ipv4_info: {src_addr: %s, dst_addr: %s, hdr_len: %u, opt_len: %u, data_len: %u}", + pkt_id, + packet->src_addr, + packet->dst_addr, + packet->hdr_len, + packet->opts_len, + packet->payload_len); +} \ No newline at end of file diff --git a/common/src/decode_ipv6.c b/common/src/decode_ipv6.c new file mode 100644 index 0000000..a583d54 --- /dev/null +++ b/common/src/decode_ipv6.c @@ -0,0 +1,55 @@ +#include "decode_ipv6.h" + +#define IPV6_GET_PLEN(ip6_hdr) ((uint16_t)ntohs((ip6_hdr)->ip6_hdrun.ip6_un1.ip6_un1_plen)) +#define IPV6_GET_NH(ip6_hdr) ((ip6_hdr)->ip6_hdrun.ip6_un1.ip6_un1_nxt) +#define IPV6_GET_SRC_ADDR(ip6_hdr) ((ip6_hdr)->ip6_hdrun2.ip6_un2.ip6_src) +#define IPV6_GET_DST_ADDR(ip6_hdr) ((ip6_hdr)->ip6_hdrun2.ip6_un2.ip6_dst) + +int decode_ipv6(ipv6_info_t *packet, const uint8_t *data, uint32_t len) +{ + if (len < IPV6_HEADER_LEN) + { + LOG_ERROR("Parser IPv6 Header: packet length too small %d", len); + return -1; + } + + // 检查 IPv6 header version + if (IP_GET_RAW_VER(data) != 6) + { + LOG_ERROR("Parser IPv6 Header: invalid IP version %d", IP_GET_RAW_VER(data)); + return -1; + } + + packet->hdr = (ipv6_header_t *)data; + if (len < (IPV6_HEADER_LEN + IPV6_GET_PLEN(packet->hdr))) + { + LOG_ERROR("Parser IPv6 Header: trunc packet"); + return -1; + } + + if (len != (IPV6_HEADER_LEN + IPV6_GET_PLEN(packet->hdr))) + { + LOG_ERROR("Parser IPv6 Header: invalid payload length %d", IPV6_GET_PLEN(packet->hdr)); + return -1; + } + + inet_ntop(AF_INET6, &IPV6_GET_SRC_ADDR(packet->hdr), packet->src_addr, sizeof(packet->src_addr)); + inet_ntop(AF_INET6, &IPV6_GET_DST_ADDR(packet->hdr), packet->dst_addr, sizeof(packet->dst_addr)); + + packet->next_protocol = IPV6_GET_NH(packet->hdr); + packet->hdr_len = IPV6_HEADER_LEN; + packet->payload = (uint8_t *)data + packet->hdr_len; + packet->payload_len = len - packet->hdr_len; + + return 0; +} + +void dump_ipv6_info(uint32_t pkt_id, ipv6_info_t *packet) +{ + LOG_DEBUG("id: %u, ipv6_info: {src_addr: %s, dst_addr: %s, hdr_len: %u, data_len: %u}", + pkt_id, + packet->src_addr, + packet->dst_addr, + packet->hdr_len, + packet->payload_len); +} \ No newline at end of file diff --git a/common/src/decode_tcp.c b/common/src/decode_tcp.c new file mode 100644 index 0000000..75a1ab8 --- /dev/null +++ b/common/src/decode_tcp.c @@ -0,0 +1,52 @@ +#include "decode_tcp.h" + +#define TCP_OPTLENMAX 40 + +#define TCP_GET_HLEN(tcp_hdr) ((((tcp_hdr)->th_offx2 & 0xf0) >> 4) << 2) +#define TCP_GET_SRC_PORT(tcp_hdr) ((uint16_t)ntohs((tcp_hdr)->th_sport)) +#define TCP_GET_DST_PORT(tcp_hdr) ((uint16_t)ntohs((tcp_hdr)->th_dport)) + +int decode_tcp(tcp_info_t *packet, const uint8_t *data, uint32_t len) +{ + if (len < TCP_HEADER_LEN) + { + LOG_ERROR("Parser TCP Header: packet length too small %d", len); + return -1; + } + + packet->hdr = (tcp_header_t *)data; + uint8_t hlen = TCP_GET_HLEN(packet->hdr); + if (len < hlen) + { + LOG_ERROR("Parser TCP Header: TCP packet too small %d", len); + return -1; + } + + uint8_t tcp_opt_len = hlen - TCP_HEADER_LEN; + if (tcp_opt_len > TCP_OPTLENMAX) + { + LOG_ERROR("Parser TCP Header: invalid opt length %d", tcp_opt_len); + return -1; + } + + packet->opt_len = tcp_opt_len; + packet->src_port = TCP_GET_SRC_PORT(packet->hdr); + packet->dst_port = TCP_GET_DST_PORT(packet->hdr); + + packet->hdr_len = hlen; + packet->payload = (uint8_t *)data + packet->hdr_len; + packet->payload_len = len - packet->hdr_len; + + return 0; +} + +void dump_tcp_info(uint32_t pkt_id, tcp_info_t *packet) +{ + LOG_DEBUG("id: %u, tcp_info: {src_port: %u, dst_port: %u, hdr_len: %u, opt_len: %u, data_len:%u}", + pkt_id, + packet->src_port, + packet->dst_port, + packet->hdr_len, + packet->opt_len, + packet->payload_len); +} \ No newline at end of file diff --git a/common/src/decode_udp.c b/common/src/decode_udp.c new file mode 100644 index 0000000..bebe337 --- /dev/null +++ b/common/src/decode_udp.c @@ -0,0 +1,48 @@ +#include "decode_udp.h" + +#define UDP_GET_LEN(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_len)) +#define UDP_GET_SRC_PORT(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_src_port)) +#define UDP_GET_DST_PORT(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_dst_port)) + +int decode_udp(udp_info_t *packet, const uint8_t *data, uint32_t len) +{ + if (len < UDP_HEADER_LEN) + { + LOG_ERROR("Parser UDP Header: packet length too small %d", len); + return -1; + } + + packet->hdr = (udp_header_t *)data; + // 检查 UDP header len + if (len < UDP_GET_LEN(packet->hdr)) + { + LOG_ERROR("Parser UDP Header: UDP packet too small %d", len); + return -1; + } + + // 检查 UDP header len + if (len != UDP_GET_LEN(packet->hdr)) + { + LOG_ERROR("Parser UDP Header: invalid UDP header length %d", UDP_GET_LEN(packet->hdr)); + return -1; + } + + packet->src_port = UDP_GET_SRC_PORT(packet->hdr); + packet->dst_port = UDP_GET_DST_PORT(packet->hdr); + + packet->hdr_len = UDP_HEADER_LEN; + packet->payload = (uint8_t *)data + UDP_HEADER_LEN; + packet->payload_len = len - UDP_HEADER_LEN; + + return 0; +} + +void dump_udp_info(uint32_t pkt_id, udp_info_t *packet) +{ + LOG_DEBUG("id: %u, udp_info: {src_port: %u, dst_port: %u, hdr_len: %u, data_len: %u}", + pkt_id, + packet->src_port, + packet->dst_port, + packet->hdr_len, + packet->payload_len); +} diff --git a/nfqnl_test.c b/nfqnl_test.c deleted file mode 100644 index a6b20c8..0000000 --- a/nfqnl_test.c +++ /dev/null @@ -1,869 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include // for NF_ACCEPT -#include - -#include "log.h" - -#define GTP_TPDU 255 -#define GTP1U_PORT 2152 -#define GTP1_F_MASK 0x07 - -#define UDP_HEADER_LEN 8 -#define TCP_HEADER_LEN 20 -#define TCP_OPTLENMAX 40 - -#define IPV4_HEADER_LEN 20 -#define IPV6_HEADER_LEN 40 - -#define IP_GET_RAW_VER(raw_pkt) ((((raw_pkt)[0] & 0xf0) >> 4)) - -#define IPV6_GET_PLEN(ip6_hdr) ((uint16_t)ntohs((ip6_hdr)->ip6_hdrun.ip6_un1.ip6_un1_plen)) -#define IPV6_GET_NH(ip6_hdr) ((ip6_hdr)->ip6_hdrun.ip6_un1.ip6_un1_nxt) -#define IPV6_GET_SRC_ADDR(ip6_hdr) ((ip6_hdr)->ip6_hdrun2.ip6_un2.ip6_src) -#define IPV6_GET_DST_ADDR(ip6_hdr) ((ip6_hdr)->ip6_hdrun2.ip6_un2.ip6_dst) - -#define IPV4_GET_HLEN(ip4_hdr) (((ip4_hdr)->ip_verhl & 0x0f) << 2) -#define IPV4_GET_IPPROTO(ip4_hdr) ((ip4_hdr)->ip_proto) -#define IPV4_GET_IPLEN(ip4_hdr) ((uint16_t)ntohs((ip4_hdr)->ip_len)) -#define IPV4_GET_SRC_ADDR(ip4_hdr) ((ip4_hdr)->ip4_hdrun1.ip4_un1.ip_src) -#define IPV4_GET_DST_ADDR(ip4_hdr) ((ip4_hdr)->ip4_hdrun1.ip4_un1.ip_dst) - -#define UDP_GET_LEN(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_len)) -#define UDP_GET_SRC_PORT(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_src_port)) -#define UDP_GET_DST_PORT(udp_hdr) ((uint16_t)ntohs((udp_hdr)->udp_dst_port)) - -#define TCP_GET_HLEN(tcp_hdr) ((((tcp_hdr)->th_offx2 & 0xf0) >> 4) << 2) -#define TCP_GET_SRC_PORT(tcp_hdr) ((uint16_t)ntohs((tcp_hdr)->th_sport)) -#define TCP_GET_DST_PORT(tcp_hdr) ((uint16_t)ntohs((tcp_hdr)->th_dport)) - -#define GTP1_GET_TYPE(gtp1_hdr) ((gtp1_hdr)->type) -#define GTP1_GET_FLAGS(gtp1_hdr) ((gtp1_hdr)->flags >> 5) -#define GTP1_GET_HLEN(gtp1_hdr) (((gtp1_hdr)->flags & GTP1_F_MASK) > 0 ? 12 : 8) - -/////////////////////////////////////////////////////////////////////////////// -// IPv4 IPv6 UDP GPT Header Struct -/////////////////////////////////////////////////////////////////////////////// - -typedef struct ipv6_header_s -{ - union - { - struct ip6_un1_ - { - uint32_t ip6_un1_flow; /* 20 bits of flow-ID */ - uint16_t ip6_un1_plen; /* payload length */ - uint8_t ip6_un1_nxt; /* next header */ - uint8_t ip6_un1_hlim; /* hop limit */ - } ip6_un1; - uint8_t ip6_un2_vfc; /* 4 bits version, top 4 bits class */ - } ip6_hdrun; - - union - { - struct - { - uint32_t ip6_src[4]; - uint32_t ip6_dst[4]; - } ip6_un2; - uint16_t ip6_addrs[16]; - } ip6_hdrun2; -} ipv6_header_t; - -typedef struct ipv4_header_s -{ - uint8_t ip_verhl; // version & header length - uint8_t ip_tos; - uint16_t ip_len; - uint16_t ip_id; - uint16_t ip_off; - uint8_t ip_ttl; - uint8_t ip_proto; - uint16_t ip_csum; - union - { - struct - { - struct in_addr ip_src; - struct in_addr ip_dst; - } ip4_un1; - uint16_t ip_addrs[4]; - } ip4_hdrun1; -} ipv4_header_t; - -typedef struct tcp_header_s -{ - uint16_t th_sport; /**< source port */ - uint16_t th_dport; /**< destination port */ - uint32_t th_seq; /**< sequence number */ - uint32_t th_ack; /**< acknowledgement number */ - uint8_t th_offx2; /**< offset and reserved */ - uint8_t th_flags; /**< pkt flags */ - uint16_t th_win; /**< pkt window */ - uint16_t th_sum; /**< checksum */ - uint16_t th_urp; /**< urgent pointer */ -} __attribute__((__packed__)) tcp_header_t; - -typedef struct udp_header_s -{ - uint16_t udp_src_port; - uint16_t udp_dst_port; - uint16_t udp_len; - uint16_t udp_sum; -} __attribute__((__packed__)) udp_header_t; - -enum gtp_version_e -{ - GTP_V0 = 0, - GTP_V1, -}; - -/* According to 3GPP TS 29.060. */ -typedef struct gtp1_header_s -{ - uint8_t flags; - uint8_t type; - uint16_t length; - uint32_t tid; -} __attribute__((packed)) gtp1_header_t; - -/////////////////////////////////////////////////////////////////////////////// -// IPv4 IPv6 UDP GPT Header Parser -/////////////////////////////////////////////////////////////////////////////// - -typedef enum network_mode_s -{ - NONE = 0, - IPv4 = 1, - IPv6 = 2, - TCP = 3, - UDP = 4, -} network_mode_t; - -typedef struct pkt_info_s -{ - uint32_t id; - uint8_t *payload; - uint32_t payload_len; -} pkt_info_t; - -typedef struct ipv4_info_s -{ - char src_addr[INET_ADDRSTRLEN]; - char dst_addr[INET_ADDRSTRLEN]; - - ipv4_header_t *hdr; - uint8_t *payload; - - uint32_t hdr_len; - uint32_t opts_len; - uint32_t payload_len; -} ipv4_info_t; - -typedef struct ipv6_info_s -{ - char src_addr[INET6_ADDRSTRLEN]; - char dst_addr[INET6_ADDRSTRLEN]; - - ipv6_header_t *hdr; - uint8_t *payload; - - uint32_t hdr_len; - uint32_t payload_len; -} ipv6_info_t; - -typedef struct udp_info_s -{ - uint16_t src_port; - uint16_t dst_port; - - udp_header_t *hdr; - uint8_t *payload; - - uint32_t hdr_len; - uint32_t payload_len; -} udp_info_t; - -typedef struct tcp_info_s -{ - uint16_t src_port; - uint16_t dst_port; - - tcp_header_t *hdr; - uint8_t *payload; - - uint32_t opt_len; - uint32_t hdr_len; - uint32_t payload_len; -} tcp_info_t; - -typedef struct gtp_info_s -{ - gtp1_header_t *hdr; - uint8_t *payload; - - uint32_t hdr_len; - uint32_t payload_len; -} gtp_info_t; - -typedef struct pkt_paser_s -{ - pkt_info_t raw_pkt; - - ipv4_info_t external_ipv4; - ipv6_info_t external_ipv6; - udp_info_t external_udp; - gtp_info_t external_gtp; - ipv4_info_t internal_ipv4; - ipv6_info_t internal_ipv6; - udp_info_t internal_udp; - tcp_info_t internal_tcp; - - network_mode_t external_ip_version; - network_mode_t external_l4_version; - network_mode_t internal_ip_version; - network_mode_t internal_l4_version; - -} pkt_paser_t; - -/////////////////////////////////////////////////////////////////////////////// -// Packet Parser API -/////////////////////////////////////////////////////////////////////////////// - -static void dump_info(pkt_paser_t *pkt_parser) -{ - - LOG_DEBUG("raw_pkt: {id: %u, data_len: %u}", pkt_parser->raw_pkt.id, pkt_parser->raw_pkt.payload_len); - - if (pkt_parser->external_ipv4.hdr) - { - LOG_DEBUG("id: %u, external_ipv4: {src_addr:%s,dst_addr:%s,hdr_len: %u, opt_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->external_ipv4.src_addr, - pkt_parser->external_ipv4.dst_addr, - pkt_parser->external_ipv4.hdr_len, - pkt_parser->external_ipv4.opts_len, - pkt_parser->external_ipv4.payload_len); - } - if (pkt_parser->external_ipv6.hdr) - { - LOG_DEBUG("id: %u, external_ipv6: {src_addr:%s,dst_addr:%s,hdr_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->external_ipv6.src_addr, - pkt_parser->external_ipv6.dst_addr, - pkt_parser->external_ipv6.hdr_len, - pkt_parser->external_ipv6.payload_len); - } - if (pkt_parser->external_udp.hdr) - { - LOG_DEBUG("id: %u, external_udp: {src_port: %u, dst_port: %u, hdr_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->external_udp.src_port, - pkt_parser->external_udp.dst_port, - pkt_parser->external_udp.hdr_len, - pkt_parser->external_udp.payload_len); - } - - if (pkt_parser->external_gtp.hdr) - { - LOG_DEBUG("id: %u, external_gtp: {hdr_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->external_gtp.hdr_len, - pkt_parser->external_gtp.payload_len); - } - if (pkt_parser->internal_ipv4.hdr) - { - LOG_DEBUG("id: %u, internal_ipv4: {src_addr:%s,dst_addr:%s,hdr_len: %u, opt_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->internal_ipv4.src_addr, - pkt_parser->internal_ipv4.dst_addr, - pkt_parser->internal_ipv4.hdr_len, - pkt_parser->internal_ipv4.opts_len, - pkt_parser->internal_ipv4.payload_len); - } - if (pkt_parser->internal_ipv6.hdr) - { - LOG_DEBUG("id: %u, interna_ipv6: {src_addr:%s,dst_addr:%s,hdr_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->internal_ipv6.src_addr, - pkt_parser->internal_ipv6.dst_addr, - pkt_parser->internal_ipv6.hdr_len, - pkt_parser->internal_ipv6.payload_len); - } - if (pkt_parser->internal_udp.hdr) - { - LOG_DEBUG("id: %u, internal_udp: {src_port: %u, dst_port: %u, hdr_len: %u, data_len: %u}", - pkt_parser->raw_pkt.id, - pkt_parser->internal_udp.src_port, - pkt_parser->internal_udp.dst_port, - pkt_parser->internal_udp.hdr_len, - pkt_parser->internal_udp.payload_len); - } - if (pkt_parser->internal_tcp.hdr) - { - LOG_DEBUG("id: %u, internal_tcp: {src_port: %u, dst_port: %u, hdr_len: %u, data_len:%u}", - pkt_parser->raw_pkt.id, - pkt_parser->internal_tcp.src_port, - pkt_parser->internal_tcp.dst_port, - pkt_parser->internal_tcp.hdr_len, - pkt_parser->internal_tcp.payload_len); - } -} - -static int decode_gtp(gtp_info_t *packet, const uint8_t *data, uint32_t len) -{ - if (len < sizeof(gtp1_header_t)) - { - LOG_ERROR("Parser GTP Header: packet length too small %d", len); - return -1; - } - - packet->hdr = (gtp1_header_t *)data; - if (GTP1_GET_FLAGS(packet->hdr) != GTP_V1) - { - LOG_ERROR("Parser GTP Header: invalid gtp flags %d", GTP1_GET_FLAGS(packet->hdr)); - return -1; - } - - if (GTP1_GET_TYPE(packet->hdr) != GTP_TPDU) - { - LOG_ERROR("Parser GTP Header: invalid gtp type %d", GTP1_GET_TYPE(packet->hdr)); - return -1; - } - - /* From 29.060: "This field shall be present if and only if any one or - * more of the S, PN and E flags are set.". - * - * If any of the bit is set, then the remaining ones also have to be set. - */ - packet->hdr_len = GTP1_GET_HLEN(packet->hdr); - packet->payload = (uint8_t *)data + packet->hdr_len; - packet->payload_len = len - packet->hdr_len; - - return 0; -} - -static int decode_udp(udp_info_t *packet, const uint8_t *data, uint32_t len) -{ - if (len < UDP_HEADER_LEN) - { - LOG_ERROR("Parser UDP Header: packet length too small %d", len); - return -1; - } - - packet->hdr = (udp_header_t *)data; - // 检查 UDP header len - if (len < UDP_GET_LEN(packet->hdr)) - { - LOG_ERROR("Parser UDP Header: UDP packet too small %d", len); - return -1; - } - - // 检查 UDP header len - if (len != UDP_GET_LEN(packet->hdr)) - { - LOG_ERROR("Parser UDP Header: invalid UDP header length %d", UDP_GET_LEN(packet->hdr)); - return -1; - } - - packet->src_port = UDP_GET_SRC_PORT(packet->hdr); - packet->dst_port = UDP_GET_DST_PORT(packet->hdr); - - packet->hdr_len = UDP_HEADER_LEN; - packet->payload = (uint8_t *)data + UDP_HEADER_LEN; - packet->payload_len = len - UDP_HEADER_LEN; - - return 0; -} - -static int decode_tcp(tcp_info_t *packet, const uint8_t *data, uint32_t len) -{ - if (len < TCP_HEADER_LEN) - { - LOG_ERROR("Parser TCP Header: packet length too small %d", len); - return -1; - } - - packet->hdr = (tcp_header_t *)data; - uint8_t hlen = TCP_GET_HLEN(packet->hdr); - if (len < hlen) - { - LOG_ERROR("Parser TCP Header: TCP packet too small %d", len); - return -1; - } - - uint8_t tcp_opt_len = hlen - TCP_HEADER_LEN; - if (tcp_opt_len > TCP_OPTLENMAX) - { - LOG_ERROR("Parser TCP Header: invalid opt length %d", tcp_opt_len); - return -1; - } - - packet->opt_len = tcp_opt_len; - packet->src_port = TCP_GET_SRC_PORT(packet->hdr); - packet->dst_port = TCP_GET_DST_PORT(packet->hdr); - - packet->hdr_len = hlen; - packet->payload = (uint8_t *)data + packet->hdr_len; - packet->payload_len = len = packet->hdr_len; - - return 0; -} - -static int decode_ipv6(ipv6_info_t *packet, const uint8_t *data, uint32_t len) -{ - if (len < IPV6_HEADER_LEN) - { - LOG_ERROR("Parser IPv6 Header: packet length too small %d", len); - return -1; - } - - // 检查 IPv6 header version - if (IP_GET_RAW_VER(data) != 6) - { - LOG_ERROR("Parser IPv6 Header: unknown IP version %d", IP_GET_RAW_VER(data)); - return -1; - } - - packet->hdr = (ipv6_header_t *)data; - if (len < (IPV6_HEADER_LEN + IPV6_GET_PLEN(packet->hdr))) - { - LOG_ERROR("Parser IPv6 Header: trunc packet"); - return -1; - } - - if (len != (IPV6_HEADER_LEN + IPV6_GET_PLEN(packet->hdr))) - { - LOG_ERROR("Parser IPv6 Header: invalid payload length %d", IPV6_GET_PLEN(packet->hdr)); - return -1; - } - - inet_ntop(AF_INET6, &IPV6_GET_SRC_ADDR(packet->hdr), packet->src_addr, sizeof(packet->src_addr)); - inet_ntop(AF_INET6, &IPV6_GET_DST_ADDR(packet->hdr), packet->dst_addr, sizeof(packet->dst_addr)); - - packet->hdr_len = IPV6_HEADER_LEN; - packet->payload = (uint8_t *)data + packet->hdr_len; - packet->payload_len = len - packet->hdr_len; - - return 0; -} - -static int decode_ipv4(ipv4_info_t *packet, const uint8_t *data, uint32_t len) -{ - // 检查包长是否大于 IPv4 header - if (len < IPV4_HEADER_LEN) - { - LOG_ERROR("Parser IPv4 Header: packet length too small %d", len); - return -1; - } - - // 检查 IPv4 header version - if (IP_GET_RAW_VER(data) != 4) - { - LOG_ERROR("Parser IPv4 Header: unknown IP version %d", IP_GET_RAW_VER(data)); - return -1; - } - - packet->hdr = (ipv4_header_t *)data; - // 检查 IPv4 header length - if (IPV4_GET_HLEN(packet->hdr) < IPV4_HEADER_LEN) - { - LOG_ERROR("Parser IPv4 Header: invalid IP header length %d", IPV4_GET_HLEN(packet->hdr)); - return -1; - } - - // 检查 IPv4 header total length - if (IPV4_GET_IPLEN(packet->hdr) < IPV4_GET_HLEN(packet->hdr)) - { - LOG_ERROR("Parser IPv4 Header: invalid IP header total length %d", IPV4_GET_IPLEN(packet->hdr)); - return -1; - } - - // 检查是否 IP 分片 - if (len < IPV4_GET_IPLEN(packet->hdr)) - { - LOG_ERROR("Parser IPv4 Header: trunc packet"); - return -1; - } - - inet_ntop(AF_INET, &IPV4_GET_SRC_ADDR(packet->hdr), packet->src_addr, sizeof(packet->src_addr)); - inet_ntop(AF_INET, &IPV4_GET_DST_ADDR(packet->hdr), packet->dst_addr, sizeof(packet->dst_addr)); - - packet->hdr_len = IPV4_GET_HLEN(packet->hdr); - packet->opts_len = packet->hdr_len - IPV4_HEADER_LEN; - packet->payload_len = len - packet->hdr_len; - packet->payload = (uint8_t *)data + packet->hdr_len; - - return 0; -} - -static int decode_internal_ip_pkt(pkt_paser_t *pkt_parser, const uint8_t *data, uint32_t len) -{ - int next_protocol = 0; - uint8_t *payload = NULL; - uint32_t payload_len = 0; - - // IPv4/IPv6 - if (len < IPV4_HEADER_LEN) - { - LOG_ERROR("Parser Internal Raw header: packet length too small %d", len); - return -1; - } - - if (IP_GET_RAW_VER(data) == 4) - { - if (decode_ipv4(&(pkt_parser->internal_ipv4), data, len) == -1) - { - return -1; - } - - pkt_parser->internal_ip_version = IPv4; - payload = pkt_parser->internal_ipv4.payload; - payload_len = pkt_parser->internal_ipv4.payload_len; - next_protocol = IPV4_GET_IPPROTO(pkt_parser->internal_ipv4.hdr); - } - else if (IP_GET_RAW_VER(data) == 6) - { - if (decode_ipv6(&(pkt_parser->internal_ipv6), data, len) == -1) - { - return -1; - } - pkt_parser->internal_ip_version = IPv6; - payload = pkt_parser->internal_ipv6.payload; - payload_len = pkt_parser->internal_ipv6.payload_len; - next_protocol = IPV6_GET_NH(pkt_parser->internal_ipv6.hdr); - } - else - { - LOG_ERROR("Unknown Internal IP version %d", IP_GET_RAW_VER(data)); - return -1; - } - - // TCP/UDP - if (next_protocol == IPPROTO_UDP) - { - if (decode_udp(&(pkt_parser->internal_udp), payload, payload_len) == -1) - { - return -1; - } - pkt_parser->internal_l4_version = UDP; - return 0; - } - else if (next_protocol == IPPROTO_TCP) - { - if (decode_tcp(&(pkt_parser->internal_tcp), payload, payload_len) == -1) - { - return -1; - } - pkt_parser->internal_l4_version = TCP; - return 0; - } - else - { - LOG_ERROR("Unknown Internal L4 next_protocol version %d", next_protocol); - return -1; - } -} - -static int decode_external_ip_pkt(pkt_paser_t *pkt_parser, const uint8_t *data, uint32_t len) -{ - int next_protocol = 0; - uint8_t *payload = NULL; - uint32_t payload_len = 0; - - // IPv4/IPv6 - if (len < IPV4_HEADER_LEN) - { - LOG_ERROR("Parser External Raw header: packet length too small %d", len); - return -1; - } - - if (IP_GET_RAW_VER(data) == 4) - { - if (decode_ipv4(&(pkt_parser->external_ipv4), data, len) == -1) - { - return -1; - } - pkt_parser->external_ip_version = IPv4; - payload = pkt_parser->external_ipv4.payload; - payload_len = pkt_parser->external_ipv4.payload_len; - next_protocol = IPV4_GET_IPPROTO(pkt_parser->external_ipv4.hdr); - } - else if (IP_GET_RAW_VER(data) == 6) - { - if (decode_ipv6(&(pkt_parser->external_ipv6), data, len) == -1) - { - return -1; - } - pkt_parser->external_ip_version = IPv6; - payload = pkt_parser->external_ipv6.payload; - payload_len = pkt_parser->external_ipv6.payload_len; - next_protocol = IPV6_GET_NH(pkt_parser->external_ipv6.hdr); - } - else - { - LOG_ERROR("Unknown External IP version %d", IP_GET_RAW_VER(data)); - return -1; - } - - // TCP/UDP - if (next_protocol == IPPROTO_UDP) - { - if (decode_udp(&(pkt_parser->external_udp), payload, payload_len) == -1) - { - return -1; - } - pkt_parser->external_l4_version = UDP; - } - else if (next_protocol == IPPROTO_TCP) - { - pkt_parser->external_l4_version = TCP; - LOG_ERROR("Unknown External L4 next_protocol version %d", next_protocol); - return -1; - } - else - { - LOG_ERROR("Unknown External L4 next_protocol version %d", next_protocol); - return -1; - } - - // GTP - if (decode_gtp(&(pkt_parser->external_gtp), pkt_parser->external_udp.payload, pkt_parser->external_udp.payload_len) == -1) - { - return -1; - } - - return 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// NFQ API -/////////////////////////////////////////////////////////////////////////////// - -/* - * nfmsg : message objetc that contains the packet - * nfa : Netlink packet data handle - */ -static int packet_handler_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) -{ - int offest = 0; - int raw_ip_fd = 0; - int packet_len = 0; - pkt_paser_t pkt_parser = {0}; - unsigned char *packet_data = NULL; - struct nfqnl_msg_packet_hdr *packet_hdr = NULL; - - packet_hdr = nfq_get_msg_packet_hdr(nfa); - if (packet_hdr == NULL) - { - LOG_ERROR("Failed at nfq_get_msg_packet_hdr()"); - goto end; - } - - packet_len = nfq_get_payload(nfa, &packet_data); - if (packet_len <= 0) - { - LOG_ERROR("Failed at nfq_get_payload()"); - goto end; - } - - pkt_parser.raw_pkt.id = ntohl(packet_hdr->packet_id); - pkt_parser.raw_pkt.payload = packet_data; - pkt_parser.raw_pkt.payload_len = packet_len; - if (decode_external_ip_pkt(&pkt_parser, pkt_parser.raw_pkt.payload, pkt_parser.raw_pkt.payload_len) == -1) - { - goto end; - } - - if (decode_internal_ip_pkt(&pkt_parser, pkt_parser.external_gtp.payload, pkt_parser.external_gtp.payload_len) == -1) - { - goto end; - } - - /* - * NF_DROP : discarded the packet - * NF_ACCEPT : the packet passes, continue iterations - * NF_QUEUE : inject the packet into a different queue (the target queue number is in the high 16 bits of the verdict) - * NF_REPEAT : iterate the same cycle once more - * NF_STOP : accept, but don't continue iterations - */ - // nfq_set_verdict() - // nfq_set_verdict2() - // nfq_set_verdict_batch() - // nfq_set_verdict_batch2() - // nfq_set_verdict_mark() - - if (pkt_parser.external_ip_version == IPv4) - { - offest += pkt_parser.external_ipv4.hdr_len; - } - if (pkt_parser.external_ip_version == IPv6) - { - offest += pkt_parser.external_ipv6.hdr_len; - } - - offest += pkt_parser.external_udp.hdr_len; - offest += pkt_parser.external_gtp.hdr_len; - - dump_info(&pkt_parser); - LOG_DEBUG("Offset : %d", offest); - - if (offest > 0) - { - if ((pkt_parser.external_ip_version == IPv4 && pkt_parser.internal_ip_version == IPv4) || (pkt_parser.external_ip_version == IPv6 && pkt_parser.internal_ip_version == IPv6)) - { - return nfq_set_verdict(qh, pkt_parser.raw_pkt.id, NF_ACCEPT, packet_len - offest, packet_data + offest); - } - if (pkt_parser.external_ip_version == IPv4 && pkt_parser.internal_ip_version == IPv6) - { - struct sockaddr_in6 saddr6 = {0}; - saddr6.sin6_family = PF_INET6; - memcpy(saddr6.sin6_addr.s6_addr, pkt_parser.internal_ipv6.hdr->ip6_hdrun2.ip6_un2.ip6_dst, sizeof(struct in6_addr)); - raw_ip_fd = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW); - if (sendto(raw_ip_fd, packet_data + offest, packet_len - offest, 0, (struct sockaddr *)&saddr6, sizeof(saddr6)) == -1) - { - LOG_ERROR("Failed at send(), %d: %s", errno, strerror(errno)); - close(raw_ip_fd); - goto end; - } - close(raw_ip_fd); - } - else if (pkt_parser.external_ip_version == IPv6 && pkt_parser.internal_ip_version == IPv4) - { - struct sockaddr_in saddr4 = {0}; - saddr4.sin_family = PF_INET; - saddr4.sin_addr.s_addr = inet_addr(pkt_parser.internal_ipv4.dst_addr); - raw_ip_fd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW); - if (sendto(raw_ip_fd, packet_data + offest, packet_len - offest, 0, (struct sockaddr *)&saddr4, sizeof(saddr4)) == -1) - { - LOG_ERROR("Failed at send(), %d: %s", errno, strerror(errno)); - close(raw_ip_fd); - goto end; - } - close(raw_ip_fd); - } - return nfq_set_verdict(qh, pkt_parser.raw_pkt.id, NF_DROP, 0, NULL); - } - -end: - return nfq_set_verdict(qh, pkt_parser.raw_pkt.id, NF_ACCEPT, 0, NULL); -} - -/* - * doc : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/ - * Library setup : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__LibrarySetup.html - * Queue handling : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__Queue.html - * Message parsing : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__Parsing.html - */ -int main(int argc, char **argv) -{ - int fd; - int rv; - uint32_t queue = 1; - struct nfq_handle *handle; - struct nfq_q_handle *q_handle; - char buf[4096] __attribute__((aligned)); - - if (argc == 2) - { - queue = atoi(argv[1]); - if (queue > 65535) - { - fprintf(stderr, "Usage: %s [<0-65535>]\n", argv[0]); - return 0; - } - } - LOG_DEBUG("Using queue: %d", queue); - - handle = nfq_open(); - if (handle == NULL) - { - LOG_ERROR("Failed at nfq_open(), %d: %s", errno, strerror(errno)); - goto error; - } - - if (nfq_unbind_pf(handle, AF_INET) < 0) - { - LOG_ERROR("Failed at nfq_unbind_pf(), %d: %s", errno, strerror(errno)); - goto error; - } - - if (nfq_bind_pf(handle, AF_INET) < 0) - { - LOG_ERROR("Failed at nfq_bind_pf(), %d: %s", errno, strerror(errno)); - goto error; - } - - q_handle = nfq_create_queue(handle, queue, &packet_handler_cb, NULL); - if (q_handle == NULL) - { - LOG_ERROR("Failed at nfq_create_queue(), %d: %s", errno, strerror(errno)); - goto error; - } - - /* - * NFQNL_COPY_NONE - noop, do not use it - * NFQNL_COPY_META - copy only packet metadata - * NFQNL_COPY_PACKET - copy entire packet - */ - if (nfq_set_mode(q_handle, NFQNL_COPY_PACKET, 0xffff) < 0) - { - LOG_ERROR("Failed at nfq_set_mode(NFQNL_COPY_PACKET), %d: %s", errno, strerror(errno)); - goto error; - } - - if (nfq_set_queue_maxlen(q_handle, 65535) < 0) - { - LOG_ERROR("Failed at nfq_set_queue_maxlen(65535), %d: %s", errno, strerror(errno)); - goto error; - } - - LOG_DEBUG("Waiting for packets..."); - - fd = nfq_fd(handle); - for (;;) - { - if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) - { - nfq_handle_packet(handle, buf, rv); - continue; - } - /* - * if your application is too slow to digest the packets that - * are sent from kernel-space, the socket buffer that we use - * to enqueue packets may fill up returning ENOBUFS. Depending - * on your application, this error may be ignored. Please, see - * the doxygen documentation of this library on how to improve - * this situation. - */ - if (rv < 0 && errno == ENOBUFS) - { - LOG_ERROR("Losing packets !!!"); - continue; - } - LOG_ERROR("Failed at recv(), %d: %s", errno, strerror(errno)); - break; - } - -error: - if (q_handle) - { - nfq_destroy_queue(q_handle); - } - - if (handle) - { - nfq_close(handle); - } - - return 0; -} diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt new file mode 100644 index 0000000..6037773 --- /dev/null +++ b/platform/CMakeLists.txt @@ -0,0 +1,10 @@ +find_package(NFNETLINK REQUIRED) + +add_executable(packetadapter src/inject_pkt.c src/system.c src/packet_adapter.c) + +target_include_directories(packetadapter PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/) + +target_link_libraries(packetadapter common) +target_link_libraries(packetadapter netfilter_queue) + +install(TARGETS packetadapter RUNTIME DESTINATION bin COMPONENT Program) \ No newline at end of file diff --git a/platform/include/inject_pkt.h b/platform/include/inject_pkt.h new file mode 100644 index 0000000..a89aece --- /dev/null +++ b/platform/include/inject_pkt.h @@ -0,0 +1,18 @@ +#ifndef _INJECT_PKT_H +#define _INJECT_PKT_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + + int inject_ipv4_pkt(char *ip4_addr, uint8_t *data, uint32_t len); + int inject_ipv6_pkt(char *ip6_addr, uint8_t *data, uint32_t len); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/platform/include/system.h b/platform/include/system.h new file mode 100644 index 0000000..bf20541 --- /dev/null +++ b/platform/include/system.h @@ -0,0 +1,17 @@ +#ifndef _SYSTEM_H +#define _SYSTEM_H + +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include "public.h" + + int run_daemon(void); + +#ifdef __cpluscplus +} +#endif + +#endif \ No newline at end of file diff --git a/platform/src/inject_pkt.c b/platform/src/inject_pkt.c new file mode 100644 index 0000000..9e336c0 --- /dev/null +++ b/platform/src/inject_pkt.c @@ -0,0 +1,53 @@ +#include "inject_pkt.h" + +int inject_ipv4_pkt(char *ip4_addr, uint8_t *data, uint32_t len) +{ + int fd = 0; + struct sockaddr_in saddr4 = {0}; + + saddr4.sin_family = PF_INET; + saddr4.sin_addr.s_addr = inet_addr(ip4_addr); + + fd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd == -1) + { + LOG_ERROR("Failed at socket(PF_INET, SOCK_RAW), %d: %s", errno, strerror(errno)); + return -1; + } + + if (sendto(fd, data, len, 0, (struct sockaddr *)&saddr4, sizeof(saddr4)) == -1) + { + LOG_ERROR("Failed at send(), %d: %s", errno, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} + +int inject_ipv6_pkt(char *ip6_addr, uint8_t *data, uint32_t len) +{ + int fd = 0; + struct sockaddr_in6 saddr6 = {0}; + + saddr6.sin6_family = PF_INET6; + inet_pton(AF_INET6, ip6_addr, &saddr6.sin6_addr); + + fd = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW); + if (fd == -1) + { + LOG_ERROR("Failed at socket(PF_INET6, SOCK_RAW), %d: %s", errno, strerror(errno)); + return -1; + } + + if (sendto(fd, data, len, 0, (struct sockaddr *)&saddr6, sizeof(saddr6)) == -1) + { + LOG_ERROR("Failed at send(), %d: %s", errno, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} \ No newline at end of file diff --git a/platform/src/packet_adapter.c b/platform/src/packet_adapter.c new file mode 100644 index 0000000..205cd87 --- /dev/null +++ b/platform/src/packet_adapter.c @@ -0,0 +1,449 @@ +#include "decode_ipv4.h" +#include "decode_ipv6.h" +#include "decode_tcp.h" +#include "decode_udp.h" +#include "decode_gtp.h" +#include "inject_pkt.h" +#include "system.h" + +#include // for NF_ACCEPT +#include + +#ifdef Packet_Adapter_GIT_VERSION +static __attribute__((__used__)) const char *Packet_Adapter_Version = Packet_Adapter_GIT_VERSION; +#else +static __attribute__((__used__)) const char *Packet_Adapter_Version = "Unknown"; +#endif + +typedef struct pkt_info_s +{ + uint32_t id; // unique ID of packet in queue + uint16_t protocol; // hw protocol + uint8_t hook; // netfilter hook + u_int32_t mark; + u_int32_t indev; + u_int32_t outdev; + u_int32_t phys_indev; + u_int32_t phys_outdev; + + uint8_t *payload; + uint32_t payload_len; + + char src_addr[512]; +} pkt_info_t; + +typedef struct union_info_s +{ + ipv4_info_t ipv4; + ipv6_info_t ipv6; + tcp_info_t tcp; + udp_info_t udp; +} union_info_t; + +typedef struct pkt_paser_s +{ + pkt_info_t raw; + union_info_t external; + gtp_info_t gtp; + union_info_t internal; +} pkt_paser_t; + +static void dump_info(pkt_paser_t *parser) +{ + uint32_t pkt_id = parser->raw.id; + LOG_DEBUG("raw: {id: %u, protocol: %u, hook: %u, mark: %u, indev: %u, outdev: %u, phys_indev: %u, phys_outdev: %u, src_addr: %s, data_len: %u}", + parser->raw.id, + parser->raw.protocol, + parser->raw.hook, + parser->raw.mark, + parser->raw.indev, + parser->raw.outdev, + parser->raw.phys_indev, + parser->raw.phys_outdev, + parser->raw.src_addr, + parser->raw.payload_len); + + // external + if (parser->external.ipv4.hdr) + { + dump_ipv4_info(pkt_id, &(parser->external.ipv4)); + } + if (parser->external.ipv6.hdr) + { + dump_ipv6_info(pkt_id, &(parser->external.ipv6)); + } + if (parser->external.udp.hdr) + { + dump_udp_info(pkt_id, &(parser->external.udp)); + } + if (parser->external.tcp.hdr) + { + dump_tcp_info(pkt_id, &(parser->external.tcp)); + } + + // gtp + if (parser->gtp.hdr) + { + dump_gtp_info(pkt_id, &(parser->gtp)); + } + + // internal + if (parser->internal.ipv4.hdr) + { + dump_ipv4_info(pkt_id, &(parser->internal.ipv4)); + } + if (parser->internal.ipv6.hdr) + { + dump_ipv6_info(pkt_id, &(parser->internal.ipv6)); + } + if (parser->internal.udp.hdr) + { + dump_udp_info(pkt_id, &(parser->internal.udp)); + } + if (parser->internal.tcp.hdr) + { + dump_tcp_info(pkt_id, &(parser->internal.tcp)); + } +} + +static int decode_ip_tcp_udp(union_info_t *parser, const uint8_t *data, uint32_t len) +{ + int next_protocol = 0; + uint8_t *payload = NULL; + uint32_t payload_len = 0; + + if (len < IPV4_HEADER_LEN) + { + LOG_ERROR("Parser IP header: packet length too small %d", len); + return -1; + } + + if (IP_GET_RAW_VER(data) == 4) + { + if (decode_ipv4(&(parser->ipv4), data, len) == -1) + { + return -1; + } + + payload = parser->ipv4.payload; + payload_len = parser->ipv4.payload_len; + next_protocol = parser->ipv4.next_protocol; + } + else if (IP_GET_RAW_VER(data) == 6) + { + if (decode_ipv6(&(parser->ipv6), data, len) == -1) + { + return -1; + } + payload = parser->ipv6.payload; + payload_len = parser->ipv6.payload_len; + next_protocol = parser->ipv6.next_protocol; + } + else + { + LOG_ERROR("Unknown IP version %d", IP_GET_RAW_VER(data)); + return -1; + } + + if (next_protocol == IPPROTO_UDP) + { + if (decode_udp(&(parser->udp), payload, payload_len) == -1) + { + return -1; + } + return 0; + } + else if (next_protocol == IPPROTO_TCP) + { + if (decode_tcp(&(parser->tcp), payload, payload_len) == -1) + { + return -1; + } + return 0; + } + else + { + LOG_ERROR("Unknown Internal L4 next_protocol version %d", next_protocol); + return -1; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// NFQ API +/////////////////////////////////////////////////////////////////////////////// + +static int decode_pkt(pkt_info_t *packet, struct nfgenmsg *nfmsg, struct nfq_data *nfa) +{ + struct nfqnl_msg_packet_hdr *packet_hdr = NULL; + struct nfqnl_msg_packet_hw *packet_hw = NULL; + + packet_hdr = nfq_get_msg_packet_hdr(nfa); + if (packet_hdr == NULL) + { + LOG_ERROR("Failed at nfq_get_msg_packet_hdr()"); + return 0; + } + packet->id = ntohl(packet_hdr->packet_id); + + packet->payload_len = nfq_get_payload(nfa, &packet->payload); + if (packet->payload_len <= 0) + { + LOG_ERROR("Failed at nfq_get_payload()"); + return packet->id; + } + packet->protocol = ntohs(packet_hdr->hw_protocol); + packet->hook = packet_hdr->hook; + + packet_hw = nfq_get_packet_hw(nfa); + if (packet_hw) + { + int i = 0; + int offset = 0; + int len = sizeof(packet->src_addr); + int hlen = ntohs(packet_hw->hw_addrlen); + + for (i = 0; i < hlen - 1; i++) + { + offset += snprintf(packet->src_addr + offset, len - offset, "%02x:", packet_hw->hw_addr[i]); + } + snprintf(packet->src_addr + offset, len - offset, "%02x", packet_hw->hw_addr[hlen - 1]); + } + + packet->mark = nfq_get_nfmark(nfa); + packet->indev = nfq_get_indev(nfa); + packet->outdev = nfq_get_outdev(nfa); + packet->phys_indev = nfq_get_physindev(nfa); + packet->phys_outdev = nfq_get_physoutdev(nfa); + + return packet->id; +} +/* + * nfmsg : message objetc that contains the packet + * nfa : Netlink packet data handle + */ +static int packet_handler_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) +{ + int offest = 0; + pkt_paser_t parser = {0}; + int packet_id = decode_pkt(&(parser.raw), nfmsg, nfa); + + // external + if (decode_ip_tcp_udp(&(parser.external), parser.raw.payload, parser.raw.payload_len) == -1) + { + goto end; + } + + if (parser.external.udp.hdr == NULL) + { + LOG_ERROR("External L4 protocol not UDP"); + goto end; + } + + // decode GTP + if (decode_gtp(&(parser.gtp), parser.external.udp.payload, parser.external.udp.payload_len) == -1) + { + return -1; + } + + // internal + if (decode_ip_tcp_udp(&(parser.internal), parser.gtp.payload, parser.gtp.payload_len) == -1) + { + goto end; + } + + /* + * NF_DROP : discarded the packet + * NF_ACCEPT : the packet passes, continue iterations + * NF_QUEUE : inject the packet into a different queue (the target queue number is in the high 16 bits of the verdict) + * NF_REPEAT : iterate the same cycle once more + * NF_STOP : accept, but don't continue iterations + */ + // nfq_set_verdict() + // nfq_set_verdict2() + // nfq_set_verdict_batch() + // nfq_set_verdict_batch2() + // nfq_set_verdict_mark() + + if (parser.external.ipv4.hdr) + { + offest += parser.external.ipv4.hdr_len; + } + if (parser.external.ipv6.hdr) + { + offest += parser.external.ipv6.hdr_len; + } + + offest += parser.external.udp.hdr_len; + offest += parser.gtp.hdr_len; + + dump_info(&parser); + LOG_DEBUG("Offset : %d", offest); + + uint8_t *inject_data = parser.raw.payload + offest; + uint32_t inject_data_len = parser.raw.payload_len - offest; + + if (offest > 0) + { + if ((parser.external.ipv4.hdr && parser.internal.ipv4.hdr) || (parser.external.ipv6.hdr && parser.internal.ipv6.hdr)) + { + return nfq_set_verdict(qh, packet_id, NF_ACCEPT, inject_data_len, inject_data); + } + + if (parser.external.ipv4.hdr && parser.internal.ipv6.hdr) + { + if (inject_ipv6_pkt(parser.internal.ipv6.dst_addr, inject_data, inject_data_len) == -1) + { + goto end; + } + return nfq_set_verdict(qh, packet_id, NF_DROP, 0, NULL); + } + + if (parser.external.ipv6.hdr && parser.internal.ipv4.hdr) + { + if (inject_ipv4_pkt(parser.internal.ipv4.dst_addr, inject_data, inject_data_len) == -1) + { + goto end; + } + return nfq_set_verdict(qh, packet_id, NF_DROP, 0, NULL); + } + } + +end: + return nfq_set_verdict(qh, packet_id, NF_ACCEPT, 0, NULL); +} + +static void usage(char *cmd) +{ + fprintf(stderr, "USAGE: %s [OPTIONS]\n", cmd); + fprintf(stderr, " -v -- show version\n"); + fprintf(stderr, " -i id -- set queue id\n"); + fprintf(stderr, " -d -- run daemon\n"); + fprintf(stderr, " -h -- show help\n"); +} + +/* + * doc : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/ + * Library setup : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__LibrarySetup.html + * Queue handling : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__Queue.html + * Message parsing : http://www.netfilter.org/projects/libnetfilter_queue/doxygen/html/group__Parsing.html + */ +int main(int argc, char **argv) +{ + int fd; + int rv; + int opt; + uint16_t queue = 1; + struct nfq_handle *handle; + struct nfq_q_handle *q_handle; + char buf[65535] __attribute__((aligned)); + + while ((opt = getopt(argc, argv, "vi:dh")) != -1) + { + switch (opt) + { + case 'v': + fprintf(stderr, "Packet Adapter Version: %s\n", Packet_Adapter_Version); + return 0; + case 'i': + queue = atoi(optarg); + if (queue < 0 || queue > 65535) + { + fprintf(stderr, "Usage: %s queueid %d out of range [0, 65535]\n", argv[0], queue); + return 0; + } + break; + case 'd': + run_daemon(); + break; + case 'h': /* fall through */ + default: + usage(argv[0]); + return 0; + } + } + + LOG_DEBUG("Using queue: %d", queue); + + handle = nfq_open(); + if (handle == NULL) + { + LOG_ERROR("Failed at nfq_open(), %d: %s", errno, strerror(errno)); + goto error; + } + + if (nfq_unbind_pf(handle, AF_INET) < 0) + { + LOG_ERROR("Failed at nfq_unbind_pf(), %d: %s", errno, strerror(errno)); + goto error; + } + + if (nfq_bind_pf(handle, AF_INET) < 0) + { + LOG_ERROR("Failed at nfq_bind_pf(), %d: %s", errno, strerror(errno)); + goto error; + } + + q_handle = nfq_create_queue(handle, queue, &packet_handler_cb, NULL); + if (q_handle == NULL) + { + LOG_ERROR("Failed at nfq_create_queue(), %d: %s", errno, strerror(errno)); + goto error; + } + + /* + * NFQNL_COPY_NONE - noop, do not use it + * NFQNL_COPY_META - copy only packet metadata + * NFQNL_COPY_PACKET - copy entire packet + */ + if (nfq_set_mode(q_handle, NFQNL_COPY_PACKET, 0xffff) < 0) + { + LOG_ERROR("Failed at nfq_set_mode(NFQNL_COPY_PACKET), %d: %s", errno, strerror(errno)); + goto error; + } + + if (nfq_set_queue_maxlen(q_handle, 65535) < 0) + { + LOG_ERROR("Failed at nfq_set_queue_maxlen(65535), %d: %s", errno, strerror(errno)); + goto error; + } + + LOG_DEBUG("Waiting for packets..."); + + fd = nfq_fd(handle); + for (;;) + { + if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) + { + nfq_handle_packet(handle, buf, rv); + continue; + } + /* + * if your application is too slow to digest the packets that + * are sent from kernel-space, the socket buffer that we use + * to enqueue packets may fill up returning ENOBUFS. Depending + * on your application, this error may be ignored. Please, see + * the doxygen documentation of this library on how to improve + * this situation. + */ + if (rv < 0 && errno == ENOBUFS) + { + LOG_ERROR("Losing packets !!!"); + continue; + } + + LOG_ERROR("Failed at recv(), %d: %s", errno, strerror(errno)); + } + +error: + if (q_handle) + { + nfq_destroy_queue(q_handle); + } + + if (handle) + { + nfq_close(handle); + } + + return 0; +} diff --git a/platform/src/system.c b/platform/src/system.c new file mode 100644 index 0000000..76141de --- /dev/null +++ b/platform/src/system.c @@ -0,0 +1,69 @@ +#include "system.h" + +int run_daemon(void) +{ + int fd; + + switch (fork()) + { + // 失败 + case -1: + LOG_ERROR("Failed at fork(), %d: %s", errno, strerror(errno)); + return -1; + // 子进程 + case 0: + break; + // 父进程 + default: + exit(0); + } + + if (setsid() == -1) + { + LOG_ERROR("Failed at setsid(), %d: %s", errno, strerror(errno)); + return -1; + } + + umask(0); + + // 以读写模式打开 /dev/null + fd = open("/dev/null", O_RDWR); + if (fd == -1) + { + LOG_ERROR("Failed at open(/dev/null), %d: %s", errno, strerror(errno)); + return -1; + } + + // 将标准输入关联到 /dev/null + if (dup2(fd, STDIN_FILENO) == -1) + { + LOG_ERROR("Failed at dup2(STDIN_FILENO), %d: %s", errno, strerror(errno)); + return -1; + } + + // 将标准输出关联到 /dev/null + if (dup2(fd, STDOUT_FILENO) == -1) + { + LOG_ERROR("Failed at dup2(STDOUT_FILENO), %d: %s", errno, strerror(errno)); + return -1; + } + + // 将标准错误关联到 /dev/null + if (dup2(fd, STDERR_FILENO) == -1) + { + LOG_ERROR("Failed at dup2(STDERR_FILENO), %d: %s", errno, strerror(errno)); + return -1; + } + + // 关闭 /dev/null 的文件句柄 + if (fd > STDERR_FILENO) + { + if (close(fd) == -1) + { + LOG_ERROR("Failed at close(), %d: %s", errno, strerror(errno)); + return -1; + } + } + + return 0; +} \ No newline at end of file diff --git a/script/CMakeLists.txt b/script/CMakeLists.txt new file mode 100644 index 0000000..c389b62 --- /dev/null +++ b/script/CMakeLists.txt @@ -0,0 +1 @@ +install(FILES service/packetadapter.service DESTINATION /usr/lib/systemd/system/ COMPONENT Program) \ No newline at end of file diff --git a/script/service/packetadapter.service b/script/service/packetadapter.service new file mode 100644 index 0000000..558ed38 --- /dev/null +++ b/script/service/packetadapter.service @@ -0,0 +1,10 @@ +[Unit] +Description=Packet Adapter +After=network.target + +[Service] +ExecStartPre=/usr/sbin/iptables -A OUTPUT -o eno4 -p udp --dport 2152 -j NFQUEUE --queue-num 1 +ExecStartPre=/usr/sbin/ip6tables -A OUTPUT -o eno4 -p udp --dport 2152 -j NFQUEUE --queue-num 1 +ExecStart=/opt/tsg/packetadapter/bin/packetadapter +ExecStopPost=/usr/sbin/iptables -D OUTPUT -o eno4 -p udp --dport 2152 -j NFQUEUE --queue-num 1 +ExecStopPost=/usr/sbin/ip6tables -D OUTPUT -o eno4 -p udp --dport 2152 -j NFQUEUE --queue-num 1 \ No newline at end of file