diff --git a/.editorconfig b/.editorconfig index 23219591..3581b168 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,10 @@ insert_final_newline = true indent_style = tab charset = utf-8 +[*.yml] +indent_style = space +indent_size = 2 + [*.py] indent_style = space indent_size = 4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..d079b235 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: Lint +on: + push: + pull_request: + types: [opened, synchronize, reopened] +jobs: + lua: + name: Lua + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Install Dependencies + run: sudo apt install lua-check + - name: Install example site + run: ln -s ./docs/site-example ./site + - name: Lint Lua code + run: make lint-lua + + sh: + name: Shell + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Install Dependencies + run: sudo apt install shellcheck + - name: Install example site + run: ln -s ./docs/site-example ./site + - name: Lint shell code + run: make lint-sh diff --git a/.luacheckrc b/.luacheckrc index 62d839a3..b308748c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -12,6 +12,7 @@ include_files = { "**/*.lua", "package/**/luasrc/**/*", "targets/*", + "package/features", } exclude_files = { @@ -104,3 +105,11 @@ files["targets/*"] = { "try_config", }, } + +files["package/features"] = { + read_globals = { + "_", + "feature", + "when", + }, +} diff --git a/Makefile b/Makefile index e121d3c8..0d174ac2 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,14 @@ GLUON_RELEASE ?= $(error GLUON_RELEASE not set. GLUON_RELEASE can be set in site GLUON_DEPRECATED ?= $(error GLUON_DEPRECATED not set. Please consult the documentation) +ifneq ($(GLUON_BRANCH),) + $(warning *** Warning: GLUON_BRANCH has been deprecated, please set GLUON_AUTOUPDATER_BRANCH and GLUON_AUTOUPDATER_ENABLED instead.) + GLUON_AUTOUPDATER_BRANCH ?= $(GLUON_BRANCH) + GLUON_AUTOUPDATER_ENABLED ?= 1 +endif + +GLUON_AUTOUPDATER_ENABLED ?= 0 + # initialize (possibly already user set) directory variables GLUON_TMPDIR ?= tmp GLUON_OUTPUTDIR ?= output @@ -58,7 +66,7 @@ endef GLUON_VARS = \ GLUON_RELEASE GLUON_REGION GLUON_MULTIDOMAIN GLUON_AUTOREMOVE GLUON_DEBUG GLUON_MINIFY GLUON_DEPRECATED \ GLUON_DEVICES GLUON_TARGETSDIR GLUON_PATCHESDIR GLUON_TMPDIR GLUON_IMAGEDIR GLUON_PACKAGEDIR GLUON_DEBUGDIR \ - GLUON_SITEDIR GLUON_RELEASE GLUON_BRANCH GLUON_LANGS GLUON_BASE_FEEDS \ + GLUON_SITEDIR GLUON_RELEASE GLUON_AUTOUPDATER_BRANCH GLUON_AUTOUPDATER_ENABLED GLUON_LANGS GLUON_BASE_FEEDS \ GLUON_TARGET BOARD SUBTARGET unexport $(GLUON_VARS) @@ -114,13 +122,6 @@ define CheckTarget fi endef -define CheckExternal - if [ ! -d openwrt ]; then - echo "You don't seem to have obtained the external repositories needed by Gluon; please call \`make update\` first!" - exit 1 - fi -endef - define CheckSite if ! GLUON_SITEDIR='$(GLUON_SITEDIR)' GLUON_SITE_CONFIG='$(1).conf' $(LUA) -e 'assert(dofile("scripts/site_config.lua")(os.getenv("GLUON_SITE_CONFIG")))'; then echo 'Your site configuration ($(1).conf) did not pass validation' @@ -147,7 +148,7 @@ LUA := openwrt/staging_dir/hostpkg/bin/lua $(LUA): +@ - $(CheckExternal) + scripts/module_check.sh [ -e openwrt/.config ] || $(OPENWRTMAKE) defconfig $(OPENWRTMAKE) tools/install @@ -157,7 +158,7 @@ $(LUA): config: $(LUA) FORCE +@ - $(CheckExternal) + scripts/module_check.sh $(CheckTarget) $(foreach conf,site $(patsubst $(GLUON_SITEDIR)/%.conf,%,$(wildcard $(GLUON_SITEDIR)/domains/*.conf)),\ $(call CheckSite,$(conf)); \ @@ -185,23 +186,23 @@ dirclean: FORCE manifest: $(LUA) FORCE @ - [ '$(GLUON_BRANCH)' ] || (echo 'Please set GLUON_BRANCH to create a manifest.'; false) + [ '$(GLUON_AUTOUPDATER_BRANCH)' ] || (echo 'Please set GLUON_AUTOUPDATER_BRANCH to create a manifest.'; false) echo '$(GLUON_PRIORITY)' | grep -qE '^([0-9]*\.)?[0-9]+$$' || (echo 'Please specify a numeric value for GLUON_PRIORITY to create a manifest.'; false) - $(CheckExternal) + scripts/module_check.sh ( export $(GLUON_ENV) - echo 'BRANCH=$(GLUON_BRANCH)' + echo 'BRANCH=$(GLUON_AUTOUPDATER_BRANCH)' echo "DATE=$$($(LUA) scripts/rfc3339date.lua)" echo 'PRIORITY=$(GLUON_PRIORITY)' echo for target in $(GLUON_TARGETS); do $(LUA) scripts/generate_manifest.lua "$$target" done - ) > 'tmp/$(GLUON_BRANCH).manifest.tmp' + ) > 'tmp/$(GLUON_AUTOUPDATER_BRANCH).manifest.tmp' mkdir -p '$(GLUON_IMAGEDIR)/sysupgrade' - mv 'tmp/$(GLUON_BRANCH).manifest.tmp' '$(GLUON_IMAGEDIR)/sysupgrade/$(GLUON_BRANCH).manifest' + mv 'tmp/$(GLUON_AUTOUPDATER_BRANCH).manifest.tmp' '$(GLUON_IMAGEDIR)/sysupgrade/$(GLUON_AUTOUPDATER_BRANCH).manifest' FORCE: ; diff --git a/README.md b/README.md index 8158ae17..0798f4f9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ the future development of Gluon. Please refrain from using the `master` branch for anything else but development purposes! Use the most recent release instead. You can list all releases by running `git tag` -and switch to one by running `git checkout v2020.1.3 && make update`. +and switch to one by running `git checkout v2020.2.1 && make update`. If you're using the autoupdater, do not autoupdate nodes with anything but releases. If you upgrade using random master commits the nodes *will break* eventually. diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..38458950 --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,45 @@ +/* + This fixes the vertical position of list markers when the first + element in the
  • is a
     block
    +
    +  Scrolling inside the 
     block is still working as expected
    +*/
    +.rst-content pre.literal-block,
    +.rst-content div[class^='highlight'] pre {
    +	overflow: visible;
    +}
    +
    +
    +/*
    +  This fixes the bottom margin of paragraphs inside lists, where margins inside
    +  a single list item would incorrectly be displayed larger than margins between
    +  the list items.
    +
    +  Upstream fix (not fixed on readthedocs.io yet):
    +  https://github.com/readthedocs/sphinx_rtd_theme/commit/ac20ce75d426efeb40fe2af1f89ea9bad285a45b
    +*/
    +.rst-content .section ol li > p,
    +.rst-content .section ol li > p:last-child,
    +.rst-content .section ul li > p,
    +.rst-content .section ul li > p:last-child {
    +	margin-bottom: 12px;
    +}
    +.rst-content .section ol li > p:only-child,
    +.rst-content .section ol li > p:only-child:last-child,
    +.rst-content .section ul li > p:only-child,
    +.rst-content .section ul li > p:only-child:last-child {
    +	margin-bottom: 0rem;
    +}
    +
    +/*
    +  This fixes the bottom margin of nested lists
    +
    +  Based on upstream fix (not on readthedocs.io yet):
    +  https://github.com/readthedocs/sphinx_rtd_theme/commit/6f0de13baff93f25204aa2cdf0308aae47d71312
    +*/
    +.rst-content .section ul li > ul,
    +.rst-content .section ul li > ol,
    +.rst-content .section ol li > ul,
    +.rst-content .section ol li > ol {
    +	margin-bottom: 12px;
    +}
    diff --git a/docs/conf.py b/docs/conf.py
    index 4f7e5191..766a07cd 100644
    --- a/docs/conf.py
    +++ b/docs/conf.py
    @@ -24,7 +24,7 @@ copyright = '2015-2020, Project Gluon'
     author = 'Project Gluon'
     
     # The short X.Y version
    -version = '2020.1+'
    +version = '2020.2+'
     # The full version, including alpha/beta/rc tags
     release = version
     
    @@ -89,7 +89,7 @@ html_theme = 'sphinx_rtd_theme'
     # relative to this directory. They are copied after the builtin static files,
     # so a file named "default.css" will overwrite the builtin "default.css".
     #
    -# html_static_path = ['_static']
    +html_static_path = ['_static']
     
     # Custom sidebar templates, must be a dictionary that maps document names
     # to template names.
    @@ -101,6 +101,10 @@ html_theme = 'sphinx_rtd_theme'
     #
     # html_sidebars = {}
     
    +# These paths are either relative to html_static_path
    +# or fully qualified paths (eg. https://...)
    +html_css_files = ['css/custom.css']
    +
     
     # -- Options for HTMLHelp output ---------------------------------------------
     
    diff --git a/docs/dev/build.rst b/docs/dev/build.rst
    index d6b6c384..74b9aa11 100644
    --- a/docs/dev/build.rst
    +++ b/docs/dev/build.rst
    @@ -23,7 +23,7 @@ GLUON_SITE_FEED
         List of site feeds; defined in file *modules* in site config
     
     \*_REPO, \*_BRANCH, \*_COMMIT
    -    Git repository URL, branch and and
    +    Git repository URL, branch and
         commit ID of the feeds to use. The branch name may be omitted; the default
         branch will be used in this case.
     
    @@ -79,7 +79,7 @@ patch.sh
         - updating all git submodules
     
         This solution with a temporary clone ensures that the timestamps of checked
    -    out files are not changed by any intermedidate patch steps, but only when
    +    out files are not changed by any intermediate patch steps, but only when
         updating the checkout with the final result. This avoids triggering unnecessary
         rebuilds.
     
    diff --git a/docs/dev/debugging.rst b/docs/dev/debugging.rst
    index 413f1c97..52073428 100644
    --- a/docs/dev/debugging.rst
    +++ b/docs/dev/debugging.rst
    @@ -7,7 +7,7 @@ Debugging
     Kernel Oops
     -----------
     
    -Sometimes a running Linux kernel detects an error during runtime that canot
    +Sometimes a running Linux kernel detects an error during runtime that can't
     be corrected.
     This usually generates a stack trace that points to the location in the code
     that caused the oops.
    diff --git a/docs/dev/packages.rst b/docs/dev/packages.rst
    index a527aad4..b6032b2e 100644
    --- a/docs/dev/packages.rst
    +++ b/docs/dev/packages.rst
    @@ -71,44 +71,62 @@ Feature flags
     =============
     
     Feature flags provide a convenient way to define package selections without
    -making it necessary to list each package explicitly.
    +making it necessary to list each package explicitly. The list of features to
    +enable for a Gluon build is set by the *GLUON_FEATURES* variable in *site.mk*.
     
     The main feature flag definition file is ``package/features``, but each package
     feed can provide additional definitions in a file called ``features`` at the root
     of the feed repository.
     
    -Each flag *$flag* without any explicit definition will simply include the package
    -with the name *gluon-$flag* by default. The feature definition file can modify
    -the package selection in two ways:
    +Each flag *$flag* will include the package the name *gluon-$flag* by default.
    +The feature definition file can modify the package selection by adding or removing
    +packages when certain combinations of flags are set.
     
    -* The *nodefault* function suppresses default of including the *gluon-$flag*
    -  package
    -* The *packages* function adds a list of packages (or removes, when package
    -  names are prepended with minus signs) when a given logical expression
    -  is satisfied
    +Feature definitions use Lua syntax. Two basic functions are defined:
    +
    +* *feature(name, pkgs)*: Defines a new feature. *feature()* expects a feature
    +  (flag) name and a list of packages to add or remove when the feature is
    +  enabled.
    +
    +  * Defining a feature using *feature* replaces the default definition of
    +    just including *gluon-$flag*.
    +  * A package is removed when the package name is prefixed with a ``-`` (after
    +    the opening quotation mark).
    +
    +* *when(expr, pkgs)*: Adds or removes packages when a given logical expression
    +  of feature flags is satisfied.
    +
    +  * *expr* is a logical expression composed of feature flag names (each prefixed
    +    with an underscore before the opening quotation mark), logical operators
    +    (*and*, *or*, *not*) and parentheses.
    +  * Referencing a feature flag in *expr* has no effect on the default handling
    +    of the flag. When no *feature()* entry for a flag exists, it will still
    +    add *gluon-$flag* by default.
    +  * *pkgs* is handled as for *feature()*.
     
     Example::
     
    -    nodefault 'web-wizard'
    +    feature('web-wizard', {
    +      'gluon-config-mode-hostname',
    +      'gluon-config-mode-geo-location',
    +      'gluon-config-mode-contact-info',
    +      'gluon-config-mode-outdoor',
    +    })
     
    -    packages 'web-wizard' \
    -      'gluon-config-mode-hostname' \
    -      'gluon-config-mode-geo-location' \
    -      'gluon-config-mode-contact-info'
    +    when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
    +      'gluon-config-mode-mesh-vpn',
    +    })
    +
    +    feature('no-radvd', {
    +      '-gluon-radvd',
    +    })
     
    -    packages 'web-wizard & (mesh-vpn-fastd | mesh-vpn-tunneldigger)' \
    -      'gluon-config-mode-mesh-vpn'
     
     This will
     
    -* disable the inclusion of a (non-existent) package called *gluon-web-wizard*
    -* enable three config mode packages when the *web-wizard* feature is enabled
    +* disable the inclusion of the (non-existent) packages *gluon-web-wizard* and *gluon-no-radvd* when their
    +  corresponding feature flags appear in *GLUON_FEATURES*
    +* enable four additional config mode packages when the *web-wizard* feature is enabled
     * enable *gluon-config-mode-mesh-vpn* when both *web-wizard* and one
       of *mesh-vpn-fastd* and *mesh-vpn-tunneldigger* are enabled
    -
    -Supported syntax elements of logical expressions are:
    -
    -* \& (and)
    -* \| (or)
    -* \! (not)
    -* parentheses
    +* disable the *gluon-radvd* package when *gluon-no-radvd* is enabled
    diff --git a/docs/dev/wan.rst b/docs/dev/wan.rst
    index b992146a..ec9d942b 100644
    --- a/docs/dev/wan.rst
    +++ b/docs/dev/wan.rst
    @@ -13,7 +13,7 @@ the mesh's DNS servers and use these for all other name resolution.
     
     If the device does not feature a WAN port, the LAN port is configured as WAN port.
     In case such a device has multiple LAN ports, all these can be used as WAN.
    -Devices, which feature a "hybrid" port (labled as WAN/LAN), this port is used as WAN.
    +Devices, which feature a "hybrid" port (labelled as WAN/LAN), this port is used as WAN.
     
     This behavior can be reversed using the ``single_as_lan`` site.conf option.
     
    diff --git a/docs/features/autoupdater.rst b/docs/features/autoupdater.rst
    index 014ee59f..e0cd9c6c 100644
    --- a/docs/features/autoupdater.rst
    +++ b/docs/features/autoupdater.rst
    @@ -7,8 +7,11 @@ Building Images
     ---------------
     
     By default, the autoupdater is disabled (as it is usually not helpful to have unexpected updates
    -during development), but it can be enabled by setting the variable GLUON_BRANCH when building
    -to override the default branch set in the site configuration.
    +during development), but it can be enabled by setting the variable ``GLUON_AUTOUPDATER_ENABLED`` to ``1`` when building.
    +It is also possible to override the default branch during build using the variable ``GLUON_AUTOUPDATER_BRANCH``.
    +
    +If a default branch is set neither in *site.conf* nor via ``GLUON_AUTOUPDATER_BRANCH``, the default branch is
    +implementation-defined. Currently, the branch with the first name in alphabetical order is chosen.
     
     A manifest file for the updater can be generated with `make manifest`. A signing script (using
     ``ecdsautils``) can be found in the `contrib` directory. When creating the manifest, the
    @@ -32,15 +35,16 @@ Automated nightly builds
     
     A fully automated nightly build could use the following commands:
     
    -::
    +.. code-block:: sh
     
         git pull
    -    (git -C site pull)
    +    # git -C site pull
         make update
         make clean GLUON_TARGET=ar71xx-generic
         NUM_CORES_PLUS_ONE=$(expr $(nproc) + 1)
    -    make -j$NUM_CORES_PLUS_ONE GLUON_TARGET=ar71xx-generic GLUON_BRANCH=experimental GLUON_RELEASE=$GLUON_RELEASE
    -    make manifest GLUON_BRANCH=experimental GLUON_RELEASE=$GLUON_RELEASE
    +    make -j$NUM_CORES_PLUS_ONE GLUON_TARGET=ar71xx-generic GLUON_RELEASE=$GLUON_RELEASE \
    +        GLUON_AUTOUPDATER_BRANCH=experimental GLUON_AUTOUPDATER_ENABLED=1
    +    make manifest GLUON_RELEASE=$GLUON_RELEASE GLUON_AUTOUPDATER_BRANCH=experimental
         contrib/sign.sh $SECRETKEY output/images/sysupgrade/experimental.manifest
     
         rm -rf /where/to/put/this/experimental
    diff --git a/docs/features/multidomain.rst b/docs/features/multidomain.rst
    index 1f9a729e..7abd59a8 100644
    --- a/docs/features/multidomain.rst
    +++ b/docs/features/multidomain.rst
    @@ -88,18 +88,25 @@ domain of a router, if and only if one of the above conditions matches.
     Switching the domain
     --------------------
     
    -**via commandline**:
    +Via commandline
    +^^^^^^^^^^^^^^^
     
     ::
     
    -    uci set gluon.core.domain="newdomaincode"
    -    gluon-reconfigure
    -    reboot
    +    gluon-switch-domain 'newdomaincode'
     
    -**via config mode:**
    +When the node is not in config mode, ``gluon-switch-domain`` will automatically
    +reboot the node by default. This can be suppressed by passing ``--no-reboot``::
     
    -To allow switching the domain via config mode, ``config-mode-domain-select``
    -has to be added to GLUON_FEATURES in the site.mk.
    +    gluon-switch-domain --no-reboot 'newdomaincode'
    +
    +Switching the domain without reboot is currently **experimental**.
    +
    +Via config mode
    +^^^^^^^^^^^^^^^
    +
    +To allow switching the domain via config mode, add ``config-mode-domain-select``
    +to GLUON_FEATURES in site.mk.
     
     |image0|
     
    diff --git a/docs/features/vpn.rst b/docs/features/vpn.rst
    index 81b6dfce..8697baad 100644
    --- a/docs/features/vpn.rst
    +++ b/docs/features/vpn.rst
    @@ -47,7 +47,7 @@ The resulting firmware will allow users to choose between secure (encrypted) and
     .. image:: fastd_mode.gif
     
     **Unix socket:**
    -To confirm whether the correct cipher is being used, fastds unix
    +To confirm whether the correct cipher is being used, fastd's unix
     socket can be interrogated, after installing for example `socat`.
     
     ::
    diff --git a/docs/features/wlan-configuration.rst b/docs/features/wlan-configuration.rst
    index 3807b889..08528196 100644
    --- a/docs/features/wlan-configuration.rst
    +++ b/docs/features/wlan-configuration.rst
    @@ -21,4 +21,6 @@ you can configure this via the uci section ``gluon-core.wireless``::
     
       uci set gluon-core.@wireless[0].preserve_channels='1'
     
    +When channels should be preserved, toggling the outdoor mode will have no effect on the channel settings.
    +Therefore, the Outdoor mode settings won't be displayed in config mode.
     Keep in mind that nodes running wifi interfaces on custom channels can't mesh with default nodes anymore!
    diff --git a/docs/index.rst b/docs/index.rst
    index cf767073..adb77584 100644
    --- a/docs/index.rst
    +++ b/docs/index.rst
    @@ -78,6 +78,9 @@ Several Freifunk communities in Germany use Gluon as the foundation of their Fre
        :caption: Releases
        :maxdepth: 1
     
    +   releases/v2020.2.1
    +   releases/v2020.2
    +   releases/v2020.1.4
        releases/v2020.1.3
        releases/v2020.1.2
        releases/v2020.1.1
    diff --git a/docs/package/gluon-hoodselector.rst b/docs/package/gluon-hoodselector.rst
    index 13054924..23fd8f41 100644
    --- a/docs/package/gluon-hoodselector.rst
    +++ b/docs/package/gluon-hoodselector.rst
    @@ -66,7 +66,7 @@ and others which contain shapes.
     
     * **default domain**
     
    -The default domain doesn’t hold any shapes and represents the inverted area of
    +The default domain doesn't hold any shapes and represents the inverted area of
     all other shapes held by other domains with geo coordinates. It will only be
     entered if a node could not be matched to a geo domain. A suggested approach is
     to define the "old" network as default domain and gradually migrate nodes from
    diff --git a/docs/releases/v2015.1.rst b/docs/releases/v2015.1.rst
    index 1e6d3396..2fd694c1 100644
    --- a/docs/releases/v2015.1.rst
    +++ b/docs/releases/v2015.1.rst
    @@ -19,7 +19,7 @@ ar71xx-generic
     
       - DIR-615 (C1)
     
    -* GL-Inet
    +* GL.iNet
     
       - 6408A (v1)
       - 6416A (v1)
    diff --git a/docs/releases/v2018.2.1.rst b/docs/releases/v2018.2.1.rst
    index 92c729a1..3f02f888 100644
    --- a/docs/releases/v2018.2.1.rst
    +++ b/docs/releases/v2018.2.1.rst
    @@ -21,7 +21,7 @@ ramips-mt7620
     ramips-mt76x8
     ^^^^^^^^^^^^^
     
    -* Gl.iNet
    +* GL.iNet
     
       - MT300N (v2) [#noibss]_
     
    diff --git a/docs/releases/v2020.1.1.rst b/docs/releases/v2020.1.1.rst
    index b6ad1831..f18d1133 100644
    --- a/docs/releases/v2020.1.1.rst
    +++ b/docs/releases/v2020.1.1.rst
    @@ -10,7 +10,7 @@ Bugfixes
     - Fixed non-working LEDs on TP-Link Archer C5 v1 and Archer C7 v2 after an upgrade to Gluon 2020.1.
     
     - Fixed an issue which leads to AVM FRITZ!WLAN Repeater 450E devices being stuck in failsafe mode
    -  ater an upgrade to Gluon 2020.1.
    +  after an upgrade to Gluon 2020.1.
     
     Other changes
     -------------
    diff --git a/docs/releases/v2020.1.2.rst b/docs/releases/v2020.1.2.rst
    index fa25c655..37adb6a1 100644
    --- a/docs/releases/v2020.1.2.rst
    +++ b/docs/releases/v2020.1.2.rst
    @@ -37,7 +37,7 @@ Other changes
     Internals
     ---------
     
    -- OpenWrt 19.07 introduced the urgnd entropy daemon that serves the same function as the haveged service, which we have been recommending. To not have two redundant entropy daemons in this release we remove urngd in favor of haveged in the v2020.1 release series.
    +- OpenWrt 19.07 introduced the urngd entropy daemon that serves the same function as the haveged service, which we have been recommending. To not have two redundant entropy daemons in this release we remove urngd in favor of haveged in the v2020.1 release series.
     
     Known issues
     ------------
    diff --git a/docs/releases/v2020.1.4.rst b/docs/releases/v2020.1.4.rst
    new file mode 100644
    index 00000000..92a31c6f
    --- /dev/null
    +++ b/docs/releases/v2020.1.4.rst
    @@ -0,0 +1,47 @@
    +Gluon 2020.1.4
    +==============
    +
    +Added hardware support
    +----------------------
    +
    +- Added support for TP-Link CPE210 3.20 (`#2080 `_)
    +
    +Bugfixes
    +--------
    +
    +- Fixed a rare race-condition during mesh interface teardown (`#2057 `_)
    +
    +- Fixed handling of mesh interfaces together with outdoor mode, site.conf defaults and config mode (`#2049 `_) (`#2054 `_)
    +
    +Other changes
    +-------------
    +
    +- Linux kernel has been updated to 4.14.193
    +- Backports of batman-adv bugfixes
    +
    +Known issues
    +------------
    +
    +* Upgrading EdgeRouter-X from versions before v2020.1.x may lead to a soft-bricked state due to bad blocks on the
    +  NAND flash which the NAND driver before this release does not handle well.
    +  (`#1937 `_)
    +
    +* The integration of the BATMAN_V routing algorithm is incomplete.
    +
    +  - Mesh neighbors don't appear on the status page. (`#1726 `_)
    +    Many tools have the BATMAN_IV metric hardcoded, these need to be updated to account for the new throughput
    +    metric.
    +  - Throughput values are not correctly acquired for different interface types.
    +    (`#1728 `_)
    +    This affects virtual interface types like bridges and VXLAN.
    +
    +* Default TX power on many Ubiquiti devices is too high, correct offsets are unknown
    +  (`#94 `_)
    +
    +  Reducing the TX power in the Advanced Settings is recommended.
    +
    +* The MAC address of the WAN interface is modified even when Mesh-on-WAN is disabled
    +  (`#496 `_)
    +
    +  This may lead to issues in environments where a fixed MAC address is expected (like VMware when promiscuous mode is
    +  disallowed).
    diff --git a/docs/releases/v2020.2.1.rst b/docs/releases/v2020.2.1.rst
    new file mode 100644
    index 00000000..8c3ea5ea
    --- /dev/null
    +++ b/docs/releases/v2020.2.1.rst
    @@ -0,0 +1,47 @@
    +Gluon 2020.2.1
    +==============
    +
    +Added hardware support
    +----------------------
    +
    +- Added support for TP-Link CPE210 3.20 (`#2080 `_)
    +
    +Bugfixes
    +--------
    +
    +- Fixed handling of *mesh_on_lan* enabled in site configuration (`#2090 `_)
    +
    +- Fixed build issues with lantiq-xrx200 target by removing unsupported DSL modem packages (`#2087 `_)
    +
    +Other changes
    +-------------
    +
    +- Linux kernel has been updated to 4.14.193
    +- Backports of batman-adv bugfixes
    +
    +Known issues
    +------------
    +
    +* Upgrading EdgeRouter-X from versions before v2020.1.x may lead to a soft-bricked state due to bad blocks on the
    +  NAND flash which the NAND driver before this release does not handle well.
    +  (`#1937 `_)
    +
    +* The integration of the BATMAN_V routing algorithm is incomplete.
    +
    +  - Mesh neighbors don't appear on the status page. (`#1726 `_)
    +    Many tools have the BATMAN_IV metric hardcoded, these need to be updated to account for the new throughput
    +    metric.
    +  - Throughput values are not correctly acquired for different interface types.
    +    (`#1728 `_)
    +    This affects virtual interface types like bridges and VXLAN.
    +
    +* Default TX power on many Ubiquiti devices is too high, correct offsets are unknown
    +  (`#94 `_)
    +
    +  Reducing the TX power in the Advanced Settings is recommended.
    +
    +* In configurations not using VXLAN, the MAC address of the WAN interface is modified even when Mesh-on-WAN is disabled
    +  (`#496 `_)
    +
    +  This may lead to issues in environments where a fixed MAC address is expected (like VMware when promiscuous mode is
    +  disallowed).
    diff --git a/docs/releases/v2020.2.rst b/docs/releases/v2020.2.rst
    new file mode 100644
    index 00000000..4d4f3cd7
    --- /dev/null
    +++ b/docs/releases/v2020.2.rst
    @@ -0,0 +1,198 @@
    +Gluon 2020.2
    +============
    +
    +Added hardware support
    +----------------------
    +
    +ath79-generic
    +~~~~~~~~~~~~~
    +
    +* GL.iNet
    +
    +  - GL-AR750S
    +
    +* TP-Link
    +
    +  - CPE220 (v3)
    +
    +ipq40xx-generic
    +~~~~~~~~~~~~~~~
    +
    +* EnGenius
    +
    +  - ENS620EXT [#outdoor]_
    +
    +* Linksys
    +
    +  - EA6350 (v3)
    +
    +lantiq-xrx200
    +~~~~~~~~~~~~~
    +
    +* TP-Link
    +
    +  - TD-W8970
    +
    +lantiq-xway
    +~~~~~~~~~~~
    +
    +* NETGEAR
    +
    +  - DGN3500B
    +
    +ramips-mt76x8
    +~~~~~~~~~~~~~
    +* Cudy
    +
    +  - WR1000
    +
    +
    +x86-legacy [#newtarget]_
    +~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +* Devices older than the Pentium 4
    +
    +
    +.. [#newtarget]
    +  This is a new target.
    +
    +.. [#outdoor]
    +  This device is supposed to be set up outdoors and will therefore have its outdoor mode flag automatically enabled.
    +
    +
    +Major changes
    +-------------
    +
    +Device Classes
    +~~~~~~~~~~~~~~
    +
    +Devices are now categorized into device classes. This device class can determine which features
    +as well as packages are installed on the device when building images.
    +
    +Currently there are two classes used in Gluon, *tiny* and *standard*. All devices with less than 64M of RAM or
    +less than 7M of usable firmware space are assigned to the tiny class.
    +
    +WPA3 support for Private WLAN
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +The private WLAN now supports WPA3-SAE key exchange as well as management frame protection (802.11w).
    +For this to work, the firmware needs to be built with the *wireless-encryption-wpa3* feature.
    +
    +OWE on Client Network
    +~~~~~~~~~~~~~~~~~~~~~
    +
    +Gluon now allows to configure a VAP for the client network which supports opportunistic encryption on the
    +client network for devices which support the OWE security type (also known as Enhanced Open).
    +
    +This encrypted VAP can be the only available access point or be configured in addition to an unencrypted VAP.
    +In the latter case, the transition mode can be enabled, which enables compatible devices to automatically
    +connect to the encrypted VAP while legacy devices continue to use the unencrypted connection.
    +
    +There are issues with some devices running Android 9 when connecting to a transition mode enabled network. See the site documentation for more information.
    +
    +SAE Encrypted Mesh Links
    +~~~~~~~~~~~~~~~~~~~~~~~~
    +
    +Mesh links can now be operated in an encrypted mode using SAE authentication. For this to work, a common shared secret
    +has to be distributed to all participating nodes using the site.conf.
    +
    +Responsive status page
    +~~~~~~~~~~~~~~~~~~~~~~
    +
    +The status page design is now responsive and reflows better on mobile devices.
    +
    +Primary domain code
    +~~~~~~~~~~~~~~~~~~~
    +
    +The primary domain code is now visible on the node status page as well as in the respondd information
    +emitted by the node.
    +
    +Logging
    +~~~~~~~
    +
    +The new *gluon-logging* package allows to configure a remote syslog server using the site.conf.
    +This package can only be included when *gluon-web-logging* is excluded.
    +
    +Peer cleanup in fastd
    +~~~~~~~~~~~~~~~~~~~~~
    +
    +fastd peers and groups are now removed on update in case they do not exist in the new site configuration.
    +To preserve a custom peer across updates, add the *preserve* key to the peer's UCI configuration and set it to ``1``.
    +
    +
    +Bugfixes
    +--------
    +
    +- The WAN MAC address now matches the one defined in OpenWrt if VXLAN is enabled for the selected domain.
    +
    +- *gluon-reload* now reloads all relevant services.
    +
    +- Disabling outdoor mode and enabling meshing in the config mode can now be performed in a single step.
    +
    +- Fixed section visibility with enabled outdoor mode in config mode.
    +
    +
    +Site changes
    +------------
    +
    +site.mk
    +~~~~~~~
    +
    +Starting with version 19.07 OpenWrt ships the urngd entropy daemon by default.
    +It replaces the haveged daemon, for which we removed the support in Gluon. Remove ``haveged`` from your package selection.
    +
    +
    +Internal
    +--------
    +
    +Editorconfig
    +~~~~~~~~~~~~
    +
    +Gluon now ships a *editorconfig* file to allow compatible editors to automatically apply key aspects of Gluon's code style.
    +
    +Continuous Integration
    +~~~~~~~~~~~~~~~~~~~~~~
    +
    +* Jenkins
    +
    +  - The CI now has a test stage to verify Gluons runtime functionality.
    +
    +* GitHub Actions
    +
    +  - GitHub actions is now enabled for the Gluon project, build-testing all available targets.
    +
    +Build system
    +~~~~~~~~~~~~
    +
    +- Source code minification can now be skipped by enabling the GLUON_MINIFY flag.
    +
    +- Enabling the GLUON_AUTOREMOVE flag will remove package build directories after they are built.
    +  This reduces space consumption at the expense of subsequent builds being slower.
    +
    +
    +Known issues
    +------------
    +
    +* Upgrading EdgeRouter-X from versions before v2020.1.x may lead to a soft-bricked state due to bad blocks on the
    +  NAND flash which the NAND driver before this release does not handle well.
    +  (`#1937 `_)
    +
    +* The integration of the BATMAN_V routing algorithm is incomplete.
    +
    +  - Mesh neighbors don't appear on the status page. (`#1726 `_)
    +    Many tools have the BATMAN_IV metric hardcoded, these need to be updated to account for the new throughput
    +    metric.
    +  - Throughput values are not correctly acquired for different interface types.
    +    (`#1728 `_)
    +    This affects virtual interface types like bridges and VXLAN.
    +
    +* Default TX power on many Ubiquiti devices is too high, correct offsets are unknown
    +  (`#94 `_)
    +
    +  Reducing the TX power in the Advanced Settings is recommended.
    +
    +* In configurations not using VXLAN, the MAC address of the WAN interface is modified even when Mesh-on-WAN is disabled
    +  (`#496 `_)
    +
    +  This may lead to issues in environments where a fixed MAC address is expected (like VMware when promiscuous mode is
    +  disallowed).
    diff --git a/docs/site-example/site.conf b/docs/site-example/site.conf
    index 22f78502..797a2fa9 100644
    --- a/docs/site-example/site.conf
    +++ b/docs/site-example/site.conf
    @@ -1,4 +1,4 @@
    --- This is an example site configuration for Gluon v2020.1.3
    +-- This is an example site configuration for Gluon v2020.2.1
     --
     -- Take a look at the documentation located at
     -- https://gluon.readthedocs.io/ for details.
    @@ -164,7 +164,8 @@
       },
     
       autoupdater = {
    -    -- Default branch. Don't forget to set GLUON_BRANCH when building!
    +    -- Default branch (optional), can be overridden by setting GLUON_AUTOUPDATER_BRANCH when building.
    +    -- Set GLUON_AUTOUPDATER_ENABLED to enable the autoupdater by default for newly installed nodes.
         branch = 'stable',
     
         -- List of branches. You may define multiple branches.
    diff --git a/docs/user/getting_started.rst b/docs/user/getting_started.rst
    index 36d9260e..1cf3bc23 100644
    --- a/docs/user/getting_started.rst
    +++ b/docs/user/getting_started.rst
    @@ -8,7 +8,7 @@ Gluon's releases are managed using `Git tags`_. If you are just getting
     started with Gluon we recommend to use the latest stable release of Gluon.
     
     Take a look at the `list of gluon releases`_ and notice the latest release,
    -e.g. *v2020.1.3*. Always get Gluon using git and don't try to download it
    +e.g. *v2020.2.1*. Always get Gluon using git and don't try to download it
     as a Zip archive as the archive will be missing version information.
     
     Please keep in mind that there is no "default Gluon" build; a site configuration
    @@ -44,7 +44,7 @@ Building the images
     -------------------
     
     To build Gluon, first check out the repository. Replace *RELEASE* with the
    -version you'd like to checkout, e.g. *v2020.1.3*.
    +version you'd like to checkout, e.g. *v2020.2.1*.
     
     ::
     
    @@ -171,10 +171,12 @@ usually be set on the command line or in ``site.mk``.
     Common variables
     ................
     
    -GLUON_BRANCH
    -  Sets the default branch of the autoupdater. If unset, the autoupdater is disabled
    -  by default. For the ``make manifest`` command, GLUON_BRANCH defines the branch to
    -  generate a manifest for.
    +GLUON_AUTOUPDATER_BRANCH
    +  Overrides the default branch of the autoupdater set in ``site.conf``. For the ``make manifest`` command,
    +  ``GLUON_AUTOUPDATER_BRANCH`` defines the branch to generate a manifest for.
    +
    +GLUON_AUTOUPDATER_ENABLED
    +  Set to ``1`` to enable the autoupdater by default for newly installed nodes.
     
     GLUON_DEPRECATED
       Controls whether images for deprecated devices should be built. The following
    @@ -225,7 +227,7 @@ GLUON_DEBUG
       similar tools. Requires a device or target with at least 16 MB of flash space, e.g. `x86-64`. Unset by default.
     
     GLUON_MINIFY
    -  Setting ``GLUON_MINIFY=0`` will omit the minification of scripts during the build process. By 
    +  Setting ``GLUON_MINIFY=0`` will omit the minification of scripts during the build process. By
       default the flag is set to ``1``. Disabling the flag is handy if human readable scripts on the
       devices are desired for development purposes. Be aware that this will increase the size of the
       resulting images and is therefore not suitable for devices with small flash chips.
    diff --git a/docs/user/site.rst b/docs/user/site.rst
    index fa14d57f..11004f3b 100644
    --- a/docs/user/site.rst
    +++ b/docs/user/site.rst
    @@ -176,7 +176,7 @@ wifi24 \: optional
     .. _user-site-wifi5:
     
     wifi5 \: optional
    -    Same as `wifi24` but for the 5Ghz radio.
    +    Same as `wifi24` but for the 5 GHz radio.
     
         Additionally a range of channels that are safe to use outsides on the 5 GHz band can
         be set up through ``outdoor_chanlist``, which allows for a space-separated list of
    @@ -326,7 +326,7 @@ mesh_vpn
         implementation.
     
         **Note:** It may be interesting to include the package *gluon-iptables-clamp-mss-to-pmtu*
    -    in the build when using *gluon-mesh-babel* to work around icmp blackholes on the internet.
    +    in the build when using *gluon-mesh-babel* to work around ICMP blackholes on the internet.
     
         ::
     
    @@ -418,12 +418,16 @@ poe_passthrough \: optional
     autoupdater \: package
         Configuration for the autoupdater feature of Gluon.
     
    +    Specifying a default branch in *site.conf* is optional. See
    +    :doc:`../features/autoupdater` for information how to change the behaviour
    +    of the autoupdater during image build.
    +
         The mirrors are checked in random order until the manifest could be downloaded
         successfully or all mirrors have been tried.
         ::
     
           autoupdater = {
    -        branch = 'stable',
    +        branch = 'stable', -- optional
             branches = {
               stable = {
                 name = 'stable',
    @@ -464,9 +468,14 @@ config_mode \: optional
         The *geo_location.osm* section is only relevant when the *gluon-config-mode-geo-location-osm*
         package is used. The *center.lon* and *center.lat* values are mandatory in this case and
         define the default center of the map when no position has been picked yet. The *zoom* level
    -    defaults to 12 in this case. *openlayers_url* allows to override the base URL of the
    +    defaults to 12 in this case.
    +
    +    *openlayers_url* allows to override the base URL of the
         *build/ol.js* and *css/ol.css* files (the default is
         ``https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0``).
    +    It is also possible to replace the default tile layer (which is OpenStreetMap)
    +    with a custom one using the *tile_layer* section. Only XYZ layers are supported
    +    at this point.
     
         The remote login page only shows SSH key configuration by default. A
         password form can be displayed by setting *remote_login.show_password_form*
    @@ -488,6 +497,11 @@ config_mode \: optional
                   },
                   zoom = 13,
                   -- openlayers_url = 'http://ffac.example.org/openlayer',
    +              -- tile_layer = {
    +              --   type = 'XYZ',
    +              --   url = 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    +              --   attributions = '© OpenStreetMap contributors.',
    +              -- },
                 },
               },
               remote_login = {
    diff --git a/docs/user/supported_devices.rst b/docs/user/supported_devices.rst
    index 1b63f56b..4820d9ac 100644
    --- a/docs/user/supported_devices.rst
    +++ b/docs/user/supported_devices.rst
    @@ -44,6 +44,7 @@ ar71xx-generic
       - GL-AR150
       - GL-AR300M
       - GL-AR750
    +  - GL-USB150 (Microuter)
     
     * Linksys
     
    @@ -107,9 +108,9 @@ ar71xx-generic
       - Nanostation M2/M5  [#device-class-tiny]_
       - Nanostation M2/M5 XW
       - Picostation M2  [#device-class-tiny]_
    -  - Rocket M2/M5
    -  - Rocket M2/M5 Ti
    -  - Rocket M2/M5 XW
    +  - Rocket M2
    +  - Rocket M2 Ti
    +  - Rocket M2 XW
       - UniFi AC Mesh
       - UniFi AC Mesh Pro
       - UniFi AP
    @@ -229,6 +230,7 @@ ipq40xx-generic
     * AVM
     
       - FRITZ!Box 4040 [#avmflash]_
    +  - FRITZ!Box 7530 [#eva_ramboot]_
       - FRITZ!Repeater 1200 [#eva_ramboot]_
     
     * EnGenius
    @@ -317,6 +319,11 @@ ramips-mt7620
       - GL-MT300N
       - GL-MT750
     
    +* NETGEAR
    +
    +  - EX3700
    +  - EX3800
    +
     * Nexx
     
       - WT3020AD/F/H
    diff --git a/modules b/modules
    index 5f7923b8..2ed26f02 100644
    --- a/modules
    +++ b/modules
    @@ -2,15 +2,15 @@ GLUON_FEEDS='packages routing gluon'
     
     OPENWRT_REPO=https://github.com/openwrt/openwrt.git
     OPENWRT_BRANCH=openwrt-19.07
    -OPENWRT_COMMIT=9cafcbe0bdd601d07ed55bee0136f5d8393c37a8
    +OPENWRT_COMMIT=29b4104d69bf91db17764dd885e9e111a373f08c
     
     PACKAGES_PACKAGES_REPO=https://github.com/openwrt/packages.git
     PACKAGES_PACKAGES_BRANCH=openwrt-19.07
    -PACKAGES_PACKAGES_COMMIT=e76090945523c71c2406276f6d42b2e7f078a2d8
    +PACKAGES_PACKAGES_COMMIT=03425a0d2f5967639d15a3ef1f0407859768917d
     
     PACKAGES_ROUTING_REPO=https://github.com/openwrt-routing/packages.git
     PACKAGES_ROUTING_BRANCH=openwrt-19.07
    -PACKAGES_ROUTING_COMMIT=9b42e24a54f03ebb6f58224b49036e8f739b175f
    +PACKAGES_ROUTING_COMMIT=b77498bd56d5e45ab4577a1f4ad6ffc55b4a86b7
     
     PACKAGES_GLUON_REPO=https://github.com/freifunk-gluon/packages.git
     PACKAGES_GLUON_COMMIT=12e41d0ff07ec54bbd67a31ab50d12ca04f2238c
    diff --git a/package/features b/package/features
    index a54837c8..72887e3a 100644
    --- a/package/features
    +++ b/package/features
    @@ -1,37 +1,52 @@
    -nodefault 'web-wizard'
    -
    -packages 'web-wizard' \
    -	'gluon-config-mode-hostname' \
    -	'gluon-config-mode-geo-location' \
    -	'gluon-config-mode-contact-info' \
    -	'gluon-config-mode-outdoor'
    -
    -packages 'web-wizard & autoupdater' \
    -	'gluon-config-mode-autoupdater'
    -
    -packages 'web-wizard & (mesh-vpn-fastd | mesh-vpn-tunneldigger)' \
    -	'gluon-config-mode-mesh-vpn'
    +-- GLUON_FEATURES definition file
    +--
    +-- See the page `dev/packages` (Developer Documentation / Package development)
    +-- in the `docs` directory or on gluon.readthedocs.io for information on the
    +-- file format
     
     
    -nodefault 'web-advanced'
    +feature('web-wizard', {
    +	'gluon-config-mode-hostname',
    +	'gluon-config-mode-geo-location',
    +	'gluon-config-mode-contact-info',
    +	'gluon-config-mode-outdoor',
    +})
     
    -packages 'web-advanced' \
    -	'gluon-web-admin' \
    -	'gluon-web-network' \
    -	'gluon-web-wifi-config'
    +when(_'web-wizard' and _'autoupdater', {
    +	'gluon-config-mode-autoupdater',
    +})
     
    -packages 'web-advanced & autoupdater' \
    -	'gluon-web-autoupdater'
    +when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
    +	'gluon-config-mode-mesh-vpn',
    +})
     
    -packages 'status-page & mesh-batman-adv-15' \
    -	'gluon-status-page-mesh-batman-adv'
     
    -packages 'mesh-batman-adv-15' \
    -	'gluon-ebtables-limit-arp' \
    -	'gluon-radvd'
    +feature('web-advanced', {
    +	'gluon-web-admin',
    +	'gluon-web-network',
    +	'gluon-web-wifi-config',
    +})
     
    -packages 'mesh-babel' \
    -	'gluon-radvd'
    +when(_'web-advanced' and _'autoupdater', {
    +	'gluon-web-autoupdater',
    +})
     
    -packages '!wireless-encryption-wpa3' \
    -	'hostapd-mini'
    +
    +when(_'mesh-batman-adv-15', {
    +	'gluon-ebtables-limit-arp',
    +	'gluon-radvd',
    +})
    +
    +when(_'status-page' and _'mesh-batman-adv-15', {
    +	'gluon-status-page-mesh-batman-adv',
    +})
    +
    +
    +when(_'mesh-babel', {
    +	'gluon-radvd',
    +})
    +
    +
    +when(not _'wireless-encryption-wpa3', {
    +	'hostapd-mini',
    +})
    diff --git a/package/gluon-autoupdater/Config.in b/package/gluon-autoupdater/Config.in
    new file mode 100644
    index 00000000..554314c9
    --- /dev/null
    +++ b/package/gluon-autoupdater/Config.in
    @@ -0,0 +1,10 @@
    +if PACKAGE_gluon-autoupdater
    +
    +config GLUON_AUTOUPDATER_BRANCH
    +	string "Autoupdater branch"
    +	default ""
    +
    +config GLUON_AUTOUPDATER_ENABLED
    +	bool "Enable autoupdater by default"
    +
    +endif
    diff --git a/package/gluon-autoupdater/Makefile b/package/gluon-autoupdater/Makefile
    index d6e87499..368dce1b 100644
    --- a/package/gluon-autoupdater/Makefile
    +++ b/package/gluon-autoupdater/Makefile
    @@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk
     PKG_NAME:=gluon-autoupdater
     PKG_VERSION:=4
     
    -PKG_CONFIG_DEPENDS := CONFIG_GLUON_BRANCH
    +PKG_CONFIG_DEPENDS := CONFIG_GLUON_AUTOUPDATER_BRANCH CONFIG_GLUON_AUTOUPDATER_ENABLED
     
     include ../gluon.mk
     
    @@ -13,19 +13,19 @@ define Package/gluon-autoupdater
     endef
     
     define Package/gluon-autoupdater/config
    -config GLUON_BRANCH
    -	string "Gluon autoupdater branch"
    -	depends on PACKAGE_gluon-autoupdater
    -	default ""
    +	source "$(SOURCE)/Config.in"
     endef
     
     define Package/gluon-autoupdater/install
     	$(Gluon/Build/Install)
     
    -ifneq ($(CONFIG_GLUON_BRANCH),"")
     	$(INSTALL_DIR) $(1)/lib/gluon/autoupdater
    -	echo '$(call qstrip,$(CONFIG_GLUON_BRANCH))' > $(1)/lib/gluon/autoupdater/default_branch
    -endif
    +  ifneq ($(CONFIG_GLUON_AUTOUPDATER_BRANCH),"")
    +	echo '$(call qstrip,$(CONFIG_GLUON_AUTOUPDATER_BRANCH))' > $(1)/lib/gluon/autoupdater/default_branch
    +  endif
    +  ifneq ($(CONFIG_GLUON_AUTOUPDATER_ENABLED),)
    +	touch $(1)/lib/gluon/autoupdater/default_enabled
    +  endif
     endef
     
     $(eval $(call BuildPackageGluon,gluon-autoupdater))
    diff --git a/package/gluon-autoupdater/check_site.lua b/package/gluon-autoupdater/check_site.lua
    index e247cc5d..eaabf285 100644
    --- a/package/gluon-autoupdater/check_site.lua
    +++ b/package/gluon-autoupdater/check_site.lua
    @@ -1,4 +1,4 @@
    -need_string(in_site({'autoupdater', 'branch'}))
    +need_string(in_site({'autoupdater', 'branch'}), false)
     
     need_table({'autoupdater', 'branches'}, function(branch)
     	need_alphanumeric_key(branch)
    diff --git a/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater b/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater
    index 88214a86..6ccd3072 100755
    --- a/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater
    +++ b/package/gluon-autoupdater/luasrc/lib/gluon/upgrade/500-autoupdater
    @@ -2,8 +2,11 @@
     
     local site = require 'gluon.site'
     local uci = require('simple-uci').cursor()
    +local unistd = require 'posix.unistd'
     
     
    +local min_branch
    +
     for name, config in pairs(site.autoupdater.branches()) do
     	uci:delete('autoupdater', name)
     	uci:section('autoupdater', 'branch', name, {
    @@ -12,19 +15,26 @@ for name, config in pairs(site.autoupdater.branches()) do
     		good_signatures = config.good_signatures,
     		pubkey = config.pubkeys,
     	})
    +
    +	if not min_branch or (name < min_branch) then
    +		min_branch = name
    +	end
     end
     
     if not uci:get('autoupdater', 'settings') then
    -	local enabled = false
    -	local branch = site.autoupdater.branch()
    +	local enabled = unistd.access('/lib/gluon/autoupdater/default_enabled') ~= nil
     
    +	local branch = site.autoupdater.branch(min_branch)
     	local f = io.open('/lib/gluon/autoupdater/default_branch')
     	if f then
    -		enabled = true
     		branch = f:read('*line')
     		f:close()
     	end
     
    +	if not branch then
    +		enabled = false
    +	end
    +
     	uci:section('autoupdater', 'autoupdater', 'settings', {
     		enabled = enabled,
     		branch = branch,
    diff --git a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
    index 0942c724..490325cf 100755
    --- a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
    +++ b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
    @@ -40,9 +40,6 @@ uci:section('network', 'interface', 'client', {
     
     uci:save('network')
     
    --- TODO: remove this line and the next in 2019. Firewall zones have been renamed in 2017.
    -uci:delete('firewall', 'client')
    -
     uci:section('firewall', 'zone', 'drop', {
     	name = 'drop',
     	network = {'client'},
    @@ -51,9 +48,9 @@ uci:section('firewall', 'zone', 'drop', {
     	forward = 'DROP',
     })
     
    -local networks = uci:get_list('firewall', 'local_client', 'network')
    +local networks = uci:get_list('firewall', 'loc_client', 'network')
     util.add_to_set(networks, 'local_node')
    -uci:set_list('firewall', 'local_client', 'network', networks)
    +uci:set_list('firewall', 'loc_client', 'network', networks)
     
     
     local dnsmasq = uci:get_first('dhcp', 'dnsmasq')
    @@ -61,10 +58,6 @@ uci:set('dhcp', dnsmasq, 'boguspriv', false)
     uci:set('dhcp', dnsmasq, 'localise_queries', false)
     uci:set('dhcp', dnsmasq, 'rebind_protection', false)
     
    --- TODO: remove this line and the next two in 2019 the zones were removed in 2017
    -uci:delete('dhcp', 'client')
    -uci:delete('firewall', 'local_node')
    -
     uci:section('dhcp', 'dhcp', 'local_client', {
     	interface = 'client',
     	ignore = true,
    diff --git a/package/gluon-config-mode-geo-location-osm/check_site.lua b/package/gluon-config-mode-geo-location-osm/check_site.lua
    index 8e69ba0a..2b3b0cb1 100644
    --- a/package/gluon-config-mode-geo-location-osm/check_site.lua
    +++ b/package/gluon-config-mode-geo-location-osm/check_site.lua
    @@ -2,3 +2,9 @@ need_number(in_site({'config_mode', 'geo_location', 'osm', 'center', 'lon'}))
     need_number(in_site({'config_mode', 'geo_location', 'osm', 'center', 'lat'}))
     need_number(in_site({'config_mode', 'geo_location', 'osm', 'zoom'}), false)
     need_string(in_site({'config_mode', 'geo_location', 'osm', 'openlayers_url'}), false)
    +
    +if need_table(in_site({'config_mode', 'geo_location', 'osm', 'tile_layer'}), nil, false) then
    +	need_one_of(in_site({'config_mode', 'geo_location', 'osm', 'tile_layer', 'type'}), {'XYZ'})
    +	need_string(in_site({'config_mode', 'geo_location', 'osm', 'tile_layer', 'url'}))
    +	need_string(in_site({'config_mode', 'geo_location', 'osm', 'tile_layer', 'attributions'}))
    +end
    diff --git a/package/gluon-config-mode-geo-location-osm/luasrc/usr/lib/lua/gluon/config-mode/geo-location-osm.lua b/package/gluon-config-mode-geo-location-osm/luasrc/usr/lib/lua/gluon/config-mode/geo-location-osm.lua
    index 0ae54555..39f968c1 100644
    --- a/package/gluon-config-mode-geo-location-osm/luasrc/usr/lib/lua/gluon/config-mode/geo-location-osm.lua
    +++ b/package/gluon-config-mode-geo-location-osm/luasrc/usr/lib/lua/gluon/config-mode/geo-location-osm.lua
    @@ -19,6 +19,7 @@ function M.options()
     
     	return {
     		openlayers_url = config.openlayers_url(),
    +		tile_layer = config.tile_layer(),
     		zoom = config.zoom(12),
     		pos = config.center(),
     	}
    diff --git a/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua b/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
    index ffa030dd..6a653df8 100644
    --- a/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
    +++ b/package/gluon-config-mode-outdoor/luasrc/lib/gluon/config-mode/wizard/0250-outdoor.lua
    @@ -1,11 +1,17 @@
     return function(form, uci)
     	local platform = require 'gluon.platform'
    +	local wireless = require 'gluon.wireless'
     
     	if not (platform.is_outdoor_device() and platform.device_uses_11a(uci)) then
     		-- only visible on wizard for outdoor devices
     		return
     	end
     
    +	if wireless.preserve_channels(uci) then
    +		-- Don't show if channel should be preserved
    +		return
    +	end
    +
     	local pkg_i18n = i18n 'gluon-config-mode-outdoor'
     
     	local section = form:section(Section, nil, pkg_i18n.translate(
    diff --git a/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css b/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css
    index 16f213e4..3365f449 100644
    --- a/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css
    +++ b/package/gluon-config-mode-theme/files/lib/gluon/config-mode/www/static/gluon.css
    @@ -1 +1 @@
    -.lang_he{direction:RTL;unicode-bidi:embed}.hidden{display:none}html{min-height:100%;height:auto;position:relative}body,input,select,option{font-family:'Open Sans', Arial, sans-serif;font-size:12pt}body{color:#4d4e53;line-height:1.5em;margin:0;display:flex;flex-direction:column;min-height:100vh;background-color:#f3f3f3}a img{border:none;text-decoration:none}.tabmenu1{text-align:center}ul.tabmenu{list-style:none;padding:0;margin:2em 0;display:inline-flex}ul.tabmenu li{white-space:nowrap;margin:0 0.5em;padding:0;text-align:center}ul.tabmenu li a{display:block;text-decoration:none;padding:1em;margin:0;color:#333;border-radius:2em}ul.tabmenu li a:hover{background:#ffe9b3}ul.tabmenu li.active a{font-weight:bold;background:white;color:#333}abbr,acronym{font-style:normal;font-variant:normal}abbr[title],acronym[title]{border-bottom:1px dotted;cursor:help}a:link abbr[title],a:visited abbr[title],a:link acronym[title],a:visited acronym[title]{cursor:pointer}code{font-family:monospace;white-space:pre}#maincontent ul{margin-left:2em}.clear{clear:both}.error{color:#ff0000;background-color:white}#menubar{display:flex;background:#dc0067;color:#ffffff}#menubar a:link.topcat,#menubar a:visited.topcat{position:relative;display:block;padding:0.5em;text-decoration:none;font-size:80%;font-weight:normal;color:white}#menubar a:link.topcat:hover,#menubar a:link.topcat:focus,#menubar a:visited.topcat:hover,#menubar a:visited.topcat:focus{background:#ffb400;color:black}#menubar a:link.topcat.active,#menubar a:visited.topcat.active{background:#ffb400;color:black;font-weight:bold}#menubar div.hostinfo{position:relative;margin:0;padding:0.5em;flex:1;font-weight:bold;font-size:80%}#menubar div.hostinfo a:link,#menubar div.hostinfo a:visited{text-decoration:none;font-weight:bold;color:white}#menubar div.hostinfo a:link:hover,#menubar div.hostinfo a:link:focus,#menubar div.hostinfo a:visited:hover,#menubar div.hostinfo a:visited:focus{text-decoration:underline}#topmenu{list-style:none;margin:0;padding:0}#topmenu li{display:inline-block}#maincontent{padding:0 1em 2em;max-width:60em;min-width:40em;margin:1em auto}#maincontent p{margin-bottom:1em}.gluon-section{margin:0;padding:0;border:none;margin-bottom:1.3em}.gluon-section:last-child{margin-bottom:0.7em}.gluon-section legend{font-size:1.4em;font-weight:bold;position:relative;padding:0;margin-bottom:0.5em}.gluon-section h2{margin:0em 0 0.5em -0.5em !important}.gluon-section h3{text-decoration:none !important;font-weight:bold !important;color:#555555 !important;margin:0.25em !important;font-size:100% !important}.gluon-section-descr{margin-bottom:2em}.gluon-osm-map{width:100%;height:40em;margin-bottom:1em}input:placeholder{color:#aaaaaa}input:-webkit-input-placeholder{color:#aaaaaa}input:-moz-placeholder{color:#aaaaaa}input:-ms-input-placeholder{color:#aaaaaa}input[type=checkbox]{display:none}input[type=checkbox]+label{display:inline-block;position:relative;width:1em;height:1em;margin:0}input[type=checkbox]:checked+label:after{content:'✔';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.5em;width:100%;text-align:center;font-size:1.7em}input[type=radio]{display:none}input[type=radio]+label{display:inline-block;position:relative;width:0.8em;height:0.8em;padding:0.5em;margin:0.2em 0.2em 0.2em 0.1em;border:none;background:#ffe199;vertical-align:middle;border-radius:50%}input[type=radio]:checked+label:after{content:'•';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.4em;width:100%;text-align:center;font-size:2em}input[type=submit],input[type=reset],input[type=image],input[type=button]{cursor:pointer}select,input,textarea,input[type=checkbox]+label{color:#003247;border:none;background:#ffe199;border-radius:3pt;padding:0.5em;margin-top:1px;margin-bottom:2px;box-sizing:content-box;outline:0}option{color:#003247;background:#ffe199}input[type=image]{border:none}select,input[type=text],input[type=password]{min-width:20em}input.gluon-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;user-select:none;font-size:100%;padding:0.5em 1em;color:rgba(0,0,0,0.8);border:none transparent;background-color:#E6E6E6;text-decoration:none;border-radius:2px;transition:0.1s linear box-shadow;margin-left:0.5em;background-repeat:no-repeat}input.gluon-button::-moz-focus-inner{padding:0;border:0}input.gluon-button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.15) inset,0 0 6px rgba(0,0,0,0.2) inset}input.gluon-button:focus{outline:0}input.gluon-button:hover,input.gluon-button:focus{background-image:linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1))}input.gluon-button[disabled]{border:none;background-image:none;opacity:0.40;cursor:not-allowed;box-shadow:none}input.gluon-button-reset{background-color:#e30;color:#fff}input.gluon-button-submit{background-color:#009ee0;color:#fff}input.gluon-button-submit:active{background:grey}.gluon-input-invalid{background:#e30 !important;color:white}div.gluon-section-remove input{border-bottom:none}textarea{margin-left:-1px;margin-bottom:0.5em}.gluon-section .gluon-rowstyle-1 h3{background-color:#eeeeff;color:#555555}.gluon-rowstyle-2{color:#000000}div.gluon-value{display:flex;flex-direction:row;margin-bottom:0.5em}.gluon-section-node .gluon-value:last-child{margin-bottom:0}.gluon-value-title{flex:2;text-align:right;padding-top:0.39em;padding-right:1em;font-weight:bold}div.gluon-value-field{flex:3;position:relative}div.gluon-value-field input,div.gluon-value-field select,div.gluon-value-field input+label{position:relative}div.gluon-value-field-text{flex:3;padding-top:0.39em}div.gluon-value-field-long{flex:10;position:relative;margin-top:0.65em}div.gluon-value-field-long input,div.gluon-value-field-long select,div.gluon-value-field-long input+label{position:relative}div.gluon-value-field-long-after{flex:2}div.gluon-value-description{font-size:8pt}div.gluon-section-create{clear:left;white-space:nowrap;vertical-align:top}div.gluon-section-create .gluon-button{margin:0.25em}input.gluon-section-create-name{margin-right:-0.25em}div.gluon-form-descr{margin-bottom:1em}.gluon-form-descr:empty,.gluon-section-descr:empty{display:none}.gluon-form-descr,.gluon-section-descr,.gluon-page-actions{padding:1em;background:#ececec}.gluon-page-actions{text-align:right;display:flex;flex-flow:row-reverse}div.gluon-optionals{padding:0.25em;border-bottom:1px dotted #bbbbbb}div.gluon-section-remove{float:right}.gluon-section-node{clear:both;position:relative;border:none}.gluon-section-node-tabbed{border-top-left-radius:0}div.gluon-error{font-size:95%;font-weight:bold;color:#ff0000;background-color:#ffffff}.gluon-value-error input,.gluon-value-error select{background-color:#ffcccc}.gluon-value-field var{color:#2222FF}.gluon-add:after,.gluon-remove:after{cursor:pointer;display:inline-block;text-align:center;vertical-align:middle;font-size:180%;width:1.2em;height:1em}.gluon-add{color:#008000;position:relative;left:21em}input+.gluon-add{left:0;top:0.04em}.gluon-add:first-child{top:0.53em;left:-0.08em}.gluon-add:after{content:'+'}.gluon-remove{color:#800000;position:relative;top:-0.03em}.gluon-remove:after{content:'–'}.left{text-align:left !important}.right{text-align:right !important}.inline{display:inline}.error500{border:1px dotted #ff0000;background-color:#ffffff;color:#000000;padding:0.5em}.errorbox{border:1px solid #FF0000;background-color:#FFCCCC;padding:5px;margin-bottom:5px}.errorbox a{color:#000000 !important}.the-key{text-align:left;font-size:1.4em;background:#ffe9b3;border:3pt dashed #dc0067;margin-bottom:0.5em;padding:0.5em}
    +html{min-height:100%;height:auto;position:relative}body,input,select,option{font-family:'Open Sans', Arial, sans-serif;font-size:12pt}body{color:#4d4e53;line-height:1.5em;margin:0;display:flex;flex-direction:column;min-height:100vh;background-color:#f3f3f3}.tabmenu1{text-align:center}ul.tabmenu{list-style:none;padding:0;margin:2em 0;display:inline-flex}ul.tabmenu li{white-space:nowrap;margin:0 0.5em;padding:0;text-align:center}ul.tabmenu li a{display:block;text-decoration:none;padding:1em;margin:0;color:#333;border-radius:2em}ul.tabmenu li a:hover{background:#ffe9b3}ul.tabmenu li.active a{font-weight:bold;background:white;color:#333}#maincontent ul{margin-left:2em}.error{color:#ff0000;background-color:white}#menubar{display:flex;background:#dc0067;color:#ffffff}#menubar a:link.topcat,#menubar a:visited.topcat{position:relative;display:block;padding:0.5em;text-decoration:none;font-size:80%;font-weight:normal;color:white}#menubar a:link.topcat:hover,#menubar a:link.topcat:focus,#menubar a:visited.topcat:hover,#menubar a:visited.topcat:focus{background:#ffb400;color:black}#menubar a:link.topcat.active,#menubar a:visited.topcat.active{background:#ffb400;color:black;font-weight:bold}#menubar .hostinfo{position:relative;margin:0;padding:0.5em;flex:1;font-weight:bold;font-size:80%}#menubar .hostinfo a:link,#menubar .hostinfo a:visited{text-decoration:none;font-weight:bold;color:white}#menubar .hostinfo a:link:hover,#menubar .hostinfo a:link:focus,#menubar .hostinfo a:visited:hover,#menubar .hostinfo a:visited:focus{text-decoration:underline}#topmenu{list-style:none;margin:0;padding:0}#topmenu li{display:inline-block}#maincontent{padding:0 1em 2em;max-width:60em;min-width:40em;margin:1em auto}#maincontent p{margin-bottom:1em}.gluon-section{margin:0;padding:0;border:none;margin-bottom:1.3em}.gluon-section:last-child{margin-bottom:0.7em}.gluon-section legend{font-size:1.4em;font-weight:bold;position:relative;padding:0;margin-bottom:0.5em}.gluon-section h2{margin:0em 0 0.5em -0.5em}.gluon-section h3{text-decoration:none;font-weight:bold;color:#555555;margin:0.25em;font-size:100%}.gluon-section-descr{margin-bottom:2em}.gluon-osm-map{width:100%;height:40em;margin-bottom:1em}input::placeholder{color:#aaaaaa}input::-webkit-input-placeholder{color:#aaaaaa}input[type=checkbox]{display:none}input[type=checkbox]+label{display:inline-block;position:relative;width:1em;height:1em;margin:0}input[type=checkbox]:checked+label::after{content:'✔';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.5em;width:100%;text-align:center;font-size:1.7em}input[type=radio]{display:none}input[type=radio]+label{display:inline-block;position:relative;width:0.8em;height:0.8em;padding:0.5em;margin:0.2em 0.2em 0.2em 0.1em;border:none;background:#ffe199;vertical-align:middle;border-radius:50%}input[type=radio]:checked+label::after{content:'•';color:#dc0067;vertical-align:middle;position:absolute;top:50%;left:0;margin-top:-0.4em;width:100%;text-align:center;font-size:2em}input[type=submit],input[type=reset],input[type=button]{cursor:pointer}select,input,textarea,input[type=checkbox]+label{color:#003247;border:none;background:#ffe199;border-radius:3pt;padding:0.5em;margin-top:1px;margin-bottom:2px;box-sizing:content-box;outline:0}.select-wrapper{position:relative;display:inline-block}.select-wrapper::before{position:absolute;z-index:1;right:0.05em;top:calc(2px + 0.1em);bottom:calc(2px + 0.1em);width:1.4em;border-left:0.05em solid rgba(0,0,0,0.25);pointer-events:none;background:url('data:image/svg+xml;utf8,') center/0.8em 0.5em no-repeat;content:''}.select-wrapper select{-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}option{color:#003247;background:#ffe199}select,input[type=text],input[type=password]{min-width:20em}.gluon-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;user-select:none;font-size:100%;padding:0.5em 1em;color:rgba(0,0,0,0.8);background-color:#E6E6E6;border:none;text-decoration:none;border-radius:2px;transition:0.1s linear box-shadow;margin-left:0.5em;background-repeat:no-repeat}.gluon-button::-moz-focus-inner{padding:0;border:0}.gluon-button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.15) inset,0 0 6px rgba(0,0,0,0.2) inset}.gluon-button:focus{outline:0}.gluon-button:hover,.gluon-button:focus{background-image:linear-gradient(transparent, rgba(0,0,0,0.05) 40%, rgba(0,0,0,0.1))}.gluon-button[disabled]{border:none;background-image:none;opacity:0.40;cursor:not-allowed;box-shadow:none}.gluon-button-reset{background-color:#e30;color:#fff}.gluon-button-submit{background-color:#009ee0;color:#fff}.gluon-button-submit:active{background:grey}.gluon-input-invalid{background:#e30 !important;color:white}textarea{margin-left:-1px;margin-bottom:0.5em}.gluon-value{display:flex;flex-direction:row;margin-bottom:0.5em}.gluon-section-node .gluon-value:last-child{margin-bottom:0}.gluon-value-title{flex:2;text-align:right;padding-top:0.39em;padding-right:1em;font-weight:bold}.gluon-value-field{flex:3;position:relative}.gluon-value-field input,.gluon-value-field select,.gluon-value-field input+label{position:relative}.gluon-value-field-text{flex:3;padding-top:0.39em}.gluon-value-field-long{flex:10;position:relative;margin-top:0.65em}.gluon-value-field-long input,.gluon-value-field-long select,.gluon-value-field-long input+label{position:relative}.gluon-value-field-long-after{flex:2}.gluon-value-description{font-size:8pt}.gluon-form-descr{margin-bottom:1em}.gluon-form-descr:empty,.gluon-section-descr:empty{display:none}.gluon-form-descr,.gluon-section-descr,.gluon-page-actions{padding:1em;background:#ececec}.gluon-page-actions{text-align:right;display:flex;flex-flow:row-reverse}.gluon-section-node{clear:both;position:relative;border:none}.gluon-value-error input,.gluon-value-error select{background-color:#ffcccc}.gluon-add::after,.gluon-remove::after{cursor:pointer;display:inline-block;text-align:center;vertical-align:middle;font-size:180%;width:1.2em;height:1em}.gluon-add{color:#008000;position:relative;left:21em}input+.gluon-add{left:0;top:0.04em}.gluon-add:first-child{top:0.53em;left:-0.08em}.gluon-add::after{content:'+'}.gluon-remove{color:#800000;position:relative;top:-0.03em}.gluon-remove::after{content:'–'}.error500{border:1px dotted #ff0000;background-color:#ffffff;color:#000000;padding:0.5em}.errorbox{border:1px solid #FF0000;background-color:#FFCCCC;padding:5px;margin-bottom:5px}.errorbox a{color:#000000 !important}.the-key{text-align:left;font-size:1.4em;background:#ffe9b3;border:3pt dashed #dc0067;margin-bottom:0.5em;padding:0.5em}
    diff --git a/package/gluon-config-mode-theme/sass/gluon.scss b/package/gluon-config-mode-theme/sass/gluon.scss
    index e1a59560..5e1aeda7 100644
    --- a/package/gluon-config-mode-theme/sass/gluon.scss
    +++ b/package/gluon-config-mode-theme/sass/gluon.scss
    @@ -1,13 +1,13 @@
     /*
    -   ATTENTION: This file is not compiled when building gluon.
    -   The compiled version is at ../files/lib/gluon/config-mode/www/static/gluon.css
    +	ATTENTION: This file is not compiled when building gluon.
    +	The compiled version is at ../files/lib/gluon/config-mode/www/static/gluon.css
     
    -   Use sass like this to update it:
    +	Use sass like this to update it:
     
    -   sass --sourcemap=none -C -t compressed sass/gluon.scss files/lib/gluon/config-mode/www/static/gluon.css
    +	sass --sourcemap=none -C -t compressed sass/gluon.scss files/lib/gluon/config-mode/www/static/gluon.css
     
    -   When commiting changes to this file make sure to commit the respective
    -   changes to the compilid version within the same commit!
    +	When commiting changes to this file make sure to commit the respective
    +	changes to the compilid version within the same commit!
     */
     
     @charset "utf-8";
    @@ -18,624 +18,530 @@ $ffzusatz:  #009ee0;
     $red:       #ee3300;
     
     @mixin button {
    -  &::-moz-focus-inner {
    -    padding: 0;
    -    border: 0;
    -  }
    +	&::-moz-focus-inner {
    +		padding: 0;
    +		border: 0;
    +	}
     
    -  display: inline-block;
    -  zoom: 1;
    -  line-height: normal;
    -  white-space: nowrap;
    -  vertical-align: baseline;
    -  text-align: center;
    -  cursor: pointer;
    -  user-select: none;
    +	display: inline-block;
    +	line-height: normal;
    +	white-space: nowrap;
    +	vertical-align: baseline;
    +	text-align: center;
    +	cursor: pointer;
    +	user-select: none;
     
     
    -  font-size: 100%;
    -  padding: 0.5em 1em;
    -  color: rgba(0, 0, 0, 0.80);
    -  border: none rgba(0, 0, 0, 0);
    -  background-color: #E6E6E6;
    -  text-decoration: none;
    -  border-radius: 2px;
    +	font-size: 100%;
    +	padding: 0.5em 1em;
    +	color: rgba(0, 0, 0, 0.80);
    +	background-color: #E6E6E6;
    +	border: none;
    +	text-decoration: none;
    +	border-radius: 2px;
     
    -  /* Transitions */
    -  transition: 0.1s linear box-shadow;
    +	/* Transitions */
    +	transition: 0.1s linear box-shadow;
     
    -  &:active {
    -    box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
    -  }
    +	&:active {
    +		box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
    +	}
     
    -  &:focus {
    -    outline: 0;
    -  }
    +	&:focus {
    +		outline: 0;
    +	}
     
    -  &:hover, &:focus {
    -    background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
    -  }
    +	&:hover, &:focus {
    +		background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
    +	}
     
    -  &[disabled] {
    -    border: none;
    -    background-image: none;
    -    opacity: 0.40;
    -    cursor: not-allowed;
    -    box-shadow: none;
    -  }
    -}
    -
    -@mixin button-primary {
    -  background-color: $ffzusatz;
    -  color: #fff;
    -}
    -
    -.lang_he {
    -  direction: RTL;
    -  unicode-bidi: embed;
    -}
    -
    -.hidden {
    -  display: none;
    +	&[disabled] {
    +		border: none;
    +		background-image: none;
    +		opacity: 0.40;
    +		cursor: not-allowed;
    +		box-shadow: none;
    +	}
     }
     
     html {
    -  min-height: 100%;
    -  height: auto;
    -  position:relative;
    +	min-height: 100%;
    +	height: auto;
    +	position: relative;
     }
     
     body, input, select, option {
    -  font-family: 'Open Sans', Arial, sans-serif;
    -  font-size: 12pt;
    +	font-family: 'Open Sans', Arial, sans-serif;
    +	font-size: 12pt;
     }
     
     body {
    -  color: rgb(77, 78, 83);
    -  line-height: 1.5em;
    -  margin: 0;
    -  display: flex;
    -  flex-direction: column;
    -  min-height: 100vh;
    -  background-color: #f3f3f3;
    -}
    -
    -a img {
    -  border: none;
    -  text-decoration: none;
    +	color: rgb(77, 78, 83);
    +	line-height: 1.5em;
    +	margin: 0;
    +	display: flex;
    +	flex-direction: column;
    +	min-height: 100vh;
    +	background-color: #f3f3f3;
     }
     
     .tabmenu1 {
    -  text-align: center;
    +	text-align: center;
     }
     
     ul.tabmenu {
    -  list-style: none;
    -  padding: 0;
    -  margin: 2em 0;
    -  display: inline-flex;
    +	list-style: none;
    +	padding: 0;
    +	margin: 2em 0;
    +	display: inline-flex;
     }
     
     ul.tabmenu li {
    -  white-space: nowrap;
    -  margin: 0 0.5em;
    -  padding: 0;
    -  text-align: center;
    +	white-space: nowrap;
    +	margin: 0 0.5em;
    +	padding: 0;
    +	text-align: center;
     
    -  a {
    -    display: block;
    -    text-decoration: none;
    -    padding: 1em;
    -    margin: 0;
    -    color: #333;
    -    border-radius: 2em;
    +	a {
    +		display: block;
    +		text-decoration: none;
    +		padding: 1em;
    +		margin: 0;
    +		color: #333;
    +		border-radius: 2em;
     
    -    &:hover {
    -      background: lighten($ffyellow, 35);
    -    }
    -  }
    +		&:hover {
    +			background: lighten($ffyellow, 35);
    +		}
    +	}
     
    -  &.active a {
    -    font-weight: bold;
    -    background: white;
    -    color: #333;
    -  }
    -}
    -
    -abbr,
    -acronym {
    -  font-style: normal;
    -  font-variant: normal;
    -}
    -
    -abbr[title],
    -acronym[title] {
    -  border-bottom: 1px dotted;
    -  cursor: help;
    -}
    -
    -a:link abbr[title],
    -a:visited abbr[title],
    -a:link acronym[title],
    -a:visited acronym[title] {
    -  cursor: pointer;
    -}
    -
    -code {
    -  font-family: monospace;
    -  white-space: pre;
    +	&.active a {
    +		font-weight: bold;
    +		background: white;
    +		color: #333;
    +	}
     }
     
     #maincontent ul {
    -  margin-left: 2em;
    -}
    -
    -.clear {
    -  clear: both;
    +	margin-left: 2em;
     }
     
     .error {
    -  color: #ff0000;
    -  background-color: white;
    +	color: #ff0000;
    +	background-color: white;
     }
     
     #menubar {
    -  display: flex;
    -  background: $ffmagenta;
    -  color: #ffffff;
    +	display: flex;
    +	background: $ffmagenta;
    +	color: #ffffff;
     }
     
     #menubar a:link.topcat,
     #menubar a:visited.topcat {
    -  position: relative;
    -  display: block;
    -  padding: 0.5em;
    -  text-decoration: none;
    -  font-size: 80%;
    -  font-weight: normal;
    -  color: white;
    +	position: relative;
    +	display: block;
    +	padding: 0.5em;
    +	text-decoration: none;
    +	font-size: 80%;
    +	font-weight: normal;
    +	color: white;
     
    -  &:hover, &:focus {
    -    background: $ffyellow;
    -    color: black;
    -  }
    +	&:hover, &:focus {
    +		background: $ffyellow;
    +		color: black;
    +	}
     
    -  &.active {
    -    background: $ffyellow;
    -    color: black;
    -    font-weight: bold;
    -  }
    +	&.active {
    +		background: $ffyellow;
    +		color: black;
    +		font-weight: bold;
    +	}
     }
     
    -#menubar div.hostinfo {
    -  position: relative;
    -  margin: 0;
    -  padding: 0.5em;
    -  flex: 1;
    -  font-weight: bold;
    -  font-size: 80%;
    +#menubar .hostinfo {
    +	position: relative;
    +	margin: 0;
    +	padding: 0.5em;
    +	flex: 1;
    +	font-weight: bold;
    +	font-size: 80%;
     }
     
    -#menubar div.hostinfo a {
    -  &:link, &:visited {
    -    text-decoration: none;
    -    font-weight: bold;
    -    color: white;
    +#menubar .hostinfo a {
    +	&:link, &:visited {
    +		text-decoration: none;
    +		font-weight: bold;
    +		color: white;
     
    -    &:hover, &:focus {
    -      text-decoration: underline;
    -    }
    -  }
    +		&:hover, &:focus {
    +			text-decoration: underline;
    +		}
    +	}
     }
     
     #topmenu {
    -  list-style: none;
    -  margin: 0;
    -  padding: 0;
    +	list-style: none;
    +	margin: 0;
    +	padding: 0;
     }
     
     #topmenu li {
    -  display: inline-block;
    +	display: inline-block;
     }
     
     #maincontent {
    -  padding: 0 1em 2em;
    -  max-width: 60em;
    -  min-width: 40em;
    -  margin: 1em auto;
    +	padding: 0 1em 2em;
    +	max-width: 60em;
    +	min-width: 40em;
    +	margin: 1em auto;
     }
     
     #maincontent p {
    -  margin-bottom: 1em;
    +	margin-bottom: 1em;
     }
     
     .gluon-section {
    -  margin: 0;
    -  padding: 0;
    -  border: none;
    -  margin-bottom: 1.3em;
    +	margin: 0;
    +	padding: 0;
    +	border: none;
    +	margin-bottom: 1.3em;
     }
     
     .gluon-section:last-child {
    -  margin-bottom: 0.7em;
    +	margin-bottom: 0.7em;
     }
     
     .gluon-section legend {
    -  font-size: 1.4em;
    -  font-weight: bold;
    -  position: relative;
    -  padding: 0;
    -  margin-bottom: 0.5em;
    +	font-size: 1.4em;
    +	font-weight: bold;
    +	position: relative;
    +	padding: 0;
    +	margin-bottom: 0.5em;
     }
     
     .gluon-section h2 {
    -  margin: 0em 0 0.5em -0.5em !important;
    +	margin: 0em 0 0.5em -0.5em;
     }
     
     .gluon-section h3 {
    -  text-decoration: none !important;
    -  font-weight: bold !important;
    -  color: #555555 !important;
    -  margin: 0.25em !important;
    -  font-size: 100% !important;
    +	text-decoration: none;
    +	font-weight: bold;
    +	color: #555555;
    +	margin: 0.25em;
    +	font-size: 100%;
     }
     
     .gluon-section-descr {
    -  margin-bottom: 2em;
    +	margin-bottom: 2em;
     }
     
     .gluon-osm-map {
    -  width: 100%;
    -  height: 40em;
    -  margin-bottom: 1em;
    +	width: 100%;
    +	height: 40em;
    +	margin-bottom: 1em;
     }
     
    -input:placeholder {
    -  color: #aaaaaa;
    +input::placeholder {
    +	color: #aaaaaa;
     }
     
    -input:-webkit-input-placeholder {
    -  color: #aaaaaa;
    -}
    -
    -input:-moz-placeholder {
    -  color: #aaaaaa;
    -}
    -
    -input:-ms-input-placeholder {
    -  color: #aaaaaa;
    +input::-webkit-input-placeholder {
    +	color: #aaaaaa;
     }
     
     input[type=checkbox] {
    -  display: none;
    +	display: none;
     
    -  & + label {
    -    display: inline-block;
    -    position: relative;
    -    width: 1em;
    -    height: 1em;
    -    margin: 0;
    -  }
    +	& + label {
    +		display: inline-block;
    +		position: relative;
    +		width: 1em;
    +		height: 1em;
    +		margin: 0;
    +	}
     
    -  &:checked + label:after {
    -    content: '✔';
    -    color: $ffmagenta;
    -    vertical-align: middle;
    -    position: absolute;
    -    top: 50%;
    -    left: 0;
    -    margin-top: -0.5em;
    -    width: 100%;
    -    text-align: center;
    -    font-size: 1.7em;
    -  }
    +	&:checked + label::after {
    +		content: '✔';
    +		color: $ffmagenta;
    +		vertical-align: middle;
    +		position: absolute;
    +		top: 50%;
    +		left: 0;
    +		margin-top: -0.5em;
    +		width: 100%;
    +		text-align: center;
    +		font-size: 1.7em;
    +	}
     }
     
     input[type=radio] {
    -  display: none;
    +	display: none;
     
    -  & + label {
    -    display: inline-block;
    -    position: relative;
    -    width: 0.8em;
    -    height: 0.8em;
    -    padding: 0.5em;
    -    margin: 0.2em 0.2em 0.2em 0.1em;
    -    border: none;
    -    background: lighten($ffyellow, 30);
    -    vertical-align: middle;
    -    border-radius: 50%;
    -  }
    +	& + label {
    +		display: inline-block;
    +		position: relative;
    +		width: 0.8em;
    +		height: 0.8em;
    +		padding: 0.5em;
    +		margin: 0.2em 0.2em 0.2em 0.1em;
    +		border: none;
    +		background: lighten($ffyellow, 30);
    +		vertical-align: middle;
    +		border-radius: 50%;
    +	}
     
    -  &:checked + label:after {
    -    content: '•';
    -    color: $ffmagenta;
    -    vertical-align: middle;
    -    position: absolute;
    -    top: 50%;
    -    left: 0;
    -    margin-top: -0.4em;
    -    width: 100%;
    -    text-align: center;
    -    font-size: 2em;
    -  }
    +	&:checked + label::after {
    +		content: '•';
    +		color: $ffmagenta;
    +		vertical-align: middle;
    +		position: absolute;
    +		top: 50%;
    +		left: 0;
    +		margin-top: -0.4em;
    +		width: 100%;
    +		text-align: center;
    +		font-size: 2em;
    +	}
     }
     
     input[type=submit],
     input[type=reset],
    -input[type=image],
     input[type=button] {
    -  cursor: pointer;
    +	cursor: pointer;
     }
     
     select,
     input,
     textarea,
     input[type=checkbox] + label {
    -  color: darken($ffzusatz, 30);
    -  border: none;
    -  background: lighten($ffyellow, 30);
    -  border-radius: 3pt;
    -  padding: 0.5em;
    -  margin-top: 1px;
    -  margin-bottom: 2px;
    -  box-sizing: content-box;
    -  outline: 0;
    +	color: darken($ffzusatz, 30);
    +	border: none;
    +	background: lighten($ffyellow, 30);
    +	border-radius: 3pt;
    +	padding: 0.5em;
    +	margin-top: 1px;
    +	margin-bottom: 2px;
    +	box-sizing: content-box;
    +	outline: 0;
    +}
    +
    +.select-wrapper {
    +	position: relative;
    +	display: inline-block;
    +
    +	&::before {
    +		position: absolute;
    +		z-index: 1;
    +		right: 0.05em;
    +		top: calc(2px + 0.1em);
    +		bottom: calc(2px + 0.1em);
    +		width: 1.4em;
    +		border-left: 0.05em solid rgba(0, 0, 0, 0.25);
    +		pointer-events: none;
    +		background:
    +			url('data:image/svg+xml;utf8,')
    +			center / 0.8em 0.5em
    +			no-repeat
    +		;
    +		content: '';
    +	}
    +
    +	select {
    +		-webkit-appearance: none;
    +		-moz-appearance: none;
    +		appearance: none;
    +		cursor: pointer;
    +	}
     }
     
     option {
    -  color: darken($ffzusatz, 30);
    -  background: lighten($ffyellow, 30);
    -}
    -
    -input[type=image] {
    -  border: none;
    +	color: darken($ffzusatz, 30);
    +	background: lighten($ffyellow, 30);
     }
     
     select,
     input[type=text],
     input[type=password] {
    -  min-width: 20em;
    +	min-width: 20em;
     }
     
    -input.gluon-button {
    -  @include button;
    +.gluon-button {
    +	@include button;
     
    -  margin-left: 0.5em;
    -  background-repeat: no-repeat;
    +	margin-left: 0.5em;
    +	background-repeat: no-repeat;
     }
     
    -input.gluon-button-reset {
    -  background-color: $red;
    -  color: #fff;
    -}
    -input.gluon-button-submit {
    -  @include button-primary;
    +.gluon-button-reset {
    +	background-color: $red;
    +	color: #fff;
     }
     
    -input.gluon-button-submit:active {
    -    background: grey;
    +.gluon-button-submit {
    +	background-color: $ffzusatz;
    +	color: #fff;
    +}
    +.gluon-button-submit:active {
    +	background: grey;
     }
     
     .gluon-input-invalid {
    -  background: $red !important;
    -  color: white;
    -}
    -
    -div.gluon-section-remove input {
    -  border-bottom: none;
    +	background: $red !important;
    +	color: white;
     }
     
     textarea {
    -  margin-left: -1px;
    -  margin-bottom: 0.5em;
    +	margin-left: -1px;
    +	margin-bottom: 0.5em;
     }
     
    -.gluon-section .gluon-rowstyle-1 h3 {
    -  background-color: #eeeeff;
    -  color: #555555;
    -}
    -
    -.gluon-rowstyle-2 {
    -  color: #000000;
    -}
    -
    -div.gluon-value {
    -  display: flex;
    -  flex-direction: row;
    -  margin-bottom: 0.5em;
    +.gluon-value {
    +	display: flex;
    +	flex-direction: row;
    +	margin-bottom: 0.5em;
     }
     
     .gluon-section-node .gluon-value:last-child {
    -  margin-bottom: 0;
    +	margin-bottom: 0;
     }
     
     .gluon-value-title {
    -  flex: 2;
    -  text-align: right;
    -  padding-top: 0.39em;
    -  padding-right: 1em;
    -  font-weight: bold;
    +	flex: 2;
    +	text-align: right;
    +	padding-top: 0.39em;
    +	padding-right: 1em;
    +	font-weight: bold;
     }
     
    -div.gluon-value-field {
    -  flex: 3;
    -  position: relative;
    +.gluon-value-field {
    +	flex: 3;
    +	position: relative;
     
    -  input, select, input + label {
    -    position: relative;
    -  }
    +	input, select, input + label {
    +		position: relative;
    +	}
     }
     
    -div.gluon-value-field-text {
    -  flex: 3;
    -  padding-top: 0.39em;
    +.gluon-value-field-text {
    +	flex: 3;
    +	padding-top: 0.39em;
     }
     
    -div.gluon-value-field-long {
    -  flex: 10;
    -  position: relative;
    -  margin-top: 0.65em;
    +.gluon-value-field-long {
    +	flex: 10;
    +	position: relative;
    +	margin-top: 0.65em;
     
    -  input, select, input + label {
    -    position: relative;
    -  }
    +	input, select, input + label {
    +		position: relative;
    +	}
     }
     
    -div.gluon-value-field-long-after {
    -  flex: 2;
    +.gluon-value-field-long-after {
    +	flex: 2;
     }
     
    -div.gluon-value-description {
    -  font-size: 8pt;
    +.gluon-value-description {
    +	font-size: 8pt;
     }
     
    -div.gluon-section-create {
    -  clear: left;
    -  white-space: nowrap;
    -  vertical-align: top;
    -}
    -
    -div.gluon-section-create .gluon-button {
    -  margin: 0.25em;
    -}
    -
    -input.gluon-section-create-name {
    -  margin-right: -0.25em;
    -}
    -
    -div.gluon-form-descr {
    -  margin-bottom: 1em;
    +.gluon-form-descr {
    +	margin-bottom: 1em;
     }
     
     .gluon-form-descr:empty, .gluon-section-descr:empty {
    -  display: none;
    +	display: none;
     }
     
     .gluon-form-descr, .gluon-section-descr, .gluon-page-actions {
    -  padding: 1em;
    -  background: #ececec;
    +	padding: 1em;
    +	background: #ececec;
     }
     
     .gluon-page-actions {
    -  text-align: right;
    -  display: flex;
    -  flex-flow: row-reverse;
    -}
    -
    -div.gluon-optionals {
    -  padding: 0.25em;
    -  border-bottom: 1px dotted #bbbbbb;
    -}
    -
    -div.gluon-section-remove {
    -  float: right;
    +	text-align: right;
    +	display: flex;
    +	flex-flow: row-reverse;
     }
     
     .gluon-section-node {
    -  clear: both;
    -  position: relative;
    -  border: none;
    -}
    -
    -.gluon-section-node-tabbed {
    -  border-top-left-radius: 0;
    -}
    -
    -div.gluon-error {
    -  font-size: 95%;
    -  font-weight: bold;
    -  color: #ff0000;
    -  background-color: #ffffff;
    +	clear: both;
    +	position: relative;
    +	border: none;
     }
     
     .gluon-value-error input,
     .gluon-value-error select {
    -  background-color: #ffcccc;
    +	background-color: #ffcccc;
     }
     
    -.gluon-value-field var {
    -  color: #2222FF;
    -}
    -
    -.gluon-add:after, .gluon-remove:after {
    -  cursor: pointer;
    -  display: inline-block;
    -  text-align: center;
    -  vertical-align: middle;
    -  font-size: 180%;
    -  width: 1.2em;
    -  height: 1em;
    +.gluon-add::after, .gluon-remove::after {
    +	cursor: pointer;
    +	display: inline-block;
    +	text-align: center;
    +	vertical-align: middle;
    +	font-size: 180%;
    +	width: 1.2em;
    +	height: 1em;
     }
     
     .gluon-add {
    -  color: #008000;
    +	color: #008000;
     
    -  position: relative;
    -  left: 21em;
    +	position: relative;
    +	left: 21em;
     
    -  input + & {
    -    left: 0;
    -    top: 0.04em;
    -  }
    +	input + & {
    +		left: 0;
    +		top: 0.04em;
    +	}
     
    -  &:first-child {
    -    top: 0.53em;
    -    left: -0.08em;
    -  }
    +	&:first-child {
    +		top: 0.53em;
    +		left: -0.08em;
    +	}
     
    -  &:after {
    -    content: '+';
    -  }
    +	&::after {
    +		content: '+';
    +	}
     }
     
     .gluon-remove {
    -  color: #800000;
    +	color: #800000;
     
    -  position: relative;
    -  top: -0.03em;
    +	position: relative;
    +	top: -0.03em;
     
    -  &:after {
    -    content: '–';
    -  }
    -}
    -
    -.left {
    -  text-align: left !important;
    -}
    -
    -.right {
    -  text-align: right !important;
    -}
    -
    -.inline {
    -  display: inline;
    +	&::after {
    +		content: '–';
    +	}
     }
     
     .error500 {
    -  border: 1px dotted #ff0000;
    -  background-color: #ffffff;
    -  color: #000000;
    -  padding: 0.5em;
    +	border: 1px dotted #ff0000;
    +	background-color: #ffffff;
    +	color: #000000;
    +	padding: 0.5em;
     }
     
     .errorbox {
    -  border: 1px solid #FF0000;
    -  background-color: #FFCCCC;
    -  padding: 5px;
    -  margin-bottom: 5px;
    +	border: 1px solid #FF0000;
    +	background-color: #FFCCCC;
    +	padding: 5px;
    +	margin-bottom: 5px;
     }
     
     .errorbox a {
    -  color: #000000 !important;
    +	color: #000000 !important;
     }
     
     
     .the-key {
    -  text-align: left;
    -  font-size: 1.4em;
    -  background: lighten($ffyellow, 35);
    -  border: 3pt dashed $ffmagenta;
    -  margin-bottom: 0.5em;
    -  padding: 0.5em
    +	text-align: left;
    +	font-size: 1.4em;
    +	background: lighten($ffyellow, 35);
    +	border: 3pt dashed $ffmagenta;
    +	margin-bottom: 0.5em;
    +	padding: 0.5em
     }
    diff --git a/package/gluon-core/files/etc/init.d/gluon-core-reconfigure b/package/gluon-core/files/etc/init.d/gluon-core-reconfigure
    new file mode 100755
    index 00000000..0a563571
    --- /dev/null
    +++ b/package/gluon-core/files/etc/init.d/gluon-core-reconfigure
    @@ -0,0 +1,12 @@
    +#!/bin/sh /etc/rc.common
    +
    +# Start right after S10boot
    +START=10
    +
    +start() {
    +	config_load gluon
    +	config_get_bool reconfigure core reconfigure 0
    +	if [ "$reconfigure" = 1 ]; then
    +		gluon-reconfigure
    +	fi
    +}
    diff --git a/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start b/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start
    new file mode 100755
    index 00000000..758bb913
    --- /dev/null
    +++ b/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start
    @@ -0,0 +1,2 @@
    +#!/bin/sh
    +/etc/init.d/gluon-core-reconfigure start
    diff --git a/package/gluon-core/files/lib/gluon/upgrade/100-core-reset-sysctl b/package/gluon-core/files/lib/gluon/upgrade/100-core-reset-sysctl
    deleted file mode 100755
    index 6f6f4316..00000000
    --- a/package/gluon-core/files/lib/gluon/upgrade/100-core-reset-sysctl
    +++ /dev/null
    @@ -1,8 +0,0 @@
    -#!/bin/sh
    -
    -# This script can be removed after Gluon v2018.2
    -
    -# Check for a random line that always was in /etc/sysctl.conf
    -if grep -qxF 'net.ipv4.ip_forward=1' /etc/sysctl.conf; then
    -	echo '# Defaults are configured in /etc/sysctl.d/* and can be customized in this file' >/etc/sysctl.conf
    -fi
    diff --git a/package/gluon-core/files/lib/gluon/upgrade/998-commit b/package/gluon-core/files/lib/gluon/upgrade/998-commit
    index db578a78..8b4be6a9 100755
    --- a/package/gluon-core/files/lib/gluon/upgrade/998-commit
    +++ b/package/gluon-core/files/lib/gluon/upgrade/998-commit
    @@ -1,3 +1,6 @@
     #!/bin/sh
     
    -exec uci commit
    +uci -q batch <<-EOF
    +	delete gluon.core.reconfigure
    +	commit
    +EOF
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain b/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain
    new file mode 100755
    index 00000000..680eab23
    --- /dev/null
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain
    @@ -0,0 +1,48 @@
    +#!/usr/bin/lua
    +
    +local unistd = require 'posix.unistd'
    +
    +
    +if not unistd.access('/lib/gluon/domains/') then
    +	return
    +end
    +
    +
    +local function domain_exists(domain)
    +	return unistd.access('/lib/gluon/domains/' .. domain .. '.json') == 0
    +end
    +
    +
    +local uci = require('simple-uci').cursor()
    +
    +local domain = uci:get('gluon', 'core', 'switch_domain')
    +if domain and not domain_exists(domain) then
    +	io.stderr:write(
    +		string.format("Warning: invalid mesh domain switch to '%s' configured, not switching\n", domain)
    +	)
    +	domain = nil
    +end
    +
    +if not domain then
    +	domain = uci:get('gluon', 'core', 'domain')
    +end
    +if domain and not domain_exists(domain) then
    +	io.stderr:write(
    +		string.format("Warning: invalid mesh domain '%s' configured, resetting to default...\n", domain)
    +	)
    +	domain = nil
    +end
    +
    +if not domain then
    +
    +	-- We can't use gluon.site yet, as it depends on gluon.core.domain to be set
    +	local json = require 'jsonc'
    +	local site = assert(json.load('/lib/gluon/site.json'))
    +
    +	domain = site.default_domain
    +
    +end
    +
    +uci:set('gluon', 'core', 'domain', domain)
    +uci:delete('gluon', 'core', 'switch_domain')
    +uci:save('gluon')
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain b/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain
    deleted file mode 100755
    index 2014dce6..00000000
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain
    +++ /dev/null
    @@ -1,27 +0,0 @@
    -#!/usr/bin/lua
    -
    -local unistd = require 'posix.unistd'
    -
    -
    -if not unistd.access('/lib/gluon/domains/') then
    -	return
    -end
    -
    -
    -local uci = require('simple-uci').cursor()
    -
    -local domain = uci:get('gluon', 'core', 'domain')
    -if domain and not unistd.access('/lib/gluon/domains/' .. domain .. '.json') then
    -	io.stderr:write(string.format("Warning: invalid mesh domain '%s' configured, resetting to default...\n", domain))
    -	domain = nil
    -end
    -
    -if domain then return end
    -
    -
    --- We can't use gluon.site yet, as it depends on gluon.core.domain to be set
    -local json = require 'jsonc'
    -local site = assert(json.load('/lib/gluon/site.json'))
    -
    -uci:set('gluon', 'core', 'domain', site.default_domain)
    -uci:commit('gluon')
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/010-primary-mac b/package/gluon-core/luasrc/lib/gluon/upgrade/010-primary-mac
    index fd1f1232..813d5374 100755
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/010-primary-mac
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/010-primary-mac
    @@ -111,6 +111,7 @@ local primary_addrs = {
     			'c20i',
     			'c50',
     			'tplink,c2-v1',
    +			'ex3700'
     		}},
     		{'x86'},
     	}},
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces b/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
    index 1e101f70..5bb1926d 100755
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
    @@ -11,9 +11,12 @@ end
     local platform = require 'gluon.platform'
     local site = require 'gluon.site'
     
    +local json = require 'jsonc'
     local uci = require('simple-uci').cursor()
     local unistd = require 'posix.unistd'
     
    +local board_data = json.load('/etc/board.json')
    +local network_data = (board_data or {}).network
     
     local function iface_exists(ifaces)
     	if not ifaces then return nil end
    @@ -26,8 +29,8 @@ local function iface_exists(ifaces)
     end
     
     
    -local lan_ifname = iface_exists(uci:get('network', 'lan', 'ifname'))
    -local wan_ifname = iface_exists(uci:get('network', 'wan', 'ifname'))
    +local lan_ifname = iface_exists((network_data.lan or {}).ifname)
    +local wan_ifname = iface_exists((network_data.wan or {}).ifname)
     
     if platform.match('ar71xx', 'generic', {
     	'cpe210',
    @@ -42,6 +45,16 @@ if platform.match('ar71xx', 'generic', {
     	'unifiac-pro',
     }) then
     	lan_ifname, wan_ifname = wan_ifname, lan_ifname
    +elseif platform.match('lantiq') then
    +	local switch_data = board_data.switch or {}
    +	local switch0_data = switch_data.switch0 or {}
    +	local roles_data = switch0_data.roles or {}
    +	for _, role_data in ipairs(roles_data) do
    +		if role_data.role == 'wan' then
    +			wan_ifname = iface_exists(role_data.device)
    +			break
    +		end
    +	end
     end
     
     if wan_ifname and lan_ifname then
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
    index 073d0694..9f1121f6 100755
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
    @@ -54,7 +54,7 @@ end
     
     local function get_channel(radio, config)
     	local channel
    -	if uci:get_first('gluon-core', 'wireless', 'preserve_channels') then
    +	if wireless.preserve_channels(uci) then
     		-- preserved channel always wins
     		channel = radio.channel
     	elseif  (radio.hwmode == '11a' or radio.hwmode == '11na') and is_outdoor() then
    @@ -82,20 +82,20 @@ local function get_htmode(radio)
     end
     
     local function is_disabled(name)
    -	if uci:get('wireless', name) then
    -		return uci:get_bool('wireless', name, 'disabled')
    -	else
    +	if not uci:get('wireless', name) then
     		return nil
     	end
    +
    +	return uci:get_bool('wireless', name, 'disabled')
     end
     
     -- Returns the first argument that is not nil; don't call without any non-nil arguments!
     local function first_non_nil(first, ...)
     	if first ~= nil then
     		return first
    -	else
    -		return first_non_nil(...)
     	end
    +
    +	return first_non_nil(...)
     end
     
     
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/140-firewall-rules b/package/gluon-core/luasrc/lib/gluon/upgrade/300-firewall-rules
    similarity index 84%
    rename from package/gluon-core/luasrc/lib/gluon/upgrade/140-firewall-rules
    rename to package/gluon-core/luasrc/lib/gluon/upgrade/300-firewall-rules
    index e835e2f2..605f6684 100755
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/140-firewall-rules
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/300-firewall-rules
    @@ -18,7 +18,7 @@ end
     
     uci:foreach('firewall', 'zone', reject_input_on_wan)
     
    -for _, zone in ipairs({'mesh', 'local_client', 'wired_mesh'}) do
    +for _, zone in ipairs({'mesh', 'loc_client', 'wired_mesh'}) do
     	-- Other packages assign interfaces to these zones
     	uci:section('firewall', 'zone', zone, {
     		name = zone,
    @@ -52,13 +52,16 @@ for _, zone in ipairs({'mesh', 'local_client', 'wired_mesh'}) do
     		family = 'ipv6',
     		target = 'ACCEPT',
     	})
    -
    -	-- Can be removed soon: was never in a release
    -	uci:delete('firewall', zone .. '_ICMPv6_out')
     end
     
    -uci:section('firewall', 'rule', 'local_client_ICMPv4_in', {
    -	src = 'local_client',
    +-- ToDo Remove in v2022.x
    +uci:delete('firewall', 'local_client')
    +uci:delete('firewall', 'local_client_ssh')
    +uci:delete('firewall', 'local_client_ICMPv4_in')
    +uci:delete('firewall', 'local_client_ICMPv6_in')
    +
    +uci:section('firewall', 'rule', 'loc_client_ICMPv4_in', {
    +	src = 'loc_client',
     	proto = 'icmp',
     	icmp_type = {
     		'echo-request',
    @@ -67,9 +70,8 @@ uci:section('firewall', 'rule', 'local_client_ICMPv4_in', {
     	target = 'ACCEPT',
     })
     
    -
     -- allow inbound SSH from anywhere
    -for _, zone in ipairs({ 'wan', 'local_client', 'mesh' }) do
    +for _, zone in ipairs({ 'wan', 'loc_client', 'mesh' }) do
     	uci:section('firewall', 'rule', zone .. '_ssh', {
     		name =  zone .. '_ssh',
     		src = zone,
    diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/820-dns-config b/package/gluon-core/luasrc/lib/gluon/upgrade/820-dns-config
    index 18b44d3f..f3fdfbff 100755
    --- a/package/gluon-core/luasrc/lib/gluon/upgrade/820-dns-config
    +++ b/package/gluon-core/luasrc/lib/gluon/upgrade/820-dns-config
    @@ -18,7 +18,7 @@ uci:delete('firewall', 'client_dns')
     if dns.servers then
     	-- allow inbound traffic for dns from client zone
     	uci:section('firewall', 'rule', 'client_dns', {
    -		src = 'local_client',
    +		src = 'loc_client',
     		dest_port = '53',
     		proto = 'tcpudp',
     		target = 'ACCEPT',
    diff --git a/package/gluon-core/luasrc/usr/bin/gluon-switch-domain b/package/gluon-core/luasrc/usr/bin/gluon-switch-domain
    new file mode 100755
    index 00000000..a964bde9
    --- /dev/null
    +++ b/package/gluon-core/luasrc/usr/bin/gluon-switch-domain
    @@ -0,0 +1,56 @@
    +#!/usr/bin/lua
    +
    +local unistd = require 'posix.unistd'
    +
    +
    +local function shift()
    +	table.remove(arg, 1)
    +end
    +
    +local reboot = true
    +if arg[1] == '--no-reboot' then
    +	reboot = false
    +	shift()
    +end
    +
    +local setup_mode = unistd.access('/var/gluon/setup-mode') == 0
    +
    +if #arg ~= 1 then
    +	io.stderr:write('Usage: gluon-switch-domain [--no-reboot] \n')
    +	os.exit(1)
    +end
    +local domain = arg[1]
    +
    +
    +if not unistd.access('/lib/gluon/domains/') then
    +	io.stderr:write('This Gluon firmware does not support multiple mesh domains.\n')
    +	os.exit(1)
    +end
    +
    +
    +local function domain_exists(dom)
    +	return unistd.access('/lib/gluon/domains/' .. dom .. '.json') == 0
    +end
    +
    +if not domain_exists(domain) then
    +	io.stderr:write(string.format("Error: invalid mesh domain '%s'\n", domain))
    +	os.exit(1)
    +end
    +
    +
    +local uci = require('simple-uci').cursor()
    +uci:set('gluon', 'core', 'switch_domain', domain)
    +uci:set('gluon', 'core', 'reconfigure', true)
    +uci:save('gluon')
    +
    +local cmd
    +if setup_mode then
    +	cmd = 'gluon-reconfigure'
    +elseif reboot then
    +	uci:commit('gluon')
    +	cmd = 'reboot'
    +else
    +	cmd = 'gluon-reload'
    +end
    +
    +unistd.execp(cmd, {[0] = cmd})
    diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
    index b4fc708b..213eb150 100644
    --- a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
    +++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
    @@ -66,7 +66,7 @@ end
     
     function M.device_supports_wpa3()
     	-- rt2x00 crashes when enabling WPA3 personal / OWE VAP
    -	if M.match('ramips', 'rt305x') or M.match('ramips', 'mt7620') then
    +	if M.match('ramips', 'rt305x') then
     		return false
     	end
     
    diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua
    index 8a2f4ed8..4f2cf088 100644
    --- a/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua
    +++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua
    @@ -96,4 +96,8 @@ function M.foreach_radio(uci, f)
     	end
     end
     
    +function M.preserve_channels(uci)
    +	return uci:get_first('gluon-core', 'wireless', 'preserve_channels')
    +end
    +
     return M
    diff --git a/package/gluon-ebtables-filter-multicast/luasrc/lib/gluon/ebtables/110-mcast-allow-respondd b/package/gluon-ebtables-filter-multicast/luasrc/lib/gluon/ebtables/110-mcast-allow-respondd
    new file mode 100644
    index 00000000..7df37ec9
    --- /dev/null
    +++ b/package/gluon-ebtables-filter-multicast/luasrc/lib/gluon/ebtables/110-mcast-allow-respondd
    @@ -0,0 +1 @@
    +rule 'MULTICAST_OUT -p IPv6 --ip6-protocol udp --ip6-destination-port 1001 --ip6-dst ff05::2:1001 -j RETURN'
    diff --git a/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
    index b93097a6..af079f36 100644
    --- a/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
    +++ b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
    @@ -67,9 +67,7 @@ end
     
     function M.set_domain_config(domain)
     	if uci:get('gluon', 'core', 'domain') ~= domain.domain_code then
    -		uci:set('gluon', 'core', 'domain', domain.domain_code)
    -		uci:commit('gluon')
    -		os.execute('gluon-reconfigure')
    +		os.execute(string.format("exec gluon-switch-domain --no-reboot '%s'", domain.domain_code))
     		M.log('Set domain "'..domain.domain.domain_names[domain.domain_code]..'"')
     		return true
     	end
    diff --git a/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
    index 12125da7..03852272 100755
    --- a/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
    +++ b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
    @@ -40,7 +40,6 @@ if geo.lat ~= nil and geo.lon ~= nil then
     	local geo_base_domain = hoodutil.get_domain_by_geo(jdomains, geo)
     	if geo_base_domain ~= nil then
     		if hoodutil.set_domain_config(geo_base_domain) then
    -			os.execute("gluon-reload")
     			hoodutil.log('Domain set by geolocation mode.\n')
     		end
     		return
    @@ -51,6 +50,4 @@ else
     end
     
     -- default domain mode
    -if hoodutil.set_domain_config(hoodutil.get_default_domain(hoodutil.get_domains())) then
    -	os.execute("gluon-reload")
    -end
    +hoodutil.set_domain_config(hoodutil.get_default_domain(hoodutil.get_domains()))
    diff --git a/package/gluon-l3roamd/luasrc/lib/gluon/upgrade/150-firewall-l3roamd b/package/gluon-l3roamd/luasrc/lib/gluon/upgrade/320-firewall-l3roamd
    similarity index 100%
    rename from package/gluon-l3roamd/luasrc/lib/gluon/upgrade/150-firewall-l3roamd
    rename to package/gluon-l3roamd/luasrc/lib/gluon/upgrade/320-firewall-l3roamd
    diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/310-gluon-mesh-babel-firewall
    similarity index 92%
    rename from package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall
    rename to package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/310-gluon-mesh-babel-firewall
    index 87433127..5f3a8976 100755
    --- a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/140-gluon-mesh-babel-firewall
    +++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/310-gluon-mesh-babel-firewall
    @@ -23,18 +23,18 @@ uci:section('firewall', 'zone', 'mmfd', {
     
     -- forwardings and respective rules
     uci:section('firewall', 'forwarding', 'fcc', {
    -	src = 'local_client',
    -	dest = 'local_client',
    +	src = 'loc_client',
    +	dest = 'loc_client',
     })
     
     uci:section('firewall', 'forwarding', 'fcm', {
    -	src = 'local_client',
    +	src = 'loc_client',
     	dest = 'mesh',
     })
     
     uci:section('firewall', 'forwarding', 'fmc', {
     	src = 'mesh',
    -	dest = 'local_client',
    +	dest = 'loc_client',
     })
     
     uci:section('firewall', 'forwarding', 'fmm', {
    @@ -44,11 +44,11 @@ uci:section('firewall', 'forwarding', 'fmm', {
     
     uci:section('firewall', 'forwarding', 'flc', {
     	src = 'l3roamd',
    -	dest = 'local_client',
    +	dest = 'loc_client',
     })
     
     uci:section('firewall', 'forwarding', 'fcl', {
    -	src = 'local_client',
    +	src = 'loc_client',
     	dest = 'l3roamd',
     })
     
    diff --git a/package/gluon-mesh-babel/src/respondd.c b/package/gluon-mesh-babel/src/respondd.c
    index 96195d89..94fc3f87 100644
    --- a/package/gluon-mesh-babel/src/respondd.c
    +++ b/package/gluon-mesh-babel/src/respondd.c
    @@ -62,38 +62,40 @@
     
     static struct babelhelper_ctx bhelper_ctx = {};
     
    -static void obtain_if_addr(const char *ifname, char *lladdr) {
    +static bool get_linklocal_address(const char *ifname, char lladdr[INET6_ADDRSTRLEN]) {
     	struct ifaddrs *ifaddr, *ifa;
    -	int family, n;
    +	bool ret = false;
     
     	if (getifaddrs(&ifaddr) == -1) {
     		perror("getifaddrs");
    -		exit(EXIT_FAILURE);
    +		return false;
     	}
     
    -	for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
    -		if (ifa->ifa_addr == NULL)
    +	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
    +		if (!ifa->ifa_addr)
     			continue;
     
    -		family = ifa->ifa_addr->sa_family;
    +		if (ifa->ifa_addr->sa_family != AF_INET6)
    +			continue;
     
    -		if ( (family == AF_INET6) && ( ! strncmp(ifname, ifa->ifa_name, strlen(ifname)) ) ) {
    -			char lhost[INET6_ADDRSTRLEN];
    -			struct in6_addr *address = &((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr;
    -			if (inet_ntop(AF_INET6, address, lhost, INET6_ADDRSTRLEN) == NULL) {
    -				fprintf(stderr, "obtain_if_addr: could not convert ip to string\n");
    -				goto cleanup;
    -			}
    +		if (strcmp(ifname, ifa->ifa_name) != 0)
    +			continue;
     
    -			if (! strncmp("fe80:", lhost, 5) ) {
    -				snprintf( lladdr, NI_MAXHOST, "%s", lhost );
    -				goto cleanup;
    -			}
    +		const struct in6_addr *address = &((const struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
    +		if (!IN6_IS_ADDR_LINKLOCAL(address))
    +			continue;
    +
    +		if (!inet_ntop(AF_INET6, address, lladdr, INET6_ADDRSTRLEN)) {
    +			perror("inet_ntop");
    +			continue;
     		}
    +
    +		ret = true;
    +		break;
     	}
     
    -cleanup:
     	freeifaddrs(ifaddr);
    +	return ret;
     }
     
     
    @@ -154,29 +156,32 @@ free:
     	return retval;
     }
     
    -static bool interface_file_exists(const char *ifname, const char *name) {
    -	const char *format = "/sys/class/net/%s/%s";
    -	char path[strlen(format) + strlen(ifname) + strlen(name)+1];
    -	snprintf(path, sizeof(path), format, ifname, name);
    -
    -	return !access(path, F_OK);
    -}
    -
     static void mesh_add_if(const char *ifname, struct json_object *wireless,
     		struct json_object *tunnel, struct json_object *other) {
    -	char str_ip[NI_MAXHOST] = {};
    +	char str_ip[INET6_ADDRSTRLEN];
     
    -	obtain_if_addr(ifname, str_ip);
    +	if (!get_linklocal_address(ifname, str_ip))
    +		return;
     
     	struct json_object *address = json_object_new_string(str_ip);
     
    -	if (interface_file_exists(ifname, "wireless"))
    -		json_object_array_add(wireless, address);
    -	else if (interface_file_exists(ifname, "tun_flags"))
    -		json_object_array_add(tunnel, address);
    -	else
    -		json_object_array_add(other, address);
    +	/* In case of VLAN and bridge interfaces, we want the lower interface
    +	 * to determine the interface type (but not for the interface address) */
    +	char lowername[IF_NAMESIZE];
    +	gluonutil_get_interface_lower(lowername, ifname);
     
    +	switch(gluonutil_get_interface_type(lowername)) {
    +	case GLUONUTIL_INTERFACE_TYPE_WIRELESS:
    +		json_object_array_add(wireless, address);
    +		break;
    +
    +	case GLUONUTIL_INTERFACE_TYPE_TUNNEL:
    +		json_object_array_add(tunnel, address);
    +		break;
    +
    +	default:
    +		json_object_array_add(other, address);
    +	}
     }
     
     
    @@ -193,24 +198,26 @@ static bool handle_neighbour(char **data, void *obj) {
     		if (data[REACH])
     			json_object_object_add(neigh, "reachability", json_object_new_double(strtod(data[REACH], NULL)));
     
    -		struct json_object *nif = 0;
    -		if (data[IF] && !json_object_object_get_ex(obj, data[IF], &nif)) {
    -			char str_ip[NI_MAXHOST] = {};
    -			obtain_if_addr( (const char*)data[IF], str_ip );
    +		if (!data[IF])
    +			return true;
    +
    +		struct json_object *nif;
    +		if (!json_object_object_get_ex(obj, data[IF], &nif)) {
    +			char str_ip[INET6_ADDRSTRLEN];
     
     			nif = json_object_new_object();
     
    -			json_object_object_add(nif, "ll-addr", json_object_new_string(str_ip));
    +			if (get_linklocal_address(data[IF], str_ip))
    +				json_object_object_add(nif, "ll-addr", json_object_new_string(str_ip));
    +
     			json_object_object_add(nif, "protocol", json_object_new_string("babel"));
     			json_object_object_add(obj, data[IF], nif);
     
    -		}
    -		struct json_object *neighborcollector = 0;
    -		if (!json_object_object_get_ex(nif, "neighbours", &neighborcollector)) {
    -			neighborcollector = json_object_new_object();
    -			json_object_object_add(nif, "neighbours", neighborcollector);
    +			json_object_object_add(nif, "neighbours", json_object_new_object());
     		}
     
    +		struct json_object *neighborcollector;
    +		json_object_object_get_ex(nif, "neighbours", &neighborcollector);
     		json_object_object_add(neighborcollector, data[ADDRESS], neigh);
     
     	}
    @@ -277,6 +284,13 @@ static void blobmsg_handle_list(struct blob_attr *attr, int len, bool array, str
     	free(proto);
     }
     
    +static void add_if_not_empty(struct json_object *obj, const char *key, struct json_object *val) {
    +	if (json_object_array_length(val))
    +		json_object_object_add(obj, key, val);
    +	else
    +		json_object_put(val);
    +}
    +
     static void receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg) {
     	struct json_object *ret = json_object_new_object();
     	struct json_object *wireless = json_object_new_array();
    @@ -298,9 +312,9 @@ static void receive_call_result_data(struct ubus_request *req, int type, struct
     
     	blobmsg_handle_list(blobmsg_data(msg), blobmsg_data_len(msg), false, wireless, tunnel, other);
     
    -	json_object_object_add(ret, "wireless", wireless);
    -	json_object_object_add(ret, "tunnel", tunnel);
    -	json_object_object_add(ret, "other", other);
    +	add_if_not_empty(ret, "wireless", wireless);
    +	add_if_not_empty(ret, "tunnel", tunnel);
    +	add_if_not_empty(ret, "other", other);
     
     	*((struct json_object**)(req->priv)) = ret;
     }
    diff --git a/package/gluon-mesh-batman-adv/files/lib/gluon/core/mesh/teardown.d/70-gluon-mesh-batman-adv b/package/gluon-mesh-batman-adv/files/lib/gluon/core/mesh/teardown.d/70-gluon-mesh-batman-adv
    index a8850f18..d74b1bf7 100755
    --- a/package/gluon-mesh-batman-adv/files/lib/gluon/core/mesh/teardown.d/70-gluon-mesh-batman-adv
    +++ b/package/gluon-mesh-batman-adv/files/lib/gluon/core/mesh/teardown.d/70-gluon-mesh-batman-adv
    @@ -1,5 +1,5 @@
     #!/bin/sh
     
     lock /var/lock/gluon_bat0.lock
    -batctl interface del "$IFNAME" 2>/dev/null
    +batctl interface -M del "$IFNAME" 2>/dev/null
     lock -u /var/lock/gluon_bat0.lock
    diff --git a/package/gluon-mesh-batman-adv/src/respondd-nodeinfo.c b/package/gluon-mesh-batman-adv/src/respondd-nodeinfo.c
    index 36e330f9..268a3623 100644
    --- a/package/gluon-mesh-batman-adv/src/respondd-nodeinfo.c
    +++ b/package/gluon-mesh-batman-adv/src/respondd-nodeinfo.c
    @@ -131,35 +131,23 @@ static void mesh_add_subif(const char *ifname, struct json_object *wireless,
     			   struct json_object *tunnel, struct json_object *other) {
     	struct json_object *address = gluonutil_wrap_and_free_string(gluonutil_get_interface_address(ifname));
     
    -	char lowername[IFNAMSIZ];
    -	strncpy(lowername, ifname, sizeof(lowername)-1);
    -	lowername[sizeof(lowername)-1] = 0;
    -
    -	const char *format = "/sys/class/net/%s/lower_*";
    -	char pattern[strlen(format) + IFNAMSIZ];
    -
     	/* In case of VLAN and bridge interfaces, we want the lower interface
     	 * to determine the interface type (but not for the interface address) */
    -	while (true) {
    -		snprintf(pattern, sizeof(pattern), format, lowername);
    -		size_t pattern_len = strlen(pattern);
    +	char lowername[IF_NAMESIZE];
    +	gluonutil_get_interface_lower(lowername, ifname);
     
    -		glob_t lower;
    -		if (glob(pattern, GLOB_NOSORT, NULL, &lower))
    -			break;
    -
    -		strncpy(lowername, lower.gl_pathv[0] + pattern_len - 1, sizeof(lowername)-1);
    -
    -		globfree(&lower);
    -	}
    -
    -	if (interface_file_exists(lowername, "wireless"))
    +	switch(gluonutil_get_interface_type(lowername)) {
    +	case GLUONUTIL_INTERFACE_TYPE_WIRELESS:
     		json_object_array_add(wireless, address);
    -	else if (interface_file_exists(lowername, "tun_flags"))
    -		json_object_array_add(tunnel, address);
    -	else
    -		json_object_array_add(other, address);
    +		break;
     
    +	case GLUONUTIL_INTERFACE_TYPE_TUNNEL:
    +		json_object_array_add(tunnel, address);
    +		break;
    +
    +	default:
    +		json_object_array_add(other, address);
    +	}
     }
     
     static struct json_object * get_mesh_subifs(const char *ifname) {
    diff --git a/package/gluon-mesh-wireless-sae/Makefile b/package/gluon-mesh-wireless-sae/Makefile
    index 32b867a4..a58823cc 100644
    --- a/package/gluon-mesh-wireless-sae/Makefile
    +++ b/package/gluon-mesh-wireless-sae/Makefile
    @@ -7,7 +7,7 @@ include ../gluon.mk
     
     define Package/gluon-mesh-wireless-sae
       TITLE:=Encryption of 802.11s Mesh Links through SAE
    -  DEPENDS:=+gluon-core +wpa-supplicant-openssl
    +  DEPENDS:=+gluon-core +wpa-supplicant-mesh-wolfssl
     endef
     
     $(eval $(call BuildPackageGluon,gluon-mesh-wireless-sae))
    diff --git a/package/gluon-radv-filterd/src/gluon-radv-filterd.c b/package/gluon-radv-filterd/src/gluon-radv-filterd.c
    index ed090c46..b1620cae 100644
    --- a/package/gluon-radv-filterd/src/gluon-radv-filterd.c
    +++ b/package/gluon-radv-filterd/src/gluon-radv-filterd.c
    @@ -559,14 +559,13 @@ static int parse_tt_local(struct nl_msg *msg,
     }
     
     static void update_tqs(void) {
    +	static const struct ether_addr unspec = {};
     	struct router *router;
     	bool update_originators = false;
    -	struct ether_addr unspec;
     	struct batadv_nlquery_opts opts;
     	int ret;
     
     	// reset TQs
    -	memset(&unspec, 0, sizeof(unspec));
     	foreach(router, G.routers) {
     		router->tq = 0;
     		if (ether_addr_equal(router->originator, unspec))
    @@ -609,12 +608,12 @@ static void update_tqs(void) {
     	foreach(router, G.routers) {
     		if (router->tq == 0) {
     			if (ether_addr_equal(router->originator, unspec))
    -				fprintf(stderr,
    -					"Unable to find router " F_MAC " in transtable_{global,local}\n",
    +				DEBUG_MSG(
    +					"Unable to find router " F_MAC " in transtable_{global,local}",
     					F_MAC_VAR(router->src));
     			else
    -				fprintf(stderr,
    -					"Unable to find TQ for originator " F_MAC " (router " F_MAC ")\n",
    +				DEBUG_MSG(
    +					"Unable to find TQ for originator " F_MAC " (router " F_MAC ")",
     					F_MAC_VAR(router->originator),
     					F_MAC_VAR(router->src));
     		}
    diff --git a/package/gluon-radv-filterd/src/mac.h b/package/gluon-radv-filterd/src/mac.h
    index cc24d907..eece1105 100644
    --- a/package/gluon-radv-filterd/src/mac.h
    +++ b/package/gluon-radv-filterd/src/mac.h
    @@ -1,4 +1,7 @@
    +#pragma once
    +
     #include 
    +#include 
     #include 
     
     #define F_MAC "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
    diff --git a/package/gluon-respondd/luasrc/lib/gluon/upgrade/400-respondd-firewall b/package/gluon-respondd/luasrc/lib/gluon/upgrade/400-respondd-firewall
    index 2419b108..a9d0b43b 100755
    --- a/package/gluon-respondd/luasrc/lib/gluon/upgrade/400-respondd-firewall
    +++ b/package/gluon-respondd/luasrc/lib/gluon/upgrade/400-respondd-firewall
    @@ -18,7 +18,7 @@ uci:section('firewall', 'rule', 'wan_respondd', {
     -- Allow respondd-access for local clients
     uci:section('firewall', 'rule', 'client_respondd', {
     	name = 'client_respondd',
    -	src = 'local_client',
    +	src = 'loc_client',
     	src_ip = 'fe80::/64',
     	dest_port = '1001',
     	proto = 'udp',
    diff --git a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
    similarity index 92%
    rename from package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain
    rename to package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
    index 31fcea36..1d7e5898 100755
    --- a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain
    +++ b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
    @@ -60,8 +60,5 @@ if not switch_after_min_reached() and not switch_time_passed() then
     	os.exit(0)
     end
     
    -uci:set("gluon", "core", "domain", target_domain)
    -uci:commit("gluon")
    -
    -os.execute("gluon-reconfigure")
    -os.execute("reboot")
    +local cmd = {[0] = 'gluon-switch-domain', target_domain}
    +unistd.execp(cmd[0], cmd)
    diff --git a/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
    index c221eeda..77803eee 100755
    --- a/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
    +++ b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
    @@ -14,5 +14,5 @@ end
     -- Only in case domain switch is scheduled
     local f = io.open(cronfile, "w")
     f:write("* * * * *  /usr/bin/gluon-check-connection\n")
    -f:write("*/5 * * * *  /usr/bin/gluon-switch-domain\n")
    +f:write("*/5 * * * *  /lib/gluon/scheduled-domain-switch/switch-domain\n")
     f:close()
    diff --git a/package/gluon-setup-mode/Makefile b/package/gluon-setup-mode/Makefile
    index ff1c730b..dfc1717a 100644
    --- a/package/gluon-setup-mode/Makefile
    +++ b/package/gluon-setup-mode/Makefile
    @@ -17,4 +17,26 @@ define Package/gluon-setup-mode/description
     	Offline mode to perform basic setup in a secure manner.
     endef
     
    +init_links := \
    +	K89log \
    +	K98boot \
    +	K99umount \
    +	S00sysfixtime \
    +	S10boot \
    +	S10gluon-core-reconfigure \
    +	S10system \
    +	S11sysctl \
    +	S12log \
    +	S95done
    +
    +define Package/gluon-setup-mode/install
    +	$(Gluon/Build/Install)
    +
    +	$(LN) S20network $(1)/lib/gluon/setup-mode/rc.d/K90network
    +
    +	for link in $(init_links); do \
    +		$(LN) "/etc/init.d/$$$${link:3}" "$(1)/lib/gluon/setup-mode/rc.d/$$$${link}"; \
    +	done
    +endef
    +
     $(eval $(call BuildPackageGluon,gluon-setup-mode))
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
    deleted file mode 120000
    index 1e0c5ac0..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/log
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
    deleted file mode 120000
    index 0a43e66b..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
    +++ /dev/null
    @@ -1 +0,0 @@
    -S20network
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
    deleted file mode 120000
    index 64aea5e8..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/boot
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
    deleted file mode 120000
    index b02f4892..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/umount
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
    deleted file mode 120000
    index a4fb1d5b..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/sysfixtime
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
    deleted file mode 120000
    index 64aea5e8..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/boot
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
    deleted file mode 120000
    index 81e8836f..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/system
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
    deleted file mode 120000
    index b4ac535e..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/sysctl
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
    deleted file mode 120000
    index 1e0c5ac0..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/log
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
    deleted file mode 120000
    index c9f30277..00000000
    --- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
    +++ /dev/null
    @@ -1 +0,0 @@
    -/etc/init.d/done
    \ No newline at end of file
    diff --git a/package/gluon-setup-mode/files/lib/preinit/90_setup_mode b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
    index c38ac281..b9365be1 100644
    --- a/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
    +++ b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
    @@ -7,6 +7,10 @@ setup_mode_enable() {
     
     	if [ "$enabled" = 1 ] || [ "$configured" != 1 ]; then
     		echo '/lib/gluon/setup-mode/rc.d' > /tmp/rc_d_path
    +
    +		# This directory is a marker for scripts to know that we're
    +		# in config mode, but it is also used for temporary files
    +		mkdir -p /var/gluon/setup-mode
     	fi
     }
     
    diff --git a/package/gluon-site/Makefile b/package/gluon-site/Makefile
    index b81ed103..df299c40 100644
    --- a/package/gluon-site/Makefile
    +++ b/package/gluon-site/Makefile
    @@ -49,7 +49,14 @@ define Build/Compile
     	rm -rf $(PKG_BUILD_DIR)/domains
     	mkdir -p $(PKG_BUILD_DIR)/domains
     	$(foreach domain,$(patsubst $(GLUON_SITEDIR)/domains/%.conf,%,$(wildcard $(GLUON_SITEDIR)/domains/*.conf)),
    -		[ ! -e '$(PKG_BUILD_DIR)/domains/$(domain).json' ]
    +		@if [ -e '$(PKG_BUILD_DIR)/domains/$(domain).json' ]; then \
    +			link='$(PKG_BUILD_DIR)/domains/$(domain).json'; \
    +			other="$$$$(basename $$$$(readlink -f "$$$$link") .json)"; \
    +			if [ "$$$$other" ]; then \
    +				echo >&2 "Error: Failed to install domain '"'$(domain)'"', name already taken by domain '$$$$other'."; \
    +			fi; \
    +			exit 1; \
    +		fi
     		$(call GenerateJSON,domains/$(domain))
     		@lua ../../scripts/domain_aliases.lua '$(PKG_BUILD_DIR)/domains/$(domain).json' | while read alias; do \
     			[ "$$$${alias}" != '$(domain)' ] || continue; \
    @@ -57,7 +64,7 @@ define Build/Compile
     			if ! ln -s '$(domain).json' "$$$$link"; then \
     				other="$$$$(basename $$$$(readlink -f "$$$$link") .json)"; \
     				if [ "$$$$other" ]; then \
    -					echo >&2 "Failed to alias domain '"'$(domain)'"' as '$$$$alias', name already taken by domain '$$$$other'."; \
    +					echo >&2 "Error: Failed to alias domain '"'$(domain)'"' as '$$$$alias', name already taken by domain '$$$$other'."; \
     				fi; \
     				exit 1; \
     			fi; \
    diff --git a/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html b/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html
    index 6e4f3647..09bb6c3e 100644
    --- a/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html
    +++ b/package/gluon-status-page/files/lib/gluon/status-page/view/status-page.html
    @@ -142,7 +142,13 @@
     					<%:RAM%><%= statistics('memory', 'memory') %>
     					<%:Filesystem%><%= statistics('rootfs_usage', 'percent') %>
     					<%:Gateway%><%= statistics('gateway') %>
    <%= statistics('gateway_nexthop', 'neighbour') %> - <%:Clients%><%= statistics('clients/total') %> + + +

    <%:Clients%>

    + + + +
    <%:Total%><%= statistics('clients/total') %>
    <%:Wireless 2.4 GHz%><%= statistics('clients/wifi24') %>
    <%:Wireless 5 GHz%><%= statistics('clients/wifi5') %>

    <%:Traffic%>

    diff --git a/package/gluon-status-page/i18n/de.po b/package/gluon-status-page/i18n/de.po index 5caebfde..d979bc37 100644 --- a/package/gluon-status-page/i18n/de.po +++ b/package/gluon-status-page/i18n/de.po @@ -55,9 +55,6 @@ msgstr "Weitergeleitet" msgid "Gateway" msgstr "Gateway" -msgid "Gateway Nexthop" -msgstr "Gateway Nexthop" - msgid "IP address" msgstr "IP-Adresse" @@ -106,6 +103,9 @@ msgstr "Site" msgid "Status" msgstr "Status" +msgid "Total" +msgstr "Gesamt" + msgid "Traffic" msgstr "" @@ -115,6 +115,12 @@ msgstr "Gesendet" msgid "Uptime" msgstr "Laufzeit" +msgid "Wireless 2.4 GHz" +msgstr "" + +msgid "Wireless 5 GHz" +msgstr "" + msgid "connected" msgstr "verbunden" @@ -126,3 +132,6 @@ msgstr "aktiviert" msgid "not connected" msgstr "nicht verbunden" + +#~ msgid "Gateway Nexthop" +#~ msgstr "Gateway Nexthop" diff --git a/package/gluon-status-page/i18n/gluon-status-page.pot b/package/gluon-status-page/i18n/gluon-status-page.pot index e471e878..2aa4b4c4 100644 --- a/package/gluon-status-page/i18n/gluon-status-page.pot +++ b/package/gluon-status-page/i18n/gluon-status-page.pot @@ -46,9 +46,6 @@ msgstr "" msgid "Gateway" msgstr "" -msgid "Gateway Nexthop" -msgstr "" - msgid "IP address" msgstr "" @@ -97,6 +94,9 @@ msgstr "" msgid "Status" msgstr "" +msgid "Total" +msgstr "" + msgid "Traffic" msgstr "" @@ -106,6 +106,12 @@ msgstr "" msgid "Uptime" msgstr "" +msgid "Wireless 2.4 GHz" +msgstr "" + +msgid "Wireless 5 GHz" +msgstr "" + msgid "connected" msgstr "" diff --git a/package/gluon-status-page/luasrc/lib/gluon/upgrade/500-status-page b/package/gluon-status-page/luasrc/lib/gluon/upgrade/500-status-page index 9771a034..b5e7ce44 100755 --- a/package/gluon-status-page/luasrc/lib/gluon/upgrade/500-status-page +++ b/package/gluon-status-page/luasrc/lib/gluon/upgrade/500-status-page @@ -27,7 +27,7 @@ uci:section('uhttpd', 'uhttpd', 'main', { uci:save('uhttpd') -for _, zone in ipairs({'mesh', 'local_client'}) do +for _, zone in ipairs({'mesh', 'loc_client'}) do uci:section('firewall', 'rule', zone .. '_http', { src = zone, dest_port = '80', @@ -35,4 +35,8 @@ for _, zone in ipairs({'mesh', 'local_client'}) do target = 'ACCEPT', }) end + +-- ToDo remove in v2022.x +uci:delete('firewall', 'local_client_http') + uci:save('firewall') diff --git a/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/interface.d/050-gluon-wan-dnsmasq b/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/interface.d/050-gluon-wan-dnsmasq new file mode 100644 index 00000000..585dd7a9 --- /dev/null +++ b/package/gluon-wan-dnsmasq/files/lib/gluon/wan-dnsmasq/interface.d/050-gluon-wan-dnsmasq @@ -0,0 +1,2 @@ +wan6 +wan diff --git a/package/gluon-wan-dnsmasq/luasrc/lib/gluon/wan-dnsmasq/update.lua b/package/gluon-wan-dnsmasq/luasrc/lib/gluon/wan-dnsmasq/update.lua index edd33f2c..196a0c2f 100755 --- a/package/gluon-wan-dnsmasq/luasrc/lib/gluon/wan-dnsmasq/update.lua +++ b/package/gluon-wan-dnsmasq/luasrc/lib/gluon/wan-dnsmasq/update.lua @@ -16,6 +16,19 @@ local function append_server(server) end +local function handled_interfaces() + local interfaces = {} + + for _, path in ipairs(util.glob('/lib/gluon/wan-dnsmasq/interface.d/*')) do + for interface in io.lines(path) do + table.insert(interfaces, interface) + end + end + + return interfaces +end + + local function handle_interface(status) local ifname = status.device local servers = status.inactive['dns-server'] @@ -41,8 +54,9 @@ if type(static) == 'table' and #static > 0 then append_server(server) end else - pcall(append_interface_servers, 'wan6') - pcall(append_interface_servers, 'wan') + for _, interface in ipairs(handled_interfaces()) do + pcall(append_interface_servers, interface) + end end diff --git a/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/upgrade.html b/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/upgrade.html index 456b9e5c..31555f16 100644 --- a/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/upgrade.html +++ b/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/upgrade.html @@ -26,7 +26,7 @@ $Id$ <%:Firmware image%>
    - +
    @@ -36,13 +36,13 @@ $Id$
    - +
    -
    +
    diff --git a/package/gluon-web-mesh-vpn-fastd/files/lib/gluon/config-mode/view/mesh-vpn-fastd.html b/package/gluon-web-mesh-vpn-fastd/files/lib/gluon/config-mode/view/mesh-vpn-fastd.html index ed527c8e..5bb5f069 100644 --- a/package/gluon-web-mesh-vpn-fastd/files/lib/gluon/config-mode/view/mesh-vpn-fastd.html +++ b/package/gluon-web-mesh-vpn-fastd/files/lib/gluon/config-mode/view/mesh-vpn-fastd.html @@ -1,6 +1,6 @@
    - /> + /> >
    @@ -18,7 +18,7 @@
    - /> + /> >
    diff --git a/package/gluon-web-model/files/lib/gluon/web/view/model/dynlist.html b/package/gluon-web-model/files/lib/gluon/web/view/model/dynlist.html index 512a194b..b0cd9d0a 100644 --- a/package/gluon-web-model/files/lib/gluon/web/view/model/dynlist.html +++ b/package/gluon-web-model/files/lib/gluon/web/view/model/dynlist.html @@ -10,7 +10,7 @@ <% for i, val in ipairs(self:cfgvalue()) do %> - /> diff --git a/package/gluon-web-model/files/lib/gluon/web/view/model/lvalue.html b/package/gluon-web-model/files/lib/gluon/web/view/model/lvalue.html index 47a2ffd1..e63d33ee 100644 --- a/package/gluon-web-model/files/lib/gluon/web/view/model/lvalue.html +++ b/package/gluon-web-model/files/lib/gluon/web/view/model/lvalue.html @@ -4,23 +4,25 @@ %> <% if self.widget == "select" then %> - +
    + +
    <% elseif self.widget == "radio" then %>
    <% for i, entry in pairs(entries) do %> @@ -28,7 +30,7 @@ attr("data-index", i) .. attr("data-depends", self:deplist(entry.deps)) %>> - style="width: 100%"<% else %> cols="<%=self.size%>"<% end %> data-update="change"<%= attr("name", id) .. attr("id", id) .. attr("rows", self.rows) .. attr("wrap", self.wrap) %>> + diff --git a/package/gluon-web-model/files/lib/gluon/web/view/model/value.html b/package/gluon-web-model/files/lib/gluon/web/view/model/value.html index f14c122e..fc8b8de6 100644 --- a/package/gluon-web-model/files/lib/gluon/web/view/model/value.html +++ b/package/gluon-web-model/files/lib/gluon/web/view/model/value.html @@ -2,7 +2,6 @@ attr("id", id) .. attr("name", id) .. attr("type", self.password and "password" or "text") .. - attr("class", self.password and "gluon-input-password" or "gluon-input-text") .. attr("value", self:cfgvalue()) .. attr("size", self.size) .. attr("placeholder", self.placeholder) .. diff --git a/package/gluon-web-model/files/lib/gluon/web/www/static/gluon-web-model.js b/package/gluon-web-model/files/lib/gluon/web/www/static/gluon-web-model.js index a826967e..07478cbb 100644 --- a/package/gluon-web-model/files/lib/gluon/web/www/static/gluon-web-model.js +++ b/package/gluon-web-model/files/lib/gluon/web/www/static/gluon-web-model.js @@ -1 +1 @@ -!function(){var f={};function a(e){return/^-?\d+$/.test(e)?+e:NaN}function r(e){return/^-?\d*\.?\d+?$/.test(e)?+e:NaN}var u={integer:function(){return!isNaN(a(this))},uinteger:function(){return 0<=a(this)},float:function(){return!isNaN(r(this))},ufloat:function(){return 0<=r(this)},ipaddr:function(){return u.ip4addr.apply(this)||u.ip6addr.apply(this)},ip4addr:function(){var e;return!!(e=this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))&&(0<=e[1]&&e[1]<=255&&0<=e[2]&&e[2]<=255&&0<=e[3]&&e[3]<=255&&0<=e[4]&&e[4]<=255)},ip6addr:function(){return this.indexOf("::")<0?null!=this.match(/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i):!(0<=this.indexOf(":::")||this.match(/::.+::/)||this.match(/^:[^:]/)||this.match(/[^:]:$/))&&(!!this.match(/^(?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}$/i)||(!!this.match(/^(?:[a-f0-9]{1,4}:){7}:$/i)||!!this.match(/^:(?::[a-f0-9]{1,4}){7}$/i)))},wpakey:function(){var e=this;return 64==e.length?null!=e.match(/^[a-f0-9]{64}$/i):8<=e.length&&e.length<=63},range:function(e,t){var n=r(this);return+e<=n&&n<=+t},min:function(e){return r(this)>=+e},max:function(e){return r(this)<=+e},irange:function(e,t){var n=a(this);return+e<=n&&n<=+t},imin:function(e){return a(this)>=+e},imax:function(e){return a(this)<=+e},minlength:function(e){return+e<=(""+this).length},maxlength:function(e){return(""+this).length<=+e}};function o(e){for(var t=0;tn.index);i=i.nextSibling);i?r.insertBefore(n.node,i):r.appendChild(n.node),n.node.dispatchEvent(new Event("gluon-show")),e=!0}r&&r.parentNode&&r.getAttribute("data-optionals")&&(r.parentNode.style.display=r.options.length<=1?"none":"")}e&&v()}function h(e,t,n,a){return e.addEventListener?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,function(){var e=window.event;return!e.target&&e.srcElement&&(e.target=e.srcElement),!!n(e)}),e}function g(l,s){var c=s.prefix;function o(e,t,n){for(var a=[];l.firstChild;){var r=l.firstChild;(i=+r.index)!=n&&("input"==r.nodeName.toLowerCase()?a.push(r.value||""):"select"==r.nodeName.toLowerCase()&&(a[a.length-1]=r.options[r.selectedIndex].value)),l.removeChild(r)}0<=t?(e=t+1,a.splice(t,0,"")):s.optional||0!=a.length||a.push("");for(var i=1;i<=a.length;i++){var o=document.createElement("input");if(o.id=c+"."+i,o.name=c,o.value=a[i-1],o.type="text",o.index=i,o.className="gluon-input-text",s.size&&(o.size=s.size),s.placeholder&&(o.placeholder=s.placeholder),l.appendChild(o),s.type&&m(o,!1,s.type),h(o,"keydown",f),h(o,"keypress",p),i==e)o.focus();else if(-i==e){o.focus();var d=o.value;o.value=" ",o.value=d}if(s.optional||1=+e},max:function(e){return r(this)<=+e},irange:function(e,t){var n=a(this);return+e<=n&&n<=+t},imin:function(e){return a(this)>=+e},imax:function(e){return a(this)<=+e},minlength:function(e){return+e<=(""+this).length},maxlength:function(e){return(""+this).length<=+e}};function o(e){for(var t=0;tn.index);i=i.nextSibling);i?r.insertBefore(n.node,i):r.appendChild(n.node),n.node.dispatchEvent(new Event("gluon-show")),e=!0}r&&r.parentNode&&r.getAttribute("data-optionals")&&(r.parentNode.style.display=r.options.length<=1?"none":"")}e&&h()}function g(e,t,n,a){return e.addEventListener?e.addEventListener(t,n,!!a):e.attachEvent("on"+t,function(){var e=window.event;return!e.target&&e.srcElement&&(e.target=e.srcElement),!!n(e)}),e}function m(l,s){var c=s.prefix;function o(e,t,n){for(var a=[];l.firstChild;){var r=l.firstChild;(i=+r.index)!=n&&("input"==r.nodeName.toLowerCase()?a.push(r.value||""):"select"==r.nodeName.toLowerCase()&&(a[a.length-1]=r.options[r.selectedIndex].value)),l.removeChild(r)}0<=t?(e=t+1,a.splice(t,0,"")):s.optional||0!=a.length||a.push("");for(var i=1;i<=a.length;i++){var o=document.createElement("input");if(o.id=c+"."+i,o.name=c,o.value=a[i-1],o.type="text",o.index=i,s.size&&(o.size=s.size),s.placeholder&&(o.placeholder=s.placeholder),l.appendChild(o),s.type&&y(o,!1,s.type),g(o,"keydown",f),g(o,"keypress",p),i==e)o.focus();else if(-i==e){o.focus();var d=o.value;o.value=" ",o.value=d}if(s.optional||1 }, {once: true}); - initOSM(<%=json(self.openlayers_url)%>, function(createMap) { + initOSM(<%=json(self.options)%>, function(createMap) { elMap.style.display = ''; var pos = <%=json(self:cfgvalue().pos)%>; diff --git a/package/gluon-web-osm/files/lib/gluon/web/www/static/gluon-web-osm.js b/package/gluon-web-osm/files/lib/gluon/web/www/static/gluon-web-osm.js index 3d85f6f1..a017a649 100644 --- a/package/gluon-web-osm/files/lib/gluon/web/www/static/gluon-web-osm.js +++ b/package/gluon-web-osm/files/lib/gluon/web/www/static/gluon-web-osm.js @@ -1 +1 @@ -"use strict";function initOSM(e,o){var t=document.createElement("link");t.rel="stylesheet",t.type="text/css",t.href=e+"/css/ol.css",document.head.appendChild(t);var n=document.createElement("script"),r=!1;n.onload=n.onreadystatechange=function(){if(!(r||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){r=!0;var t=new Image;t.onload=function(){var e=new ol.style.Style({image:new ol.style.Icon({img:t,imgSize:[30,45],anchor:[.5,1]})}),c=new ol.Feature;c.setStyle(e),o(function(e,t,o,n,r){var a=new ol.Map({target:e,layers:[new ol.layer.Tile({source:new ol.source.OSM}),new ol.layer.Vector({source:new ol.source.Vector({features:[c]})})],view:new ol.View({center:ol.proj.fromLonLat(t),zoom:o})}),l=function(e){c.setGeometry(new ol.geom.Point(e))};return a.addEventListener("click",function(e){l(e.coordinate),r(ol.proj.toLonLat(e.coordinate))}),n&&l(ol.proj.fromLonLat(t)),a})},t.src="data:image/svg+xml,"+escape('')}},n.src=e+"/build/ol.js",document.head.appendChild(n)} \ No newline at end of file +"use strict";function initOSM(o,r){var e=document.createElement("link");e.rel="stylesheet",e.type="text/css",e.href=o.openlayers_url+"/css/ol.css",document.head.appendChild(e);var t=document.createElement("script"),l=!1;t.onload=t.onreadystatechange=function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){l=!0;var t=new Image;t.onload=function(){var i,e=new ol.style.Style({image:new ol.style.Icon({img:t,imgSize:[30,45],anchor:[.5,1]})}),s=new ol.Feature;s.setStyle(e),i=o.tile_layer&&"XYZ"===o.tile_layer.type?new ol.source.XYZ({url:o.tile_layer.url,attributions:o.tile_layer.attributions}):new ol.source.OSM,r(function(e,t,o,r,l){function n(e){s.setGeometry(new ol.geom.Point(e))}var a=new ol.Map({target:e,layers:[new ol.layer.Tile({source:i}),new ol.layer.Vector({source:new ol.source.Vector({features:[s]})})],view:new ol.View({center:ol.proj.fromLonLat(t),zoom:o})});return a.addEventListener("click",function(e){n(e.coordinate),l(ol.proj.toLonLat(e.coordinate))}),r&&n(ol.proj.fromLonLat(t)),a})},t.src="data:image/svg+xml,"+escape('')}},t.src=o.openlayers_url+"/build/ol.js",document.head.appendChild(t)} \ No newline at end of file diff --git a/package/gluon-web-osm/javascript/gluon-web-osm.js b/package/gluon-web-osm/javascript/gluon-web-osm.js index 65b06e26..b4acbf8b 100644 --- a/package/gluon-web-osm/javascript/gluon-web-osm.js +++ b/package/gluon-web-osm/javascript/gluon-web-osm.js @@ -6,7 +6,7 @@ 'use strict'; -function initOSM(openlayers_url, ready) { +function initOSM(options, ready) { var markerSvg = '' + '' + '' @@ -15,7 +15,7 @@ function initOSM(openlayers_url, ready) { var style = document.createElement('link'); style.rel = 'stylesheet'; style.type = 'text/css'; - style.href = openlayers_url + '/css/ol.css'; + style.href = options.openlayers_url + '/css/ol.css'; document.head.appendChild(style); var script = document.createElement('script'); @@ -42,12 +42,22 @@ function initOSM(openlayers_url, ready) { var marker = new ol.Feature(); marker.setStyle(markerStyle); + var source; + if (options.tile_layer && options.tile_layer.type === 'XYZ') { + source = new ol.source.XYZ({ + url: options.tile_layer.url, + attributions: options.tile_layer.attributions, + }); + } else { + source = new ol.source.OSM(); + } + ready(function(elMap, pos, zoom, set, onUpdate) { var map = new ol.Map({ target: elMap, layers: [ new ol.layer.Tile({ - source: new ol.source.OSM() + source: source }), new ol.layer.Vector({ source: new ol.source.Vector({ @@ -79,6 +89,6 @@ function initOSM(openlayers_url, ready) { markerImg.src = 'data:image/svg+xml,' + escape(markerSvg); }; - script.src = openlayers_url + '/build/ol.js'; + script.src = options.openlayers_url + '/build/ol.js'; document.head.appendChild(script); } diff --git a/package/gluon-web-osm/luasrc/usr/lib/lua/gluon/web/model/osm.lua b/package/gluon-web-osm/luasrc/usr/lib/lua/gluon/web/model/osm.lua index cf07c228..ec18a003 100644 --- a/package/gluon-web-osm/luasrc/usr/lib/lua/gluon/web/model/osm.lua +++ b/package/gluon-web-osm/luasrc/usr/lib/lua/gluon/web/model/osm.lua @@ -15,7 +15,10 @@ M.MapValue = MapValue function MapValue:__init__(title, options) classes.AbstractValue.__init__(self, title) self.subtemplate = "model/osm/map" - self.openlayers_url = options.openlayers_url or DEFAULT_URL + self.options = { + openlayers_url = options.openlayers_url or DEFAULT_URL, + tile_layer = options.tile_layer, + } self.lon = options.lon self.lat = options.lat diff --git a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua index efc6d0f0..49580413 100644 --- a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua +++ b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua @@ -142,7 +142,7 @@ uci:foreach('wireless', 'wifi-device', function(config) end) -if has_5ghz_radio() then +if has_5ghz_radio() and not wireless.preserve_channels(uci) then local r = f:section(Section, translate("Outdoor Installation"), translate( "Configuring the node for outdoor use tunes the 5 GHz radio to a frequency " .. "and transmission power that conforms with the local regulatory requirements. " diff --git a/package/gluon-wireless-encryption/Makefile b/package/gluon-wireless-encryption/Makefile index 35b61576..a9e47210 100644 --- a/package/gluon-wireless-encryption/Makefile +++ b/package/gluon-wireless-encryption/Makefile @@ -6,7 +6,7 @@ PKG_RELEASE:=1 include ../gluon.mk define Package/gluon-wireless-encryption-wpa3 - DEPENDS:=+hostapd-openssl + DEPENDS:=+hostapd-wolfssl TITLE:=Package for supporting WPA3 encrypted wireless networks endef diff --git a/package/libgluonutil/src/libgluonutil.c b/package/libgluonutil/src/libgluonutil.c index e7a88fd1..0950f0d8 100644 --- a/package/libgluonutil/src/libgluonutil.c +++ b/package/libgluonutil/src/libgluonutil.c @@ -28,13 +28,16 @@ #include #include + #include + +#include +#include +#include #include #include #include #include -#include -#include /** * Merges two JSON objects @@ -129,6 +132,74 @@ char * gluonutil_get_interface_address(const char *ifname) { return gluonutil_read_line(path); } +void gluonutil_get_interface_lower(char out[IF_NAMESIZE], const char *ifname) { + strncpy(out, ifname, IF_NAMESIZE-1); + out[IF_NAMESIZE-1] = 0; + + const char *format = "/sys/class/net/%s/lower_*"; + char pattern[strlen(format) + IF_NAMESIZE]; + + while (true) { + snprintf(pattern, sizeof(pattern), format, out); + size_t pattern_len = strlen(pattern); + + glob_t lower; + if (glob(pattern, GLOB_NOSORT, NULL, &lower) != 0) + break; + + strncpy(out, lower.gl_pathv[0] + pattern_len - 1, IF_NAMESIZE-1); + + globfree(&lower); + } +} + +enum gluonutil_interface_type lookup_interface_type(const char *devtype) { + if (strcmp(devtype, "wlan") == 0) + return GLUONUTIL_INTERFACE_TYPE_WIRELESS; + + if (strcmp(devtype, "l2tpeth") == 0 || strcmp(devtype, "wireguard") == 0) + return GLUONUTIL_INTERFACE_TYPE_TUNNEL; + + /* Regular wired interfaces do not set DEVTYPE, so if this point is + * reached, we have something different */ + return GLUONUTIL_INTERFACE_TYPE_UNKNOWN; +} + +enum gluonutil_interface_type gluonutil_get_interface_type(const char *ifname) { + const char *pattern = "/sys/class/net/%s/%s"; + + /* Default to wired type when no DEVTYPE is set */ + enum gluonutil_interface_type ret = GLUONUTIL_INTERFACE_TYPE_WIRED; + char *line = NULL, path[PATH_MAX]; + size_t buflen = 0; + ssize_t len; + FILE *f; + + snprintf(path, sizeof(path), pattern, ifname, "tun_flags"); + if (access(path, F_OK) == 0) + return GLUONUTIL_INTERFACE_TYPE_TUNNEL; + + snprintf(path, sizeof(path), pattern, ifname, "uevent"); + f = fopen(path, "r"); + if (!f) + return GLUONUTIL_INTERFACE_TYPE_UNKNOWN; + + while ((len = getline(&line, &buflen, f)) >= 0) { + if (len == 0) + continue; + + if (line[len-1] == '\n') + line[len-1] = '\0'; + + if (strncmp(line, "DEVTYPE=", 8) == 0) { + ret = lookup_interface_type(line+8); + break; + } + } + + fclose(f); + return ret; +} struct json_object * gluonutil_wrap_string(const char *str) { diff --git a/package/libgluonutil/src/libgluonutil.h b/package/libgluonutil/src/libgluonutil.h index 39b253ba..b33b93e7 100644 --- a/package/libgluonutil/src/libgluonutil.h +++ b/package/libgluonutil/src/libgluonutil.h @@ -27,6 +27,7 @@ #ifndef _LIBGLUON_LIBGLUON_H_ #define _LIBGLUON_LIBGLUON_H_ +#include #include #include @@ -34,7 +35,18 @@ char * gluonutil_read_line(const char *filename); char * gluonutil_get_sysconfig(const char *key); char * gluonutil_get_node_id(void); + +enum gluonutil_interface_type { + GLUONUTIL_INTERFACE_TYPE_UNKNOWN, + GLUONUTIL_INTERFACE_TYPE_WIRED, + GLUONUTIL_INTERFACE_TYPE_WIRELESS, + GLUONUTIL_INTERFACE_TYPE_TUNNEL, +}; + +void gluonutil_get_interface_lower(char out[IF_NAMESIZE], const char *ifname); char * gluonutil_get_interface_address(const char *ifname); +enum gluonutil_interface_type gluonutil_get_interface_type(const char *ifname); + bool gluonutil_get_node_prefix6(struct in6_addr *prefix); struct json_object * gluonutil_wrap_string(const char *str); diff --git a/patches/openwrt/0009-ath79-enable-GL-AR750S-NOR-variant-from-master.patch b/patches/openwrt/0009-ath79-enable-GL-AR750S-NOR-variant-from-master.patch index 594266d3..1b9848d7 100644 --- a/patches/openwrt/0009-ath79-enable-GL-AR750S-NOR-variant-from-master.patch +++ b/patches/openwrt/0009-ath79-enable-GL-AR750S-NOR-variant-from-master.patch @@ -3,10 +3,10 @@ Date: Tue, 31 Mar 2020 21:50:28 +0200 Subject: ath79: enable GL-AR750S NOR variant from master diff --git a/target/linux/ath79/base-files/etc/board.d/02_network b/target/linux/ath79/base-files/etc/board.d/02_network -index 9b9bc8a7fc15300247bceeb431ab3ae8d3ac47e6..da45b40086c0cb0011c1db6b89ae75b981fcc9b7 100755 +index 5dda551caae0429880ee9d5965bfb6797d218e6d..b8fac8816c9a2b2a87a5d1335b41127666afe2e4 100755 --- a/target/linux/ath79/base-files/etc/board.d/02_network +++ b/target/linux/ath79/base-files/etc/board.d/02_network -@@ -151,7 +151,7 @@ ath79_setup_interfaces() +@@ -155,7 +155,7 @@ ath79_setup_interfaces() etactica,eg200) ucidef_set_interface_lan "eth0" "dhcp" ;; @@ -29,7 +29,7 @@ index d93e6dcd71ab19c53905daa41e95cc4fc614f114..c917f38211d0b246f064dba4b7feefec ath10kcal_patch_mac $(macaddr_add $(mtd_get_mac_binary art 0) +1) ;; diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts -index 0145a24fbae2cdbe6fb6445607795af6b792352d..ebecb8cc776091ec019638589cb88159345d367f 100644 +index 03922bcd1fe9a453d5916537609317b94eea18c6..ff64e16d1ce7a94d16529e5954e1d50513a5e2cb 100644 --- a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts +++ b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts @@ -7,8 +7,8 @@ @@ -41,8 +41,8 @@ index 0145a24fbae2cdbe6fb6445607795af6b792352d..ebecb8cc776091ec019638589cb88159 + compatible = "glinet,gl-ar750s-nor", "qca,qca9563"; + model = "GL.iNet GL-AR750S (NOR)"; - chosen { - bootargs = "console=ttyS0,115200n8"; + aliases { + led-boot = &power; diff --git a/target/linux/ath79/image/generic.mk b/target/linux/ath79/image/generic.mk index 55053be34f11f0df982c85f94c9180fdba9ff221..892ef10f870e347c8a1509cecd35bce4b5e98bee 100644 --- a/target/linux/ath79/image/generic.mk diff --git a/patches/openwrt/0012-mac80211-rt2800-enable-MFP-support-unconditionally.patch b/patches/openwrt/0012-mac80211-rt2800-enable-MFP-support-unconditionally.patch new file mode 100644 index 00000000..0a8ca213 --- /dev/null +++ b/patches/openwrt/0012-mac80211-rt2800-enable-MFP-support-unconditionally.patch @@ -0,0 +1,62 @@ +From: Rui Salvaterra +Date: Mon, 25 May 2020 14:49:07 +0100 +Subject: mac80211: rt2800: enable MFP support unconditionally + +This gives us WPA3 support out of the box without having to manually disable +hardware crypto. The driver will fall back to software crypto if the connection +requires management frame protection. + +Signed-off-by: Daniel Golle +[apply to openwrt-1907] +Signed-off-by: David Bauer + +diff --git a/package/kernel/mac80211/patches/rt2x00/080-rt2800-enable-MFP-support-unconditionally.patch b/package/kernel/mac80211/patches/rt2x00/080-rt2800-enable-MFP-support-unconditionally.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..1d55b2756c365a13b2315e874809e2397fb35855 +--- /dev/null ++++ b/package/kernel/mac80211/patches/rt2x00/080-rt2800-enable-MFP-support-unconditionally.patch +@@ -0,0 +1,44 @@ ++From b6b15e20421fefae9f78274f9fef80bc97bf5d5c Mon Sep 17 00:00:00 2001 ++From: Rui Salvaterra ++Date: Mon, 25 May 2020 14:49:07 +0100 ++Subject: [PATCH] rt2800: enable MFP support unconditionally ++ ++This gives us WPA3 support out of the box without having to manually disable ++hardware crypto. The driver will fall back to software crypto if the connection ++requires management frame protection. ++ ++Suggested-by: Stanislaw Gruszka ++Signed-off-by: Rui Salvaterra ++Acked-by: Stanislaw Gruszka ++Signed-off-by: Kalle Valo ++Link: https://lore.kernel.org/r/20200525134906.1672-1-rsalvaterra@gmail.com ++--- ++ drivers/net/wireless/ralink/rt2x00/rt2800lib.c | 4 +--- ++ drivers/net/wireless/ralink/rt2x00/rt2x00mac.c | 3 ++- ++ 2 files changed, 3 insertions(+), 4 deletions(-) ++ ++--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c +++++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c ++@@ -9985,9 +9985,7 @@ static int rt2800_probe_hw_mode(struct r ++ if (!rt2x00_is_usb(rt2x00dev)) ++ ieee80211_hw_set(rt2x00dev->hw, HOST_BROADCAST_PS_BUFFERING); ++ ++- /* Set MFP if HW crypto is disabled. */ ++- if (rt2800_hwcrypt_disabled(rt2x00dev)) ++- ieee80211_hw_set(rt2x00dev->hw, MFP_CAPABLE); +++ ieee80211_hw_set(rt2x00dev->hw, MFP_CAPABLE); ++ ++ SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); ++ SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, ++--- a/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c +++++ b/drivers/net/wireless/ralink/rt2x00/rt2x00mac.c ++@@ -459,7 +459,8 @@ int rt2x00mac_set_key(struct ieee80211_h ++ if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) ++ return 0; ++ ++- if (!rt2x00_has_cap_hw_crypto(rt2x00dev)) +++ /* The hardware can't do MFP */ +++ if (!rt2x00_has_cap_hw_crypto(rt2x00dev) || (sta && sta->mfp)) ++ return -EOPNOTSUPP; ++ ++ /* diff --git a/patches/openwrt/0013-mt76-mt76x0-disable-GTK-offloading.patch b/patches/openwrt/0013-mt76-mt76x0-disable-GTK-offloading.patch new file mode 100644 index 00000000..c1f404c8 --- /dev/null +++ b/patches/openwrt/0013-mt76-mt76x0-disable-GTK-offloading.patch @@ -0,0 +1,49 @@ +From: David Bauer +Date: Sat, 13 Jun 2020 19:19:17 +0200 +Subject: mt76: mt76x0: disable GTK offloading + +When the GTK is offloaded, MT7610 won't transmit any multicast frames. +This is most likely due to a bug in the offloading datapath. MT7612 is +not affected. + +Disable GTK offloading for now. It can be re-enabled once the bug in the +offloading path is fixed. + +Signed-off-by: David Bauer + +diff --git a/package/kernel/mt76/patches/001-mt76-mt76x0-disable-gtk-offloading.patch b/package/kernel/mt76/patches/001-mt76-mt76x0-disable-gtk-offloading.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..e7e19ac957dbfaa9510016d3387abe9eed353538 +--- /dev/null ++++ b/package/kernel/mt76/patches/001-mt76-mt76x0-disable-gtk-offloading.patch +@@ -0,0 +1,30 @@ ++From ae01717951013fbc8bb0315d902d5b9f5873631a Mon Sep 17 00:00:00 2001 ++From: David Bauer ++Date: Fri, 12 Jun 2020 01:09:57 +0200 ++Subject: [PATCH] mt76: mt76x0: disable GTK offloading ++ ++When the GTK is offloaded, MT7610 won't transmit any multicast frames. ++This is most likely due to a bug in the offloading datapath. MT7612 is ++not affected. ++ ++Disable GTK offloading for now. It can be re-enabled once the bug in the ++offloading path is fixed. ++ ++Signed-off-by: David Bauer ++--- ++ drivers/net/wireless/mediatek/mt76/mt76x02_util.c | 4 ++++ ++ 1 file changed, 4 insertions(+) ++ ++--- a/mt76x02_util.c +++++ b/mt76x02_util.c ++@@ -432,6 +432,10 @@ int mt76x02_set_key(struct ieee80211_hw ++ !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) ++ return -EOPNOTSUPP; ++ +++ /* MT76x0 GTK offloading is currently broken */ +++ if (is_mt76x0(dev) && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) +++ return -EOPNOTSUPP; +++ ++ /* ++ * In USB AP mode, broadcast/multicast frames are setup in beacon ++ * data registers and sent via HW beacons engine, they require to diff --git a/patches/openwrt/0014-mac80211-create-channel-list-for-fixed-channel-operation.patch b/patches/openwrt/0014-mac80211-create-channel-list-for-fixed-channel-operation.patch new file mode 100644 index 00000000..c2178b1e --- /dev/null +++ b/patches/openwrt/0014-mac80211-create-channel-list-for-fixed-channel-operation.patch @@ -0,0 +1,28 @@ +From: David Bauer +Date: Sat, 4 Jul 2020 13:20:02 +0200 +Subject: mac80211: create channel list for fixed channel operation + +Currently a device which has a DFS channel selected using the UCI +channel setting might switch to a non-DFS channel in case no chanlist is +provided (UCI setting "channels") when the radio detects a DFS event. + +Automatically add a chanlist consisting of the configured channel when +the device does not operate in auto-channel mode and no chanlist set to +circumvent this issue. + +Signed-off-by: David Bauer + +diff --git a/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh b/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh +index 36aebbb2ccfec2137d5d260fe2111d77f531ddec..367a3e8e37a8e8435c35ca2912ef0855efbdfc78 100644 +--- a/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh ++++ b/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh +@@ -100,6 +100,9 @@ mac80211_hostapd_setup_base() { + json_get_vars noscan ht_coex + json_get_values ht_capab_list ht_capab tx_burst + ++ [ "$auto_channel" = 0 ] && [ -z "$channel_list" ] && \ ++ channel_list="$channel" ++ + set_default noscan 0 + + [ "$noscan" -gt 0 ] && hostapd_noscan=1 diff --git a/patches/openwrt/0015-hostapd-enter-DFS-state-if-no-available-channel-is-found.patch b/patches/openwrt/0015-hostapd-enter-DFS-state-if-no-available-channel-is-found.patch new file mode 100644 index 00000000..bed5a376 --- /dev/null +++ b/patches/openwrt/0015-hostapd-enter-DFS-state-if-no-available-channel-is-found.patch @@ -0,0 +1,47 @@ +From: David Bauer +Date: Sat, 4 Jul 2020 13:20:07 +0200 +Subject: hostapd: enter DFS state if no available channel is found + +Previously hostapd would not stop transmitting when a DFS event was +detected and no available channel to switch to was available. + +Disable and re-enable the interface to enter DFS state. This way, TX +does not happen until the kernel notifies hostapd about the NOP +expiring. + +Signed-off-by: David Bauer + +diff --git a/package/network/services/hostapd/patches/800-dfs-enter-DFS-state-if-no-available-channel-is-found.patch b/package/network/services/hostapd/patches/800-dfs-enter-DFS-state-if-no-available-channel-is-found.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..59e903f06ae66208517c2d620b4cd128f41f25c1 +--- /dev/null ++++ b/package/network/services/hostapd/patches/800-dfs-enter-DFS-state-if-no-available-channel-is-found.patch +@@ -0,0 +1,28 @@ ++From cefc52e6b93731c713f1bba1cb5e7e92105b758b Mon Sep 17 00:00:00 2001 ++From: David Bauer ++Date: Fri, 3 Jul 2020 23:00:34 +0200 ++Subject: [PATCH] dfs: enter DFS state if no available channel is found ++ ++Previously hostapd would not stop transmitting when a DFS event was ++detected and no available channel to switch to was available. ++ ++Disable and re-enable the interface to enter DFS state. This way, TX ++does not happen until the kernel notifies hostapd about the NOP ++expiring. ++ ++Signed-off-by: David Bauer ++--- ++ src/ap/dfs.c | 11 +++++++++-- ++ 1 file changed, 9 insertions(+), 2 deletions(-) ++ ++--- a/src/ap/dfs.c +++++ b/src/ap/dfs.c ++@@ -930,6 +930,8 @@ static int hostapd_dfs_start_channel_swi ++ wpa_printf(MSG_INFO, ++ "%s: no DFS channels left, waiting for NOP to finish", ++ __func__); +++ hostapd_disable_iface(iface); +++ hostapd_enable_iface(iface); ++ return err; ++ } ++ diff --git a/patches/openwrt/0016-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch b/patches/openwrt/0016-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch new file mode 100644 index 00000000..ad9cc4f1 --- /dev/null +++ b/patches/openwrt/0016-kernel-bridge-Implement-MLD-Querier-wake-up-calls-Android-bug-workaround.patch @@ -0,0 +1,2605 @@ +From: Linus Lüssing +Date: Tue, 30 Jun 2020 18:01:56 +0200 +Subject: kernel: bridge: Implement MLD Querier wake-up calls / Android bug workaround + +Implement a configurable MLD Querier wake-up calls "feature" which +works around a widely spread Android bug in connection with IGMP/MLD +snooping. + +Currently there are mobile devices (e.g. Android) which are not able +to receive and respond to MLD Queries reliably because the Wifi driver +filters a lot of ICMPv6 when the device is asleep - including +MLD. This in turn breaks IPv6 communication when MLD Snooping is +enabled. However there is one ICMPv6 type which is allowed to pass and +which can be used to wake up the mobile device: ICMPv6 Echo Requests. + +If this bridge is the selected MLD Querier then setting +"multicast_wakeupcall" to a number n greater than 0 will send n +ICMPv6 Echo Requests to each host behind this port to wake +them up with each MLD Query. Upon receiving a matching ICMPv6 Echo +Reply an MLD Query with a unicast ethernet destination will be sent +to the specific host(s). + +Link: https://issuetracker.google.com/issues/149630944 +Link: https://github.com/freifunk-gluon/gluon/issues/1832 + +Signed-off-by: Linus Lüssing + +diff --git a/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch b/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..7ae2960d51d78b03be368bbfd17aff9219da524d +--- /dev/null ++++ b/package/network/config/netifd/patches/0001-bridge-Add-multicast_wakeupcall-option.patch +@@ -0,0 +1,169 @@ ++From 026c823dc34c34393498f10a489ea8773866e9e4 Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Sun, 5 Jul 2020 23:33:51 +0200 ++Subject: [PATCH] bridge: Add multicast_wakeupcall option ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++This makes the new per bridge port multicast_wakeupcall feature ++for the Linux bridge configurable for wireless interfaces and enables it ++by default for an AP interface. ++ ++The MLD Querier wake-up calls "feature" works around a widely spread Android ++bug in connection with IGMP/MLD snooping. ++ ++Currently there are mobile devices (e.g. Android) which are not able ++to receive and respond to MLD Queries reliably because the Wifi driver ++filters a lot of ICMPv6 when the device is asleep - including ++MLD. This in turn breaks IPv6 communication when MLD Snooping is ++enabled. However there is one ICMPv6 type which is allowed to pass and ++which can be used to wake up the mobile device: ICMPv6 Echo Requests. ++ ++If this bridge is the selected MLD Querier then setting ++"multicast_wakeupcall" to a number n greater than 0 will send n ++ICMPv6 Echo Requests to each host behind this port to wake ++them up with each MLD Query. Upon receiving a matching ICMPv6 Echo ++Reply an MLD Query with a unicast ethernet destination will be sent ++to the specific host(s). ++ ++Link: https://issuetracker.google.com/issues/149630944 ++Link: https://github.com/freifunk-gluon/gluon/issues/1832 ++ ++Signed-off-by: Linus Lüssing ++--- ++ device.c | 9 +++++++++ ++ device.h | 21 ++++++++++++--------- ++ system-linux.c | 13 +++++++++++++ ++ 3 files changed, 34 insertions(+), 9 deletions(-) ++ ++diff --git a/device.c b/device.c ++index 128151a..2e8c24c 100644 ++--- a/device.c +++++ b/device.c ++@@ -50,6 +50,7 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = { ++ [DEV_ATTR_NEIGHGCSTALETIME] = { .name = "neighgcstaletime", .type = BLOBMSG_TYPE_INT32 }, ++ [DEV_ATTR_DADTRANSMITS] = { .name = "dadtransmits", .type = BLOBMSG_TYPE_INT32 }, ++ [DEV_ATTR_MULTICAST_TO_UNICAST] = { .name = "multicast_to_unicast", .type = BLOBMSG_TYPE_BOOL }, +++ [DEV_ATTR_MULTICAST_WAKEUPCALL] = { .name = "multicast_wakeupcall", .type = BLOBMSG_TYPE_INT32 }, ++ [DEV_ATTR_MULTICAST_ROUTER] = { .name = "multicast_router", .type = BLOBMSG_TYPE_INT32 }, ++ [DEV_ATTR_MULTICAST_FAST_LEAVE] = { .name = "multicast_fast_leave", . type = BLOBMSG_TYPE_BOOL }, ++ [DEV_ATTR_MULTICAST] = { .name ="multicast", .type = BLOBMSG_TYPE_BOOL }, ++@@ -223,6 +224,7 @@ device_merge_settings(struct device *dev, struct device_settings *n) ++ n->multicast = s->flags & DEV_OPT_MULTICAST ? ++ s->multicast : os->multicast; ++ n->multicast_to_unicast = s->multicast_to_unicast; +++ n->multicast_wakeupcall = s->multicast_wakeupcall; ++ n->multicast_router = s->multicast_router; ++ n->multicast_fast_leave = s->multicast_fast_leave; ++ n->learning = s->learning; ++@@ -330,6 +332,11 @@ device_init_settings(struct device *dev, struct blob_attr **tb) ++ s->flags |= DEV_OPT_MULTICAST_TO_UNICAST; ++ } ++ +++ if ((cur = tb[DEV_ATTR_MULTICAST_WAKEUPCALL])) { +++ s->multicast_wakeupcall = blobmsg_get_u32(cur); +++ s->flags |= DEV_OPT_MULTICAST_WAKEUPCALL; +++ } +++ ++ if ((cur = tb[DEV_ATTR_MULTICAST_ROUTER])) { ++ s->multicast_router = blobmsg_get_u32(cur); ++ if (s->multicast_router <= 2) ++@@ -1028,6 +1035,8 @@ device_dump_status(struct blob_buf *b, struct device *dev) ++ blobmsg_add_u32(b, "dadtransmits", st.dadtransmits); ++ if (st.flags & DEV_OPT_MULTICAST_TO_UNICAST) ++ blobmsg_add_u8(b, "multicast_to_unicast", st.multicast_to_unicast); +++ if (st.flags & DEV_OPT_MULTICAST_WAKEUPCALL) +++ blobmsg_add_u32(b, "multicast_wakeupcall", st.multicast_wakeupcall); ++ if (st.flags & DEV_OPT_MULTICAST_ROUTER) ++ blobmsg_add_u32(b, "multicast_router", st.multicast_router); ++ if (st.flags & DEV_OPT_MULTICAST_FAST_LEAVE) ++diff --git a/device.h b/device.h ++index 5f3fae2..4935db0 100644 ++--- a/device.h +++++ b/device.h ++@@ -42,6 +42,7 @@ enum { ++ DEV_ATTR_NEIGHREACHABLETIME, ++ DEV_ATTR_DADTRANSMITS, ++ DEV_ATTR_MULTICAST_TO_UNICAST, +++ DEV_ATTR_MULTICAST_WAKEUPCALL, ++ DEV_ATTR_MULTICAST_ROUTER, ++ DEV_ATTR_MULTICAST_FAST_LEAVE, ++ DEV_ATTR_MULTICAST, ++@@ -95,15 +96,16 @@ enum { ++ DEV_OPT_MTU6 = (1 << 12), ++ DEV_OPT_DADTRANSMITS = (1 << 13), ++ DEV_OPT_MULTICAST_TO_UNICAST = (1 << 14), ++- DEV_OPT_MULTICAST_ROUTER = (1 << 15), ++- DEV_OPT_MULTICAST = (1 << 16), ++- DEV_OPT_LEARNING = (1 << 17), ++- DEV_OPT_UNICAST_FLOOD = (1 << 18), ++- DEV_OPT_NEIGHGCSTALETIME = (1 << 19), ++- DEV_OPT_MULTICAST_FAST_LEAVE = (1 << 20), ++- DEV_OPT_SENDREDIRECTS = (1 << 21), ++- DEV_OPT_NEIGHLOCKTIME = (1 << 22), ++- DEV_OPT_ISOLATE = (1 << 23), +++ DEV_OPT_MULTICAST_WAKEUPCALL = (1 << 15), +++ DEV_OPT_MULTICAST_ROUTER = (1 << 16), +++ DEV_OPT_MULTICAST = (1 << 17), +++ DEV_OPT_LEARNING = (1 << 18), +++ DEV_OPT_UNICAST_FLOOD = (1 << 19), +++ DEV_OPT_NEIGHGCSTALETIME = (1 << 20), +++ DEV_OPT_MULTICAST_FAST_LEAVE = (1 << 21), +++ DEV_OPT_SENDREDIRECTS = (1 << 22), +++ DEV_OPT_NEIGHLOCKTIME = (1 << 23), +++ DEV_OPT_ISOLATE = (1 << 24), ++ }; ++ ++ /* events broadcasted to all users of a device */ ++@@ -164,6 +166,7 @@ struct device_settings { ++ int neigh4locktime; ++ unsigned int dadtransmits; ++ bool multicast_to_unicast; +++ unsigned int multicast_wakeupcall; ++ unsigned int multicast_router; ++ bool multicast_fast_leave; ++ bool multicast; ++diff --git a/system-linux.c b/system-linux.c ++index acfd40e..f1abccf 100644 ++--- a/system-linux.c +++++ b/system-linux.c ++@@ -355,6 +355,11 @@ static void system_bridge_set_multicast_to_unicast(struct device *dev, const cha ++ system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_to_unicast", dev->ifname, val); ++ } ++ +++static void system_bridge_set_multicast_wakeupcall(struct device *dev, const char *val) +++{ +++ system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_wakeupcall", dev->ifname, val); +++} +++ ++ static void system_bridge_set_multicast_fast_leave(struct device *dev, const char *val) ++ { ++ system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_fast_leave", dev->ifname, val); ++@@ -784,8 +789,10 @@ static char *system_get_bridge(const char *name, char *buf, int buflen) ++ static void ++ system_bridge_set_wireless(struct device *bridge, struct device *dev) ++ { +++ unsigned int mcast_wakeupcall = dev->wireless_ap ? 2 : 0; ++ bool mcast_to_ucast = dev->wireless_ap; ++ bool hairpin = true; +++ char buf[64]; ++ ++ if (bridge->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST && ++ !bridge->settings.multicast_to_unicast) ++@@ -796,6 +803,12 @@ system_bridge_set_wireless(struct device *bridge, struct device *dev) ++ ++ system_bridge_set_multicast_to_unicast(dev, mcast_to_ucast ? "1" : "0"); ++ system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0"); +++ +++ if (bridge->settings.flags & DEV_OPT_MULTICAST_WAKEUPCALL) +++ mcast_wakeupcall = dev->settings.multicast_wakeupcall; +++ +++ snprintf(buf, sizeof(buf), "%u", mcast_wakeupcall); +++ system_bridge_set_multicast_wakeupcall(dev, buf); ++ } ++ ++ int system_bridge_addif(struct device *bridge, struct device *dev) ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch b/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..e8f60cc4cc267d3bb1c38770b7e1447765514f2b +--- /dev/null ++++ b/target/linux/generic/backport-4.14/111-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch +@@ -0,0 +1,506 @@ ++From 2a0f1172f505121c75313f6cce2874476b82ce45 Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Mon, 21 Jan 2019 07:26:25 +0100 ++Subject: [PATCH] bridge: simplify ip_mc_check_igmp() and ++ ipv6_mc_check_mld() calls ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++This patch refactors ip_mc_check_igmp(), ipv6_mc_check_mld() and ++their callers (more precisely, the Linux bridge) to not rely on ++the skb_trimmed parameter anymore. ++ ++An skb with its tail trimmed to the IP packet length was initially ++introduced for the following three reasons: ++ ++1) To be able to verify the ICMPv6 checksum. ++2) To be able to distinguish the version of an IGMP or MLD query. ++ They are distinguishable only by their size. ++3) To avoid parsing data for an IGMPv3 or MLDv2 report that is ++ beyond the IP packet but still within the skb. ++ ++The first case still uses a cloned and potentially trimmed skb to ++verfiy. However, there is no need to propagate it to the caller. ++For the second and third case explicit IP packet length checks were ++added. ++ ++This hopefully makes ip_mc_check_igmp() and ipv6_mc_check_mld() easier ++to read and verfiy, as well as easier to use. ++ ++Signed-off-by: Linus Lüssing ++Signed-off-by: David S. Miller ++--- ++ include/linux/igmp.h | 11 +++++++- ++ include/linux/ip.h | 5 ++++ ++ include/linux/ipv6.h | 6 ++++ ++ include/net/addrconf.h | 12 +++++++- ++ net/batman-adv/multicast.c | 4 +-- ++ net/bridge/br_multicast.c | 56 ++++++++++++++++++-------------------- ++ net/ipv4/igmp.c | 23 +++------------- ++ net/ipv6/mcast_snoop.c | 24 +++------------- ++ 8 files changed, 69 insertions(+), 72 deletions(-) ++ ++diff --git a/include/linux/igmp.h b/include/linux/igmp.h ++index f8231854b5d6..858143489a2a 100644 ++--- a/include/linux/igmp.h +++++ b/include/linux/igmp.h ++@@ -18,6 +18,7 @@ ++ #include ++ #include ++ #include +++#include ++ #include ++ #include ++ ++@@ -106,6 +107,14 @@ struct ip_mc_list { ++ #define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value) ++ #define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value) ++ +++static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len) +++{ +++ if (skb_transport_offset(skb) + ip_transport_len(skb) < len) +++ return -EINVAL; +++ +++ return pskb_may_pull(skb, len); +++} +++ ++ extern int ip_check_mc_rcu(struct in_device *dev, __be32 mc_addr, __be32 src_addr, u8 proto); ++ extern int igmp_rcv(struct sk_buff *); ++ extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); ++@@ -128,6 +137,6 @@ extern void ip_mc_unmap(struct in_device *); ++ extern void ip_mc_remap(struct in_device *); ++ extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr); ++ extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr); ++-int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed); +++int ip_mc_check_igmp(struct sk_buff *skb); ++ ++ #endif ++diff --git a/include/linux/ip.h b/include/linux/ip.h ++index 492bc6513533..482b7b7c9f30 100644 ++--- a/include/linux/ip.h +++++ b/include/linux/ip.h ++@@ -34,4 +34,9 @@ static inline struct iphdr *ipip_hdr(const struct sk_buff *skb) ++ { ++ return (struct iphdr *)skb_transport_header(skb); ++ } +++ +++static inline unsigned int ip_transport_len(const struct sk_buff *skb) +++{ +++ return ntohs(ip_hdr(skb)->tot_len) - skb_network_header_len(skb); +++} ++ #endif /* _LINUX_IP_H */ ++diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h ++index 067a6fa675ed..f2128d94a680 100644 ++--- a/include/linux/ipv6.h +++++ b/include/linux/ipv6.h ++@@ -103,6 +103,12 @@ static inline struct ipv6hdr *ipipv6_hdr(const struct sk_buff *skb) ++ return (struct ipv6hdr *)skb_transport_header(skb); ++ } ++ +++static inline unsigned int ipv6_transport_len(const struct sk_buff *skb) +++{ +++ return ntohs(ipv6_hdr(skb)->payload_len) + sizeof(struct ipv6hdr) - +++ skb_network_header_len(skb); +++} +++ ++ /* ++ This structure contains results of exthdrs parsing ++ as offsets from skb->nh. ++diff --git a/include/net/addrconf.h b/include/net/addrconf.h ++index f30ee99a1d72..0eb1e1f6ea9a 100644 ++--- a/include/net/addrconf.h +++++ b/include/net/addrconf.h ++@@ -49,6 +49,7 @@ struct prefix_info { ++ struct in6_addr prefix; ++ }; ++ +++#include ++ #include ++ #include ++ #include ++@@ -189,6 +190,15 @@ u32 ipv6_addr_label(struct net *net, const struct in6_addr *addr, ++ /* ++ * multicast prototypes (mcast.c) ++ */ +++static inline int ipv6_mc_may_pull(struct sk_buff *skb, +++ unsigned int len) +++{ +++ if (skb_transport_offset(skb) + ipv6_transport_len(skb) < len) +++ return -EINVAL; +++ +++ return pskb_may_pull(skb, len); +++} +++ ++ int ipv6_sock_mc_join(struct sock *sk, int ifindex, ++ const struct in6_addr *addr); ++ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, ++@@ -207,7 +217,7 @@ void ipv6_mc_unmap(struct inet6_dev *idev); ++ void ipv6_mc_remap(struct inet6_dev *idev); ++ void ipv6_mc_init_dev(struct inet6_dev *idev); ++ void ipv6_mc_destroy_dev(struct inet6_dev *idev); ++-int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed); +++int ipv6_mc_check_mld(struct sk_buff *skb); ++ void addrconf_dad_failure(struct inet6_ifaddr *ifp); ++ ++ bool ipv6_chk_mcast_addr(struct net_device *dev, const struct in6_addr *group, ++diff --git a/net/batman-adv/multicast.c b/net/batman-adv/multicast.c ++index d47865e0e697..9b8f118c69f9 100644 ++--- a/net/batman-adv/multicast.c +++++ b/net/batman-adv/multicast.c ++@@ -611,7 +611,7 @@ static void batadv_mcast_mla_update(struct work_struct *work) ++ */ ++ static bool batadv_mcast_is_report_ipv4(struct sk_buff *skb) ++ { ++- if (ip_mc_check_igmp(skb, NULL) < 0) +++ if (ip_mc_check_igmp(skb) < 0) ++ return false; ++ ++ switch (igmp_hdr(skb)->type) { ++@@ -677,7 +677,7 @@ static int batadv_mcast_forw_mode_check_ipv4(struct batadv_priv *bat_priv, ++ */ ++ static bool batadv_mcast_is_report_ipv6(struct sk_buff *skb) ++ { ++- if (ipv6_mc_check_mld(skb, NULL) < 0) +++ if (ipv6_mc_check_mld(skb) < 0) ++ return false; ++ ++ switch (icmp6_hdr(skb)->icmp6_type) { ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index b24782d53474..5224e9b3c46d 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -1128,7 +1128,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br, ++ ++ for (i = 0; i < num; i++) { ++ len += sizeof(*grec); ++- if (!pskb_may_pull(skb, len)) +++ if (!ip_mc_may_pull(skb, len)) ++ return -EINVAL; ++ ++ grec = (void *)(skb->data + len - sizeof(*grec)); ++@@ -1137,7 +1137,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br, ++ nsrcs = ntohs(grec->grec_nsrcs); ++ ++ len += nsrcs * 4; ++- if (!pskb_may_pull(skb, len)) +++ if (!ip_mc_may_pull(skb, len)) ++ return -EINVAL; ++ ++ /* We treat this as an IGMPv2 report for now. */ ++@@ -1176,15 +1176,17 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br, ++ struct sk_buff *skb, ++ u16 vid) ++ { +++ unsigned int nsrcs_offset; ++ const unsigned char *src; ++ struct icmp6hdr *icmp6h; ++ struct mld2_grec *grec; +++ unsigned int grec_len; ++ int i; ++ int len; ++ int num; ++ int err = 0; ++ ++- if (!pskb_may_pull(skb, sizeof(*icmp6h))) +++ if (!ipv6_mc_may_pull(skb, sizeof(*icmp6h))) ++ return -EINVAL; ++ ++ icmp6h = icmp6_hdr(skb); ++@@ -1195,23 +1197,26 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br, ++ __be16 *_nsrcs, __nsrcs; ++ u16 nsrcs; ++ ++- _nsrcs = skb_header_pointer(skb, ++- len + offsetof(struct mld2_grec, ++- grec_nsrcs), +++ nsrcs_offset = len + offsetof(struct mld2_grec, grec_nsrcs); +++ +++ if (skb_transport_offset(skb) + ipv6_transport_len(skb) < +++ nsrcs_offset + sizeof(_nsrcs)) +++ return -EINVAL; +++ +++ _nsrcs = skb_header_pointer(skb, nsrcs_offset, ++ sizeof(__nsrcs), &__nsrcs); ++ if (!_nsrcs) ++ return -EINVAL; ++ ++ nsrcs = ntohs(*_nsrcs); +++ grec_len = sizeof(*grec) + +++ sizeof(struct in6_addr) * nsrcs; ++ ++- if (!pskb_may_pull(skb, ++- len + sizeof(*grec) + ++- sizeof(struct in6_addr) * nsrcs)) +++ if (!ipv6_mc_may_pull(skb, len + grec_len)) ++ return -EINVAL; ++ ++ grec = (struct mld2_grec *)(skb->data + len); ++- len += sizeof(*grec) + ++- sizeof(struct in6_addr) * nsrcs; +++ len += grec_len; ++ ++ /* We treat these as MLDv1 reports for now. */ ++ switch (grec->grec_type) { ++@@ -1403,6 +1408,7 @@ static int br_ip4_multicast_query(struct net_bridge *br, ++ struct sk_buff *skb, ++ u16 vid) ++ { +++ unsigned int transport_len = ip_transport_len(skb); ++ const struct iphdr *iph = ip_hdr(skb); ++ struct igmphdr *ih = igmp_hdr(skb); ++ struct net_bridge_mdb_entry *mp; ++@@ -1412,7 +1418,6 @@ static int br_ip4_multicast_query(struct net_bridge *br, ++ struct br_ip saddr; ++ unsigned long max_delay; ++ unsigned long now = jiffies; ++- unsigned int offset = skb_transport_offset(skb); ++ __be32 group; ++ int err = 0; ++ ++@@ -1423,14 +1428,14 @@ static int br_ip4_multicast_query(struct net_bridge *br, ++ ++ group = ih->group; ++ ++- if (skb->len == offset + sizeof(*ih)) { +++ if (transport_len == sizeof(*ih)) { ++ max_delay = ih->code * (HZ / IGMP_TIMER_SCALE); ++ ++ if (!max_delay) { ++ max_delay = 10 * HZ; ++ group = 0; ++ } ++- } else if (skb->len >= offset + sizeof(*ih3)) { +++ } else if (transport_len >= sizeof(*ih3)) { ++ ih3 = igmpv3_query_hdr(skb); ++ if (ih3->nsrcs) ++ goto out; ++@@ -1482,6 +1487,7 @@ static int br_ip6_multicast_query(struct net_bridge *br, ++ struct sk_buff *skb, ++ u16 vid) ++ { +++ unsigned int transport_len = ipv6_transport_len(skb); ++ struct mld_msg *mld; ++ struct net_bridge_mdb_entry *mp; ++ struct mld2_query *mld2q; ++@@ -1500,7 +1506,7 @@ static int br_ip6_multicast_query(struct net_bridge *br, ++ (port && port->state == BR_STATE_DISABLED)) ++ goto out; ++ ++- if (skb->len == offset + sizeof(*mld)) { +++ if (transport_len == sizeof(*mld)) { ++ if (!pskb_may_pull(skb, offset + sizeof(*mld))) { ++ err = -EINVAL; ++ goto out; ++@@ -1771,12 +1777,11 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ++ struct sk_buff *skb, ++ u16 vid) ++ { ++- struct sk_buff *skb_trimmed = NULL; ++ const unsigned char *src; ++ struct igmphdr *ih; ++ int err; ++ ++- err = ip_mc_check_igmp(skb, &skb_trimmed); +++ err = ip_mc_check_igmp(skb); ++ ++ if (err == -ENOMSG) { ++ if (!ipv4_is_local_multicast(ip_hdr(skb)->daddr)) { ++@@ -1802,19 +1807,16 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ++ err = br_ip4_multicast_add_group(br, port, ih->group, vid, src); ++ break; ++ case IGMPV3_HOST_MEMBERSHIP_REPORT: ++- err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid); +++ err = br_ip4_multicast_igmp3_report(br, port, skb, vid); ++ break; ++ case IGMP_HOST_MEMBERSHIP_QUERY: ++- err = br_ip4_multicast_query(br, port, skb_trimmed, vid); +++ err = br_ip4_multicast_query(br, port, skb, vid); ++ break; ++ case IGMP_HOST_LEAVE_MESSAGE: ++ br_ip4_multicast_leave_group(br, port, ih->group, vid, src); ++ break; ++ } ++ ++- if (skb_trimmed && skb_trimmed != skb) ++- kfree_skb(skb_trimmed); ++- ++ br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp, ++ BR_MCAST_DIR_RX); ++ ++@@ -1827,12 +1829,11 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ++ struct sk_buff *skb, ++ u16 vid) ++ { ++- struct sk_buff *skb_trimmed = NULL; ++ const unsigned char *src; ++ struct mld_msg *mld; ++ int err; ++ ++- err = ipv6_mc_check_mld(skb, &skb_trimmed); +++ err = ipv6_mc_check_mld(skb); ++ ++ if (err == -ENOMSG) { ++ if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) ++@@ -1854,10 +1855,10 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ++ src); ++ break; ++ case ICMPV6_MLD2_REPORT: ++- err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid); +++ err = br_ip6_multicast_mld2_report(br, port, skb, vid); ++ break; ++ case ICMPV6_MGM_QUERY: ++- err = br_ip6_multicast_query(br, port, skb_trimmed, vid); +++ err = br_ip6_multicast_query(br, port, skb, vid); ++ break; ++ case ICMPV6_MGM_REDUCTION: ++ src = eth_hdr(skb)->h_source; ++@@ -1865,9 +1866,6 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ++ break; ++ } ++ ++- if (skb_trimmed && skb_trimmed != skb) ++- kfree_skb(skb_trimmed); ++- ++ br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp, ++ BR_MCAST_DIR_RX); ++ ++diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c ++index b6f0ee01f2e0..d6a222032be3 100644 ++--- a/net/ipv4/igmp.c +++++ b/net/ipv4/igmp.c ++@@ -1538,7 +1538,7 @@ static inline __sum16 ip_mc_validate_checksum(struct sk_buff *skb) ++ return skb_checksum_simple_validate(skb); ++ } ++ ++-static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) +++static int __ip_mc_check_igmp(struct sk_buff *skb) ++ ++ { ++ struct sk_buff *skb_chk; ++@@ -1560,16 +1560,10 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) ++ if (ret) ++ goto err; ++ ++- if (skb_trimmed) ++- *skb_trimmed = skb_chk; ++- /* free now unneeded clone */ ++- else if (skb_chk != skb) ++- kfree_skb(skb_chk); ++- ++ ret = 0; ++ ++ err: ++- if (ret && skb_chk && skb_chk != skb) +++ if (skb_chk && skb_chk != skb) ++ kfree_skb(skb_chk); ++ ++ return ret; ++@@ -1578,7 +1572,6 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) ++ /** ++ * ip_mc_check_igmp - checks whether this is a sane IGMP packet ++ * @skb: the skb to validate ++- * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) ++ * ++ * Checks whether an IPv4 packet is a valid IGMP packet. If so sets ++ * skb transport header accordingly and returns zero. ++@@ -1588,18 +1581,10 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) ++ * -ENOMSG: IP header validation succeeded but it is not an IGMP packet. ++ * -ENOMEM: A memory allocation failure happened. ++ * ++- * Optionally, an skb pointer might be provided via skb_trimmed (or set it ++- * to NULL): After parsing an IGMP packet successfully it will point to ++- * an skb which has its tail aligned to the IP packet end. This might ++- * either be the originally provided skb or a trimmed, cloned version if ++- * the skb frame had data beyond the IP packet. A cloned skb allows us ++- * to leave the original skb and its full frame unchanged (which might be ++- * desirable for layer 2 frame jugglers). ++- * ++ * Caller needs to set the skb network header and free any returned skb if it ++ * differs from the provided skb. ++ */ ++-int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) +++int ip_mc_check_igmp(struct sk_buff *skb) ++ { ++ int ret = ip_mc_check_iphdr(skb); ++ ++@@ -1609,7 +1594,7 @@ int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) ++ if (ip_hdr(skb)->protocol != IPPROTO_IGMP) ++ return -ENOMSG; ++ ++- return __ip_mc_check_igmp(skb, skb_trimmed); +++ return __ip_mc_check_igmp(skb); ++ } ++ EXPORT_SYMBOL(ip_mc_check_igmp); ++ ++diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c ++index 9405b04eecc6..1a917dc80d5e 100644 ++--- a/net/ipv6/mcast_snoop.c +++++ b/net/ipv6/mcast_snoop.c ++@@ -136,8 +136,7 @@ static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) ++ return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); ++ } ++ ++-static int __ipv6_mc_check_mld(struct sk_buff *skb, ++- struct sk_buff **skb_trimmed) +++static int __ipv6_mc_check_mld(struct sk_buff *skb) ++ ++ { ++ struct sk_buff *skb_chk = NULL; ++@@ -160,16 +159,10 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, ++ if (ret) ++ goto err; ++ ++- if (skb_trimmed) ++- *skb_trimmed = skb_chk; ++- /* free now unneeded clone */ ++- else if (skb_chk != skb) ++- kfree_skb(skb_chk); ++- ++ ret = 0; ++ ++ err: ++- if (ret && skb_chk && skb_chk != skb) +++ if (skb_chk && skb_chk != skb) ++ kfree_skb(skb_chk); ++ ++ return ret; ++@@ -178,7 +171,6 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, ++ /** ++ * ipv6_mc_check_mld - checks whether this is a sane MLD packet ++ * @skb: the skb to validate ++- * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) ++ * ++ * Checks whether an IPv6 packet is a valid MLD packet. If so sets ++ * skb transport header accordingly and returns zero. ++@@ -188,18 +180,10 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, ++ * -ENOMSG: IP header validation succeeded but it is not an MLD packet. ++ * -ENOMEM: A memory allocation failure happened. ++ * ++- * Optionally, an skb pointer might be provided via skb_trimmed (or set it ++- * to NULL): After parsing an MLD packet successfully it will point to ++- * an skb which has its tail aligned to the IP packet end. This might ++- * either be the originally provided skb or a trimmed, cloned version if ++- * the skb frame had data beyond the IP packet. A cloned skb allows us ++- * to leave the original skb and its full frame unchanged (which might be ++- * desirable for layer 2 frame jugglers). ++- * ++ * Caller needs to set the skb network header and free any returned skb if it ++ * differs from the provided skb. ++ */ ++-int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) +++int ipv6_mc_check_mld(struct sk_buff *skb) ++ { ++ int ret; ++ ++@@ -211,6 +195,6 @@ int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) ++ if (ret < 0) ++ return ret; ++ ++- return __ipv6_mc_check_mld(skb, skb_trimmed); +++ return __ipv6_mc_check_mld(skb); ++ } ++ EXPORT_SYMBOL(ipv6_mc_check_mld); ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch b/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..f6478bd664c8e2c366ba11c1fb9690ff77d22223 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/112-bridge-simplify-ip_mc_check_igmp-and-ipv6_mc_check_m.patch +@@ -0,0 +1,234 @@ ++From 96d914ab71c2540e8c492cbe6877854d17a5ec4c Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Mon, 21 Jan 2019 07:26:26 +0100 ++Subject: [PATCH] bridge: simplify ip_mc_check_igmp() and ++ ipv6_mc_check_mld() internals ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++With this patch the internal use of the skb_trimmed is reduced to ++the ICMPv6/IGMP checksum verification. And for the length checks ++the newly introduced helper functions are used instead of calculating ++and checking with skb->len directly. ++ ++These changes should hopefully make it easier to verify that length ++checks are performed properly. ++ ++Signed-off-by: Linus Lüssing ++Signed-off-by: David S. Miller ++--- ++ net/ipv4/igmp.c | 51 +++++++++++++++------------------- ++ net/ipv6/mcast_snoop.c | 62 ++++++++++++++++++++---------------------- ++ 2 files changed, 52 insertions(+), 61 deletions(-) ++ ++diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c ++index d6a222032be3..9033c8cb0e95 100644 ++--- a/net/ipv4/igmp.c +++++ b/net/ipv4/igmp.c ++@@ -1487,22 +1487,22 @@ static int ip_mc_check_igmp_reportv3(struct sk_buff *skb) ++ ++ len += sizeof(struct igmpv3_report); ++ ++- return pskb_may_pull(skb, len) ? 0 : -EINVAL; +++ return ip_mc_may_pull(skb, len) ? 0 : -EINVAL; ++ } ++ ++ static int ip_mc_check_igmp_query(struct sk_buff *skb) ++ { ++- unsigned int len = skb_transport_offset(skb); ++- ++- len += sizeof(struct igmphdr); ++- if (skb->len < len) ++- return -EINVAL; +++ unsigned int transport_len = ip_transport_len(skb); +++ unsigned int len; ++ ++ /* IGMPv{1,2}? */ ++- if (skb->len != len) { +++ if (transport_len != sizeof(struct igmphdr)) { ++ /* or IGMPv3? */ ++- len += sizeof(struct igmpv3_query) - sizeof(struct igmphdr); ++- if (skb->len < len || !pskb_may_pull(skb, len)) +++ if (transport_len < sizeof(struct igmpv3_query)) +++ return -EINVAL; +++ +++ len = skb_transport_offset(skb) + sizeof(struct igmpv3_query); +++ if (!ip_mc_may_pull(skb, len)) ++ return -EINVAL; ++ } ++ ++@@ -1538,35 +1538,24 @@ static inline __sum16 ip_mc_validate_checksum(struct sk_buff *skb) ++ return skb_checksum_simple_validate(skb); ++ } ++ ++-static int __ip_mc_check_igmp(struct sk_buff *skb) ++- +++static int ip_mc_check_igmp_csum(struct sk_buff *skb) ++ { ++- struct sk_buff *skb_chk; ++- unsigned int transport_len; ++ unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); ++- int ret = -EINVAL; +++ unsigned int transport_len = ip_transport_len(skb); +++ struct sk_buff *skb_chk; ++ ++- transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); +++ if (!ip_mc_may_pull(skb, len)) +++ return -EINVAL; ++ ++ skb_chk = skb_checksum_trimmed(skb, transport_len, ++ ip_mc_validate_checksum); ++ if (!skb_chk) ++- goto err; +++ return -EINVAL; ++ ++- if (!pskb_may_pull(skb_chk, len)) ++- goto err; ++- ++- ret = ip_mc_check_igmp_msg(skb_chk); ++- if (ret) ++- goto err; ++- ++- ret = 0; ++- ++-err: ++- if (skb_chk && skb_chk != skb) +++ if (skb_chk != skb) ++ kfree_skb(skb_chk); ++ ++- return ret; +++ return 0; ++ } ++ ++ /** ++@@ -1594,7 +1583,11 @@ int ip_mc_check_igmp(struct sk_buff *skb) ++ if (ip_hdr(skb)->protocol != IPPROTO_IGMP) ++ return -ENOMSG; ++ ++- return __ip_mc_check_igmp(skb); +++ ret = ip_mc_check_igmp_csum(skb); +++ if (ret < 0) +++ return ret; +++ +++ return ip_mc_check_igmp_msg(skb); ++ } ++ EXPORT_SYMBOL(ip_mc_check_igmp); ++ ++diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c ++index 1a917dc80d5e..a72ddfc40eb3 100644 ++--- a/net/ipv6/mcast_snoop.c +++++ b/net/ipv6/mcast_snoop.c ++@@ -77,27 +77,27 @@ static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) ++ ++ len += sizeof(struct mld2_report); ++ ++- return pskb_may_pull(skb, len) ? 0 : -EINVAL; +++ return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL; ++ } ++ ++ static int ipv6_mc_check_mld_query(struct sk_buff *skb) ++ { +++ unsigned int transport_len = ipv6_transport_len(skb); ++ struct mld_msg *mld; ++- unsigned int len = skb_transport_offset(skb); +++ unsigned int len; ++ ++ /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ ++ if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) ++ return -EINVAL; ++ ++- len += sizeof(struct mld_msg); ++- if (skb->len < len) ++- return -EINVAL; ++- ++ /* MLDv1? */ ++- if (skb->len != len) { +++ if (transport_len != sizeof(struct mld_msg)) { ++ /* or MLDv2? */ ++- len += sizeof(struct mld2_query) - sizeof(struct mld_msg); ++- if (skb->len < len || !pskb_may_pull(skb, len)) +++ if (transport_len < sizeof(struct mld2_query)) +++ return -EINVAL; +++ +++ len = skb_transport_offset(skb) + sizeof(struct mld2_query); +++ if (!ipv6_mc_may_pull(skb, len)) ++ return -EINVAL; ++ } ++ ++@@ -115,7 +115,13 @@ static int ipv6_mc_check_mld_query(struct sk_buff *skb) ++ ++ static int ipv6_mc_check_mld_msg(struct sk_buff *skb) ++ { ++- struct mld_msg *mld = (struct mld_msg *)skb_transport_header(skb); +++ unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); +++ struct mld_msg *mld; +++ +++ if (!ipv6_mc_may_pull(skb, len)) +++ return -EINVAL; +++ +++ mld = (struct mld_msg *)skb_transport_header(skb); ++ ++ switch (mld->mld_type) { ++ case ICMPV6_MGM_REDUCTION: ++@@ -136,36 +142,24 @@ static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) ++ return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); ++ } ++ ++-static int __ipv6_mc_check_mld(struct sk_buff *skb) ++- +++static int ipv6_mc_check_icmpv6(struct sk_buff *skb) ++ { ++- struct sk_buff *skb_chk = NULL; ++- unsigned int transport_len; ++- unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); ++- int ret = -EINVAL; +++ unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); +++ unsigned int transport_len = ipv6_transport_len(skb); +++ struct sk_buff *skb_chk; ++ ++- transport_len = ntohs(ipv6_hdr(skb)->payload_len); ++- transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr); +++ if (!ipv6_mc_may_pull(skb, len)) +++ return -EINVAL; ++ ++ skb_chk = skb_checksum_trimmed(skb, transport_len, ++ ipv6_mc_validate_checksum); ++ if (!skb_chk) ++- goto err; +++ return -EINVAL; ++ ++- if (!pskb_may_pull(skb_chk, len)) ++- goto err; ++- ++- ret = ipv6_mc_check_mld_msg(skb_chk); ++- if (ret) ++- goto err; ++- ++- ret = 0; ++- ++-err: ++- if (skb_chk && skb_chk != skb) +++ if (skb_chk != skb) ++ kfree_skb(skb_chk); ++ ++- return ret; +++ return 0; ++ } ++ ++ /** ++@@ -195,6 +189,10 @@ int ipv6_mc_check_mld(struct sk_buff *skb) ++ if (ret < 0) ++ return ret; ++ ++- return __ipv6_mc_check_mld(skb); +++ ret = ipv6_mc_check_icmpv6(skb); +++ if (ret < 0) +++ return ret; +++ +++ return ipv6_mc_check_mld_msg(skb); ++ } ++ EXPORT_SYMBOL(ipv6_mc_check_mld); ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch b/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..d98a80ffd5c28ab03deb17acd025a8c323898922 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/113-bridge-join-all-snoopers-multicast-address.patch +@@ -0,0 +1,174 @@ ++From 8fec90b6958b01ea8487cf9c821c39067644756a Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Mon, 21 Jan 2019 07:26:27 +0100 ++Subject: [PATCH] bridge: join all-snoopers multicast address ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++Next to snooping IGMP/MLD queries RFC4541, section 2.1.1.a) recommends ++to snoop multicast router advertisements to detect multicast routers. ++ ++Multicast router advertisements are sent to an "all-snoopers" ++multicast address. To be able to receive them reliably, we need to ++join this group. ++ ++Otherwise other snooping switches might refrain from forwarding these ++advertisements to us. ++ ++Signed-off-by: Linus Lüssing ++Signed-off-by: David S. Miller ++--- ++ include/uapi/linux/in.h | 9 ++--- ++ net/bridge/br_multicast.c | 72 ++++++++++++++++++++++++++++++++++++++- ++ net/ipv6/mcast.c | 2 ++ ++ 3 files changed, 78 insertions(+), 5 deletions(-) ++ ++diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h ++index 48e8a225b985..478443169386 100644 ++--- a/include/uapi/linux/in.h +++++ b/include/uapi/linux/in.h ++@@ -288,10 +288,11 @@ struct sockaddr_in { ++ #define IN_LOOPBACK(a) ((((long int) (a)) & 0xff000000) == 0x7f000000) ++ ++ /* Defines for Multicast INADDR */ ++-#define INADDR_UNSPEC_GROUP 0xe0000000U /* 224.0.0.0 */ ++-#define INADDR_ALLHOSTS_GROUP 0xe0000001U /* 224.0.0.1 */ ++-#define INADDR_ALLRTRS_GROUP 0xe0000002U /* 224.0.0.2 */ ++-#define INADDR_MAX_LOCAL_GROUP 0xe00000ffU /* 224.0.0.255 */ +++#define INADDR_UNSPEC_GROUP 0xe0000000U /* 224.0.0.0 */ +++#define INADDR_ALLHOSTS_GROUP 0xe0000001U /* 224.0.0.1 */ +++#define INADDR_ALLRTRS_GROUP 0xe0000002U /* 224.0.0.2 */ +++#define INADDR_ALLSNOOPERS_GROUP 0xe000006aU /* 224.0.0.106 */ +++#define INADDR_MAX_LOCAL_GROUP 0xe00000ffU /* 224.0.0.255 */ ++ #endif ++ ++ /* contains the htonl type stuff.. */ ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index 5224e9b3c46d..917cc9d13ea9 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -1970,6 +1970,68 @@ void br_multicast_init(struct net_bridge *br) ++ #endif ++ } ++ +++static void br_ip4_multicast_join_snoopers(struct net_bridge *br) +++{ +++ struct in_device *in_dev = in_dev_get(br->dev); +++ +++ if (!in_dev) +++ return; +++ +++ ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP)); +++ in_dev_put(in_dev); +++} +++ +++#if IS_ENABLED(CONFIG_IPV6) +++static void br_ip6_multicast_join_snoopers(struct net_bridge *br) +++{ +++ struct in6_addr addr; +++ +++ ipv6_addr_set(&addr, htonl(0xff020000), 0, 0, htonl(0x6a)); +++ ipv6_dev_mc_inc(br->dev, &addr); +++} +++#else +++static inline void br_ip6_multicast_join_snoopers(struct net_bridge *br) +++{ +++} +++#endif +++ +++static void br_multicast_join_snoopers(struct net_bridge *br) +++{ +++ br_ip4_multicast_join_snoopers(br); +++ br_ip6_multicast_join_snoopers(br); +++} +++ +++static void br_ip4_multicast_leave_snoopers(struct net_bridge *br) +++{ +++ struct in_device *in_dev = in_dev_get(br->dev); +++ +++ if (WARN_ON(!in_dev)) +++ return; +++ +++ ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP)); +++ in_dev_put(in_dev); +++} +++ +++#if IS_ENABLED(CONFIG_IPV6) +++static void br_ip6_multicast_leave_snoopers(struct net_bridge *br) +++{ +++ struct in6_addr addr; +++ +++ ipv6_addr_set(&addr, htonl(0xff020000), 0, 0, htonl(0x6a)); +++ ipv6_dev_mc_dec(br->dev, &addr); +++} +++#else +++static inline void br_ip6_multicast_leave_snoopers(struct net_bridge *br) +++{ +++} +++#endif +++ +++static void br_multicast_leave_snoopers(struct net_bridge *br) +++{ +++ br_ip4_multicast_leave_snoopers(br); +++ br_ip6_multicast_leave_snoopers(br); +++} +++ ++ static void __br_multicast_open(struct net_bridge *br, ++ struct bridge_mcast_own_query *query) ++ { ++@@ -1983,6 +2045,9 @@ static void __br_multicast_open(struct net_bridge *br, ++ ++ void br_multicast_open(struct net_bridge *br) ++ { +++ if (!br->multicast_disabled) +++ br_multicast_join_snoopers(br); +++ ++ __br_multicast_open(br, &br->ip4_own_query); ++ #if IS_ENABLED(CONFIG_IPV6) ++ __br_multicast_open(br, &br->ip6_own_query); ++@@ -1998,6 +2063,9 @@ void br_multicast_stop(struct net_bridge *br) ++ del_timer_sync(&br->ip6_other_query.timer); ++ del_timer_sync(&br->ip6_own_query.timer); ++ #endif +++ +++ if (!br->multicast_disabled) +++ br_multicast_leave_snoopers(br); ++ } ++ ++ void br_multicast_dev_del(struct net_bridge *br) ++@@ -2152,8 +2220,10 @@ int br_multicast_toggle(struct net_bridge *br, unsigned long val) ++ ++ br_mc_disabled_update(br->dev, !val); ++ br->multicast_disabled = !val; ++- if (br->multicast_disabled) +++ if (br->multicast_disabled) { +++ br_multicast_leave_snoopers(br); ++ goto unlock; +++ } ++ ++ if (!netif_running(br->dev)) ++ goto unlock; ++diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c ++index 611dc5d55fa0..d907c938ec1a 100644 ++--- a/net/ipv6/mcast.c +++++ b/net/ipv6/mcast.c ++@@ -915,6 +915,7 @@ int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr) ++ ma_put(mc); ++ return 0; ++ } +++EXPORT_SYMBOL(ipv6_dev_mc_inc); ++ ++ /* ++ * device multicast group del ++@@ -962,6 +963,7 @@ int ipv6_dev_mc_dec(struct net_device *dev, const struct in6_addr *addr) ++ ++ return err; ++ } +++EXPORT_SYMBOL(ipv6_dev_mc_dec); ++ ++ /* ++ * check if the interface/address pair is valid ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch b/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..92279ce9ee866dfd006e5937b718ac7bcb7e7c6a +--- /dev/null ++++ b/target/linux/generic/backport-4.14/114-bridge-Snoop-Multicast-Router-Advertisements.patch +@@ -0,0 +1,246 @@ ++From 690f6ed25b5fac9408fdebb7b4e8131f5b0a5c86 Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Mon, 21 Jan 2019 07:26:28 +0100 ++Subject: [PATCH] bridge: Snoop Multicast Router Advertisements ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++When multiple multicast routers are present in a broadcast domain then ++only one of them will be detectable via IGMP/MLD query snooping. The ++multicast router with the lowest IP address will become the selected and ++active querier while all other multicast routers will then refrain from ++sending queries. ++ ++To detect such rather silent multicast routers, too, RFC4286 ++("Multicast Router Discovery") provides a standardized protocol to ++detect multicast routers for multicast snooping switches. ++ ++This patch implements the necessary MRD Advertisement message parsing ++and after successful processing adds such routers to the internal ++multicast router list. ++ ++Signed-off-by: Linus Lüssing ++Signed-off-by: David S. Miller ++--- ++ include/linux/in.h | 5 ++++ ++ include/net/addrconf.h | 15 ++++++++++ ++ include/uapi/linux/icmpv6.h | 2 ++ ++ include/uapi/linux/igmp.h | 1 + ++ net/bridge/br_multicast.c | 55 +++++++++++++++++++++++++++++++++++++ ++ net/ipv6/mcast_snoop.c | 5 +++- ++ 6 files changed, 82 insertions(+), 1 deletion(-) ++ ++diff --git a/include/linux/in.h b/include/linux/in.h ++index 31b493734763..435e7f2a513a 100644 ++--- a/include/linux/in.h +++++ b/include/linux/in.h ++@@ -60,6 +60,11 @@ static inline bool ipv4_is_lbcast(__be32 addr) ++ return addr == htonl(INADDR_BROADCAST); ++ } ++ +++static inline bool ipv4_is_all_snoopers(__be32 addr) +++{ +++ return addr == htonl(INADDR_ALLSNOOPERS_GROUP); +++} +++ ++ static inline bool ipv4_is_zeronet(__be32 addr) ++ { ++ return (addr & htonl(0xff000000)) == htonl(0x00000000); ++diff --git a/include/net/addrconf.h b/include/net/addrconf.h ++index 0eb1e1f6ea9a..c93530f9e8e4 100644 ++--- a/include/net/addrconf.h +++++ b/include/net/addrconf.h ++@@ -217,6 +217,7 @@ void ipv6_mc_unmap(struct inet6_dev *idev); ++ void ipv6_mc_remap(struct inet6_dev *idev); ++ void ipv6_mc_init_dev(struct inet6_dev *idev); ++ void ipv6_mc_destroy_dev(struct inet6_dev *idev); +++int ipv6_mc_check_icmpv6(struct sk_buff *skb); ++ int ipv6_mc_check_mld(struct sk_buff *skb); ++ void addrconf_dad_failure(struct inet6_ifaddr *ifp); ++ ++@@ -445,6 +446,20 @@ static inline bool ipv6_addr_is_solict_mult(const struct in6_addr *addr) ++ #endif ++ } ++ +++static inline bool ipv6_addr_is_all_snoopers(const struct in6_addr *addr) +++{ +++#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && BITS_PER_LONG == 64 +++ __be64 *p = (__be64 *)addr; +++ +++ return ((p[0] ^ cpu_to_be64(0xff02000000000000UL)) | +++ (p[1] ^ cpu_to_be64(0x6a))) == 0UL; +++#else +++ return ((addr->s6_addr32[0] ^ htonl(0xff020000)) | +++ addr->s6_addr32[1] | addr->s6_addr32[2] | +++ (addr->s6_addr32[3] ^ htonl(0x0000006a))) == 0; +++#endif +++} +++ ++ #ifdef CONFIG_PROC_FS ++ int if6_proc_init(void); ++ void if6_proc_exit(void); ++diff --git a/include/uapi/linux/icmpv6.h b/include/uapi/linux/icmpv6.h ++index caf8dc019250..325395f56bfa 100644 ++--- a/include/uapi/linux/icmpv6.h +++++ b/include/uapi/linux/icmpv6.h ++@@ -108,6 +108,8 @@ struct icmp6hdr { ++ #define ICMPV6_MOBILE_PREFIX_SOL 146 ++ #define ICMPV6_MOBILE_PREFIX_ADV 147 ++ +++#define ICMPV6_MRDISC_ADV 151 +++ ++ /* ++ * Codes for Destination Unreachable ++ */ ++diff --git a/include/uapi/linux/igmp.h b/include/uapi/linux/igmp.h ++index 7e44ac02ca18..90c28bc466c6 100644 ++--- a/include/uapi/linux/igmp.h +++++ b/include/uapi/linux/igmp.h ++@@ -93,6 +93,7 @@ struct igmpv3_query { ++ #define IGMP_MTRACE_RESP 0x1e ++ #define IGMP_MTRACE 0x1f ++ +++#define IGMP_MRDISC_ADV 0x30 /* From RFC4286 */ ++ ++ /* ++ * Use the BSD names for these for compatibility ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index 917cc9d13ea9..8f0bd1133bbd 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -14,6 +14,7 @@ ++ #include ++ #include ++ #include +++#include ++ #include ++ #include ++ #include ++@@ -29,10 +30,12 @@ ++ #include ++ #include ++ #if IS_ENABLED(CONFIG_IPV6) +++#include ++ #include ++ #include ++ #include ++ #include +++#include ++ #endif ++ ++ #include "br_private.h" ++@@ -1772,6 +1775,19 @@ static void br_multicast_pim(struct net_bridge *br, ++ br_multicast_mark_router(br, port); ++ } ++ +++static int br_ip4_multicast_mrd_rcv(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb) +++{ +++ if (ip_hdr(skb)->protocol != IPPROTO_IGMP || +++ igmp_hdr(skb)->type != IGMP_MRDISC_ADV) +++ return -ENOMSG; +++ +++ br_multicast_mark_router(br, port); +++ +++ return 0; +++} +++ ++ static int br_multicast_ipv4_rcv(struct net_bridge *br, ++ struct net_bridge_port *port, ++ struct sk_buff *skb, ++@@ -1789,7 +1805,15 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ++ } else if (pim_ipv4_all_pim_routers(ip_hdr(skb)->daddr)) { ++ if (ip_hdr(skb)->protocol == IPPROTO_PIM) ++ br_multicast_pim(br, port, skb); +++ } else if (ipv4_is_all_snoopers(ip_hdr(skb)->daddr)) { +++ err = br_ip4_multicast_mrd_rcv(br, port, skb); +++ +++ if (err < 0 && err != -ENOMSG) { +++ br_multicast_err_count(br, port, skb->protocol); +++ return err; +++ } ++ } +++ ++ return 0; ++ } else if (err < 0) { ++ br_multicast_err_count(br, port, skb->protocol); ++@@ -1824,6 +1848,27 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ++ } ++ ++ #if IS_ENABLED(CONFIG_IPV6) +++static int br_ip6_multicast_mrd_rcv(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb) +++{ +++ int ret; +++ +++ if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6) +++ return -ENOMSG; +++ +++ ret = ipv6_mc_check_icmpv6(skb); +++ if (ret < 0) +++ return ret; +++ +++ if (icmp6_hdr(skb)->icmp6_type != ICMPV6_MRDISC_ADV) +++ return -ENOMSG; +++ +++ br_multicast_mark_router(br, port); +++ +++ return 0; +++} +++ ++ static int br_multicast_ipv6_rcv(struct net_bridge *br, ++ struct net_bridge_port *port, ++ struct sk_buff *skb, ++@@ -1838,6 +1883,16 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ++ if (err == -ENOMSG) { ++ if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) ++ BR_INPUT_SKB_CB(skb)->mrouters_only = 1; +++ +++ if (ipv6_addr_is_all_snoopers(&ipv6_hdr(skb)->daddr)) { +++ err = br_ip6_multicast_mrd_rcv(br, port, skb); +++ +++ if (err < 0 && err != -ENOMSG) { +++ br_multicast_err_count(br, port, skb->protocol); +++ return err; +++ } +++ } +++ ++ return 0; ++ } else if (err < 0) { ++ br_multicast_err_count(br, port, skb->protocol); ++diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c ++index a72ddfc40eb3..55e2ac179f28 100644 ++--- a/net/ipv6/mcast_snoop.c +++++ b/net/ipv6/mcast_snoop.c ++@@ -41,6 +41,8 @@ static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) ++ if (skb->len < len || len <= offset) ++ return -EINVAL; ++ +++ skb_set_transport_header(skb, offset); +++ ++ return 0; ++ } ++ ++@@ -142,7 +144,7 @@ static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) ++ return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); ++ } ++ ++-static int ipv6_mc_check_icmpv6(struct sk_buff *skb) +++int ipv6_mc_check_icmpv6(struct sk_buff *skb) ++ { ++ unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); ++ unsigned int transport_len = ipv6_transport_len(skb); ++@@ -161,6 +163,7 @@ static int ipv6_mc_check_icmpv6(struct sk_buff *skb) ++ ++ return 0; ++ } +++EXPORT_SYMBOL(ipv6_mc_check_icmpv6); ++ ++ /** ++ * ipv6_mc_check_mld - checks whether this is a sane MLD packet ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch b/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..ce8b19454986f2c61a9cda24f61a9eef5eaacf56 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/115-bridge-remove-duplicated-include-from-br_multicast.c.patch +@@ -0,0 +1,28 @@ ++From 995dbd5e30c99deb8f0c91264ae8d75951b832f7 Mon Sep 17 00:00:00 2001 ++From: YueHaibing ++Date: Fri, 25 Jan 2019 10:59:09 +0800 ++Subject: [PATCH] bridge: remove duplicated include from br_multicast.c ++ ++Remove duplicated include. ++ ++Signed-off-by: YueHaibing ++Signed-off-by: David S. Miller ++--- ++ net/bridge/br_multicast.c | 1 - ++ 1 file changed, 1 deletion(-) ++ ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index 8f0bd1133bbd..4c1b9c0a290b 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -35,7 +35,6 @@ ++ #include ++ #include ++ #include ++-#include ++ #endif ++ ++ #include "br_private.h" ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch b/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..cb22c3fb97d232e8664569a5842d8c1bbd7c9596 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/116-net-Fix-ip_mc_-dec-inc-_group-allocation-context.patch +@@ -0,0 +1,221 @@ ++From cb12869990893ec947c2d97b0e64449174c72d4a Mon Sep 17 00:00:00 2001 ++From: Florian Fainelli ++Date: Fri, 1 Feb 2019 20:20:52 -0800 ++Subject: [PATCH] net: Fix ip_mc_{dec,inc}_group allocation context ++ ++After 4effd28c1245 ("bridge: join all-snoopers multicast address"), I ++started seeing the following sleep in atomic warnings: ++ ++[ 26.763893] BUG: sleeping function called from invalid context at mm/slab.h:421 ++[ 26.771425] in_atomic(): 1, irqs_disabled(): 0, pid: 1658, name: sh ++[ 26.777855] INFO: lockdep is turned off. ++[ 26.781916] CPU: 0 PID: 1658 Comm: sh Not tainted 5.0.0-rc4 #20 ++[ 26.787943] Hardware name: BCM97278SV (DT) ++[ 26.792118] Call trace: ++[ 26.794645] dump_backtrace+0x0/0x170 ++[ 26.798391] show_stack+0x24/0x30 ++[ 26.801787] dump_stack+0xa4/0xe4 ++[ 26.805182] ___might_sleep+0x208/0x218 ++[ 26.809102] __might_sleep+0x78/0x88 ++[ 26.812762] kmem_cache_alloc_trace+0x64/0x28c ++[ 26.817301] igmp_group_dropped+0x150/0x230 ++[ 26.821573] ip_mc_dec_group+0x1b0/0x1f8 ++[ 26.825585] br_ip4_multicast_leave_snoopers.isra.11+0x174/0x190 ++[ 26.831704] br_multicast_toggle+0x78/0xcc ++[ 26.835887] store_bridge_parm+0xc4/0xfc ++[ 26.839894] multicast_snooping_store+0x3c/0x4c ++[ 26.844517] dev_attr_store+0x44/0x5c ++[ 26.848262] sysfs_kf_write+0x50/0x68 ++[ 26.852006] kernfs_fop_write+0x14c/0x1b4 ++[ 26.856102] __vfs_write+0x60/0x190 ++[ 26.859668] vfs_write+0xc8/0x168 ++[ 26.863059] ksys_write+0x70/0xc8 ++[ 26.866449] __arm64_sys_write+0x24/0x30 ++[ 26.870458] el0_svc_common+0xa0/0x11c ++[ 26.874291] el0_svc_handler+0x38/0x70 ++[ 26.878120] el0_svc+0x8/0xc ++ ++while toggling the bridge's multicast_snooping attribute dynamically. ++ ++Pass a gfp_t down to igmpv3_add_delrec(), introduce ++__igmp_group_dropped() and introduce __ip_mc_dec_group() to take a gfp_t ++argument. ++ ++Similarly introduce ____ip_mc_inc_group() and __ip_mc_inc_group() to ++allow caller to specify gfp_t. ++ ++IPv6 part of the patch appears fine. ++ ++Fixes: 4effd28c1245 ("bridge: join all-snoopers multicast address") ++Signed-off-by: Florian Fainelli ++Signed-off-by: David S. Miller ++--- ++ include/linux/igmp.h | 8 +++++++- ++ net/bridge/br_multicast.c | 4 ++-- ++ net/ipv4/igmp.c | 33 +++++++++++++++++++++++---------- ++ 3 files changed, 32 insertions(+), 13 deletions(-) ++ ++diff --git a/include/linux/igmp.h b/include/linux/igmp.h ++index 858143489a2a..a9cccb4fcb29 100644 ++--- a/include/linux/igmp.h +++++ b/include/linux/igmp.h ++@@ -135,7 +135,13 @@ extern void ip_mc_up(struct in_device *); ++ extern void ip_mc_down(struct in_device *); ++ extern void ip_mc_unmap(struct in_device *); ++ extern void ip_mc_remap(struct in_device *); ++-extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr); +++extern void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp); +++static inline void ip_mc_dec_group(struct in_device *in_dev, __be32 addr) +++{ +++ return __ip_mc_dec_group(in_dev, addr, GFP_KERNEL); +++} +++extern void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr, +++ gfp_t gfp); ++ extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr); ++ int ip_mc_check_igmp(struct sk_buff *skb); ++ ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index 4c1b9c0a290b..779af4efea27 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -2031,7 +2031,7 @@ static void br_ip4_multicast_join_snoopers(struct net_bridge *br) ++ if (!in_dev) ++ return; ++ ++- ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP)); +++ __ip_mc_inc_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP), GFP_ATOMIC); ++ in_dev_put(in_dev); ++ } ++ ++@@ -2062,7 +2062,7 @@ static void br_ip4_multicast_leave_snoopers(struct net_bridge *br) ++ if (WARN_ON(!in_dev)) ++ return; ++ ++- ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP)); +++ __ip_mc_dec_group(in_dev, htonl(INADDR_ALLSNOOPERS_GROUP), GFP_ATOMIC); ++ in_dev_put(in_dev); ++ } ++ ++diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c ++index 9033c8cb0e95..862f25f19441 100644 ++--- a/net/ipv4/igmp.c +++++ b/net/ipv4/igmp.c ++@@ -162,7 +162,8 @@ static int unsolicited_report_interval(struct in_device *in_dev) ++ return interval_jiffies; ++ } ++ ++-static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im); +++static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im, +++ gfp_t gfp); ++ static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im); ++ static void igmpv3_clear_delrec(struct in_device *in_dev); ++ static int sf_setstate(struct ip_mc_list *pmc); ++@@ -1152,7 +1153,8 @@ static void ip_mc_filter_del(struct in_device *in_dev, __be32 addr) ++ /* ++ * deleted ip_mc_list manipulation ++ */ ++-static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im) +++static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im, +++ gfp_t gfp) ++ { ++ struct ip_mc_list *pmc; ++ struct net *net = dev_net(in_dev->dev); ++@@ -1163,7 +1165,7 @@ static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im) ++ * for deleted items allows change reports to use common code with ++ * non-deleted or query-response MCA's. ++ */ ++- pmc = kzalloc(sizeof(*pmc), GFP_KERNEL); +++ pmc = kzalloc(sizeof(*pmc), gfp); ++ if (!pmc) ++ return; ++ spin_lock_init(&pmc->lock); ++@@ -1264,7 +1266,7 @@ static void igmpv3_clear_delrec(struct in_device *in_dev) ++ } ++ #endif ++ ++-static void igmp_group_dropped(struct ip_mc_list *im) +++static void __igmp_group_dropped(struct ip_mc_list *im, gfp_t gfp) ++ { ++ struct in_device *in_dev = im->interface; ++ #ifdef CONFIG_IP_MULTICAST ++@@ -1295,13 +1297,18 @@ static void igmp_group_dropped(struct ip_mc_list *im) ++ return; ++ } ++ /* IGMPv3 */ ++- igmpv3_add_delrec(in_dev, im); +++ igmpv3_add_delrec(in_dev, im, gfp); ++ ++ igmp_ifc_event(in_dev); ++ } ++ #endif ++ } ++ +++static void igmp_group_dropped(struct ip_mc_list *im) +++{ +++ __igmp_group_dropped(im, GFP_KERNEL); +++} +++ ++ static void igmp_group_added(struct ip_mc_list *im) ++ { ++ struct in_device *in_dev = im->interface; ++@@ -1396,7 +1403,7 @@ static void ip_mc_hash_remove(struct in_device *in_dev, ++ * A socket has joined a multicast group on device dev. ++ */ ++ ++-void ip_mc_inc_group(struct in_device *in_dev, __be32 addr) +++void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr, gfp_t gfp) ++ { ++ struct ip_mc_list *im; ++ #ifdef CONFIG_IP_MULTICAST ++@@ -1413,7 +1420,7 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr) ++ } ++ } ++ ++- im = kzalloc(sizeof(*im), GFP_KERNEL); +++ im = kzalloc(sizeof(*im), gfp); ++ if (!im) ++ goto out; ++ ++@@ -1446,6 +1453,12 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr) ++ out: ++ return; ++ } +++EXPORT_SYMBOL(__ip_mc_inc_group); +++ +++void ip_mc_inc_group(struct in_device *in_dev, __be32 addr) +++{ +++ __ip_mc_inc_group(in_dev, addr, GFP_KERNEL); +++} ++ EXPORT_SYMBOL(ip_mc_inc_group); ++ ++ static int ip_mc_check_iphdr(struct sk_buff *skb) ++@@ -1628,7 +1641,7 @@ static void ip_mc_rejoin_groups(struct in_device *in_dev) ++ * A socket has left a multicast group on device dev ++ */ ++ ++-void ip_mc_dec_group(struct in_device *in_dev, __be32 addr) +++void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp) ++ { ++ struct ip_mc_list *i; ++ struct ip_mc_list __rcu **ip; ++@@ -1643,7 +1656,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr) ++ ip_mc_hash_remove(in_dev, i); ++ *ip = i->next_rcu; ++ in_dev->mc_count--; ++- igmp_group_dropped(i); +++ __igmp_group_dropped(i, gfp); ++ ip_mc_clear_src(i); ++ ++ if (!in_dev->dead) ++@@ -1656,7 +1669,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr) ++ } ++ } ++ } ++-EXPORT_SYMBOL(ip_mc_dec_group); +++EXPORT_SYMBOL(__ip_mc_dec_group); ++ ++ /* Device changing type */ ++ ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch b/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..0d390f1d2f1b4a3542257e14761a7e1a9d9072c3 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/117-net-remove-unneeded-switch-fall-through.patch +@@ -0,0 +1,42 @@ ++From 4b3920aedc1b730583dccaeb702ab67722b09b5c Mon Sep 17 00:00:00 2001 ++From: Li RongQing ++Date: Tue, 19 Feb 2019 10:15:56 +0800 ++Subject: [PATCH] net: remove unneeded switch fall-through ++ ++This case block has been terminated by a return, so not need ++a switch fall-through ++ ++Signed-off-by: Li RongQing ++Signed-off-by: David S. Miller ++--- ++ net/ipv4/igmp.c | 1 - ++ net/ipv6/mcast_snoop.c | 1 - ++ 2 files changed, 2 deletions(-) ++ ++diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c ++index 862f25f19441..9fbf4f9117bb 100644 ++--- a/net/ipv4/igmp.c +++++ b/net/ipv4/igmp.c ++@@ -1535,7 +1535,6 @@ static int ip_mc_check_igmp_msg(struct sk_buff *skb) ++ case IGMP_HOST_LEAVE_MESSAGE: ++ case IGMP_HOST_MEMBERSHIP_REPORT: ++ case IGMPV2_HOST_MEMBERSHIP_REPORT: ++- /* fall through */ ++ return 0; ++ case IGMPV3_HOST_MEMBERSHIP_REPORT: ++ return ip_mc_check_igmp_reportv3(skb); ++diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c ++index 55e2ac179f28..dddd75d1be0e 100644 ++--- a/net/ipv6/mcast_snoop.c +++++ b/net/ipv6/mcast_snoop.c ++@@ -128,7 +128,6 @@ static int ipv6_mc_check_mld_msg(struct sk_buff *skb) ++ switch (mld->mld_type) { ++ case ICMPV6_MGM_REDUCTION: ++ case ICMPV6_MGM_REPORT: ++- /* fall through */ ++ return 0; ++ case ICMPV6_MLD2_REPORT: ++ return ipv6_mc_check_mld_reportv2(skb); ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch b/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..c17e2b4bd7b499b92a224450be0dca311d62027a +--- /dev/null ++++ b/target/linux/generic/backport-4.14/118-ipv6-Fix-return-value-of-ipv6_mc_may_pull-for-malfor.patch +@@ -0,0 +1,47 @@ ++From fc8cb7c0bf06853ea1f6ee14409b7f91b0acbe85 Mon Sep 17 00:00:00 2001 ++From: Stefano Brivio ++Date: Tue, 13 Aug 2019 00:46:01 +0200 ++Subject: [PATCH] ipv6: Fix return value of ipv6_mc_may_pull() for ++ malformed packets ++ ++Commit ba5ea614622d ("bridge: simplify ip_mc_check_igmp() and ++ipv6_mc_check_mld() calls") replaces direct calls to pskb_may_pull() ++in br_ipv6_multicast_mld2_report() with calls to ipv6_mc_may_pull(), ++that returns -EINVAL on buffers too short to be valid IPv6 packets, ++while maintaining the previous handling of the return code. ++ ++This leads to the direct opposite of the intended effect: if the ++packet is malformed, -EINVAL evaluates as true, and we'll happily ++proceed with the processing. ++ ++Return 0 if the packet is too short, in the same way as this was ++fixed for IPv4 by commit 083b78a9ed64 ("ip: fix ip_mc_may_pull() ++return value"). ++ ++I don't have a reproducer for this, unlike the one referred to by ++the IPv4 commit, but this is clearly broken. ++ ++Fixes: ba5ea614622d ("bridge: simplify ip_mc_check_igmp() and ipv6_mc_check_mld() calls") ++Signed-off-by: Stefano Brivio ++Acked-by: Guillaume Nault ++Signed-off-by: David S. Miller ++--- ++ include/net/addrconf.h | 2 +- ++ 1 file changed, 1 insertion(+), 1 deletion(-) ++ ++diff --git a/include/net/addrconf.h b/include/net/addrconf.h ++index c93530f9e8e4..ea97f70d66b8 100644 ++--- a/include/net/addrconf.h +++++ b/include/net/addrconf.h ++@@ -194,7 +194,7 @@ static inline int ipv6_mc_may_pull(struct sk_buff *skb, ++ unsigned int len) ++ { ++ if (skb_transport_offset(skb) + ipv6_transport_len(skb) < len) ++- return -EINVAL; +++ return 0; ++ ++ return pskb_may_pull(skb, len); ++ } ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch b/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..92bb9275df9d54778ce8f00b1cb6e999eae40606 +--- /dev/null ++++ b/target/linux/generic/backport-4.14/119-timers-Add-a-function-to-start-reduce-a-timer.patch +@@ -0,0 +1,201 @@ ++From dbc2cc8bcbdb0765516a7d0201f3337e66f4f2fc Mon Sep 17 00:00:00 2001 ++From: David Howells ++Date: Thu, 9 Nov 2017 12:35:07 +0000 ++Subject: [PATCH] timers: Add a function to start/reduce a timer ++ ++Add a function, similar to mod_timer(), that will start a timer if it isn't ++running and will modify it if it is running and has an expiry time longer ++than the new time. If the timer is running with an expiry time that's the ++same or sooner, no change is made. ++ ++The function looks like: ++ ++ int timer_reduce(struct timer_list *timer, unsigned long expires); ++ ++This can be used by code such as networking code to make it easier to share ++a timer for multiple timeouts. For instance, in upcoming AF_RXRPC code, ++the rxrpc_call struct will maintain a number of timeouts: ++ ++ unsigned long ack_at; ++ unsigned long resend_at; ++ unsigned long ping_at; ++ unsigned long expect_rx_by; ++ unsigned long expect_req_by; ++ unsigned long expect_term_by; ++ ++each of which is set independently of the others. With timer reduction ++available, when the code needs to set one of the timeouts, it only needs to ++look at that timeout and then call timer_reduce() to modify the timer, ++starting it or bringing it forward if necessary. There is no need to refer ++to the other timeouts to see which is earliest and no need to take any lock ++other than, potentially, the timer lock inside timer_reduce(). ++ ++Note, that this does not protect against concurrent invocations of any of ++the timer functions. ++ ++As an example, the expect_rx_by timeout above, which terminates a call if ++we don't get a packet from the server within a certain time window, would ++be set something like this: ++ ++ unsigned long now = jiffies; ++ unsigned long expect_rx_by = now + packet_receive_timeout; ++ WRITE_ONCE(call->expect_rx_by, expect_rx_by); ++ timer_reduce(&call->timer, expect_rx_by); ++ ++The timer service code (which might, say, be in a work function) would then ++check all the timeouts to see which, if any, had triggered, deal with ++those: ++ ++ t = READ_ONCE(call->ack_at); ++ if (time_after_eq(now, t)) { ++ cmpxchg(&call->ack_at, t, now + MAX_JIFFY_OFFSET); ++ set_bit(RXRPC_CALL_EV_ACK, &call->events); ++ } ++ ++and then restart the timer if necessary by finding the soonest timeout that ++hasn't yet passed and then calling timer_reduce(). ++ ++The disadvantage of doing things this way rather than comparing the timers ++each time and calling mod_timer() is that you *will* take timer events ++unless you can finish what you're doing and delete the timer in time. ++ ++The advantage of doing things this way is that you don't need to use a lock ++to work out when the next timer should be set, other than the timer's own ++lock - which you might not have to take. ++ ++[ tglx: Fixed weird formatting and adopted it to pending changes ] ++ ++Signed-off-by: David Howells ++Signed-off-by: Thomas Gleixner ++Cc: keyrings@vger.kernel.org ++Cc: linux-afs@lists.infradead.org ++Link: https://lkml.kernel.org/r/151023090769.23050.1801643667223880753.stgit@warthog.procyon.org.uk ++--- ++ include/linux/timer.h | 1 + ++ kernel/time/timer.c | 45 ++++++++++++++++++++++++++++++++++++------- ++ 2 files changed, 39 insertions(+), 7 deletions(-) ++ ++diff --git a/include/linux/timer.h b/include/linux/timer.h ++index e0ea1fe87572..db65e00fb7b2 100644 ++--- a/include/linux/timer.h +++++ b/include/linux/timer.h ++@@ -202,6 +202,7 @@ extern void add_timer_on(struct timer_list *timer, int cpu); ++ extern int del_timer(struct timer_list * timer); ++ extern int mod_timer(struct timer_list *timer, unsigned long expires); ++ extern int mod_timer_pending(struct timer_list *timer, unsigned long expires); +++extern int timer_reduce(struct timer_list *timer, unsigned long expires); ++ ++ /* ++ * The jiffies value which is added to now, when there is no timer ++diff --git a/kernel/time/timer.c b/kernel/time/timer.c ++index 9f8e8892e5b0..07892e658cbd 100644 ++--- a/kernel/time/timer.c +++++ b/kernel/time/timer.c ++@@ -927,8 +927,11 @@ static struct timer_base *lock_timer_base(struct timer_list *timer, ++ } ++ } ++ +++#define MOD_TIMER_PENDING_ONLY 0x01 +++#define MOD_TIMER_REDUCE 0x02 +++ ++ static inline int ++-__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) +++__mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options) ++ { ++ struct timer_base *base, *new_base; ++ unsigned int idx = UINT_MAX; ++@@ -948,7 +951,11 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) ++ * larger granularity than you would get from adding a new ++ * timer with this expiry. ++ */ ++- if (timer->expires == expires) +++ long diff = timer->expires - expires; +++ +++ if (!diff) +++ return 1; +++ if (options & MOD_TIMER_REDUCE && diff <= 0) ++ return 1; ++ ++ /* ++@@ -960,6 +967,12 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) ++ base = lock_timer_base(timer, &flags); ++ forward_timer_base(base); ++ +++ if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) && +++ time_before_eq(timer->expires, expires)) { +++ ret = 1; +++ goto out_unlock; +++ } +++ ++ clk = base->clk; ++ idx = calc_wheel_index(expires, clk); ++ ++@@ -969,7 +982,10 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) ++ * subsequent call will exit in the expires check above. ++ */ ++ if (idx == timer_get_idx(timer)) { ++- timer->expires = expires; +++ if (!(options & MOD_TIMER_REDUCE)) +++ timer->expires = expires; +++ else if (time_after(timer->expires, expires)) +++ timer->expires = expires; ++ ret = 1; ++ goto out_unlock; ++ } ++@@ -979,7 +995,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) ++ } ++ ++ ret = detach_if_pending(timer, base, false); ++- if (!ret && pending_only) +++ if (!ret && (options & MOD_TIMER_PENDING_ONLY)) ++ goto out_unlock; ++ ++ new_base = get_target_base(base, timer->flags); ++@@ -1040,7 +1056,7 @@ __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only) ++ */ ++ int mod_timer_pending(struct timer_list *timer, unsigned long expires) ++ { ++- return __mod_timer(timer, expires, true); +++ return __mod_timer(timer, expires, MOD_TIMER_PENDING_ONLY); ++ } ++ EXPORT_SYMBOL(mod_timer_pending); ++ ++@@ -1066,10 +1082,25 @@ EXPORT_SYMBOL(mod_timer_pending); ++ */ ++ int mod_timer(struct timer_list *timer, unsigned long expires) ++ { ++- return __mod_timer(timer, expires, false); +++ return __mod_timer(timer, expires, 0); ++ } ++ EXPORT_SYMBOL(mod_timer); ++ +++/** +++ * timer_reduce - Modify a timer's timeout if it would reduce the timeout +++ * @timer: The timer to be modified +++ * @expires: New timeout in jiffies +++ * +++ * timer_reduce() is very similar to mod_timer(), except that it will only +++ * modify a running timer if that would reduce the expiration time (it will +++ * start a timer that isn't running). +++ */ +++int timer_reduce(struct timer_list *timer, unsigned long expires) +++{ +++ return __mod_timer(timer, expires, MOD_TIMER_REDUCE); +++} +++EXPORT_SYMBOL(timer_reduce); +++ ++ /** ++ * add_timer - start a timer ++ * @timer: the timer to be added ++@@ -1742,7 +1773,7 @@ signed long __sched schedule_timeout(signed long timeout) ++ expire = timeout + jiffies; ++ ++ setup_timer_on_stack(&timer, process_timeout, (unsigned long)current); ++- __mod_timer(&timer, expire, false); +++ __mod_timer(&timer, expire, 0); ++ schedule(); ++ del_singleshot_timer_sync(&timer); ++ ++-- ++2.27.0 ++ +diff --git a/target/linux/generic/config-4.14 b/target/linux/generic/config-4.14 +index d54ede9efda0a3ffd84e9a0c49dc410a01737d82..15b50523bf55d9a77fc1655ec6ba6ffde6d93a3e 100644 +--- a/target/linux/generic/config-4.14 ++++ b/target/linux/generic/config-4.14 +@@ -628,6 +628,7 @@ CONFIG_BRIDGE=y + # CONFIG_BRIDGE_EBT_T_NAT is not set + # CONFIG_BRIDGE_EBT_VLAN is not set + CONFIG_BRIDGE_IGMP_SNOOPING=y ++CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS=y + # CONFIG_BRIDGE_NETFILTER is not set + # CONFIG_BRIDGE_NF_EBTABLES is not set + CONFIG_BRIDGE_VLAN_FILTERING=y +diff --git a/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch b/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch +new file mode 100644 +index 0000000000000000000000000000000000000000..d02a4bc64deafa7fe2e17e9d17b259cea999c13b +--- /dev/null ++++ b/target/linux/generic/pending-4.14/151-bridge-Implement-MLD-Querier-wake-up-calls-Android-b.patch +@@ -0,0 +1,632 @@ ++From 95479b1485191f0e7b95f9b37df0b39c751a5b56 Mon Sep 17 00:00:00 2001 ++From: =?UTF-8?q?Linus=20L=C3=BCssing?= ++Date: Mon, 29 Jun 2020 19:04:05 +0200 ++Subject: [PATCH] bridge: Implement MLD Querier wake-up calls / Android bug ++ workaround ++MIME-Version: 1.0 ++Content-Type: text/plain; charset=UTF-8 ++Content-Transfer-Encoding: 8bit ++ ++Implement a configurable MLD Querier wake-up calls "feature" which ++works around a widely spread Android bug in connection with IGMP/MLD ++snooping. ++ ++Currently there are mobile devices (e.g. Android) which are not able ++to receive and respond to MLD Queries reliably because the Wifi driver ++filters a lot of ICMPv6 when the device is asleep - including ++MLD. This in turn breaks IPv6 communication when MLD Snooping is ++enabled. However there is one ICMPv6 type which is allowed to pass and ++which can be used to wake up the mobile device: ICMPv6 Echo Requests. ++ ++If this bridge is the selected MLD Querier then setting ++"multicast_wakeupcall" to a number n greater than 0 will send n ++ICMPv6 Echo Requests to each host behind this port to wake ++them up with each MLD Query. Upon receiving a matching ICMPv6 Echo ++Reply an MLD Query with a unicast ethernet destination will be sent ++to the specific host(s). ++ ++Link: https://issuetracker.google.com/issues/149630944 ++Link: https://github.com/freifunk-gluon/gluon/issues/1832 ++ ++Signed-off-by: Linus Lüssing ++--- ++ include/linux/if_bridge.h | 1 + ++ include/uapi/linux/if_link.h | 1 + ++ net/bridge/Kconfig | 26 ++++ ++ net/bridge/br_fdb.c | 10 ++ ++ net/bridge/br_input.c | 4 +- ++ net/bridge/br_multicast.c | 288 ++++++++++++++++++++++++++++++++++- ++ net/bridge/br_netlink.c | 19 +++ ++ net/bridge/br_private.h | 19 +++ ++ net/bridge/br_sysfs_if.c | 18 +++ ++ 9 files changed, 378 insertions(+), 8 deletions(-) ++ ++diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h ++index 78d2ed50c524..44fa4235c8e8 100644 ++--- a/include/linux/if_bridge.h +++++ b/include/linux/if_bridge.h ++@@ -50,6 +50,7 @@ struct br_ip_list { ++ #define BR_VLAN_TUNNEL BIT(13) ++ #define BR_BCAST_FLOOD BIT(14) ++ #define BR_ISOLATED BIT(16) +++#define BR_MULTICAST_WAKEUPCALL BIT(17) ++ ++ #define BR_DEFAULT_AGEING_TIME (300 * HZ) ++ ++diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h ++index 45529dba6554..d019754d6870 100644 ++--- a/include/uapi/linux/if_link.h +++++ b/include/uapi/linux/if_link.h ++@@ -328,6 +328,7 @@ enum { ++ IFLA_BRPORT_BCAST_FLOOD, ++ IFLA_BRPORT_NEIGH_SUPPRESS, ++ IFLA_BRPORT_ISOLATED, +++ IFLA_BRPORT_MCAST_WAKEUPCALL, ++ __IFLA_BRPORT_MAX ++ }; ++ #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) ++diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig ++index aa0d3b2f1bb7..66495bc07dfc 100644 ++--- a/net/bridge/Kconfig +++++ b/net/bridge/Kconfig ++@@ -47,6 +47,32 @@ config BRIDGE_IGMP_SNOOPING ++ ++ If unsure, say Y. ++ +++config BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ bool "MLD Querier wake-up calls" +++ depends on BRIDGE_IGMP_SNOOPING +++ depends on IPV6 +++ help +++ If you say Y here, then the MLD Snooping Querier will be built +++ with a per bridge port wake-up call "feature"/workaround. +++ +++ Currently there are mobile devices (e.g. Android) which are not able +++ to receive and respond to MLD Queries reliably because the Wifi driver +++ filters a lot of ICMPv6 when the device is asleep - including MLD. +++ This in turn breaks IPv6 communication when MLD Snooping is enabled. +++ However there is one ICMPv6 type which is allowed to pass and +++ which can be used to wake up the mobile device: ICMPv6 Echo Requests. +++ +++ If this bridge is the selected MLD Querier then setting +++ "multicast_wakeupcall" to a number n greater than 0 will send n +++ ICMPv6 Echo Requests to each host behind this port to wake them up +++ with each MLD Query. Upon receiving a matching ICMPv6 Echo Reply +++ an MLD Query with a unicast ethernet destination will be sent to the +++ specific host(s). +++ +++ Say N to exclude this support and reduce the binary size. +++ +++ If unsure, say N. +++ ++ config BRIDGE_VLAN_FILTERING ++ bool "VLAN filtering" ++ depends on BRIDGE ++diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c ++index 4ea5c8bbe286..180555d2f21b 100644 ++--- a/net/bridge/br_fdb.c +++++ b/net/bridge/br_fdb.c ++@@ -81,6 +81,10 @@ static void fdb_rcu_free(struct rcu_head *head) ++ { ++ struct net_bridge_fdb_entry *ent ++ = container_of(head, struct net_bridge_fdb_entry, rcu); +++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ del_timer_sync(&ent->wakeupcall_timer); +++#endif ++ kmem_cache_free(br_fdb_cache, ent); ++ } ++ ++@@ -498,6 +502,12 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head, ++ fdb->added_by_external_learn = 0; ++ fdb->offloaded = 0; ++ fdb->updated = fdb->used = jiffies; +++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ timer_setup(&fdb->wakeupcall_timer, +++ br_multicast_send_wakeupcall, 0); +++#endif +++ ++ hlist_add_head_rcu(&fdb->hlist, head); ++ } ++ return fdb; ++diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c ++index 66175565efa6..cc8f04408981 100644 ++--- a/net/bridge/br_input.c +++++ b/net/bridge/br_input.c ++@@ -200,8 +200,10 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb ++ if (dst) { ++ unsigned long now = jiffies; ++ ++- if (dst->is_local) +++ if (dst->is_local) { +++ br_multicast_wakeupcall_rcv(br, p, skb, vid); ++ return br_pass_frame_up(skb); +++ } ++ ++ if (now != dst->used) ++ dst->used = now; ++diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c ++index 779af4efea27..6d8d1cf2ddc6 100644 ++--- a/net/bridge/br_multicast.c +++++ b/net/bridge/br_multicast.c ++@@ -463,10 +463,11 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, ++ #if IS_ENABLED(CONFIG_IPV6) ++ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, ++ const struct in6_addr *grp, ++- u8 *igmp_type) +++ u8 *igmp_type, +++ bool delay) ++ { +++ unsigned long interval = 0; ++ struct mld2_query *mld2q; ++- unsigned long interval; ++ struct ipv6hdr *ip6h; ++ struct mld_msg *mldq; ++ size_t mld_hdr_size; ++@@ -525,9 +526,13 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, ++ ++ /* ICMPv6 */ ++ skb_set_transport_header(skb, skb->len); ++- interval = ipv6_addr_any(grp) ? ++- br->multicast_query_response_interval : ++- br->multicast_last_member_interval; +++ if (delay) { +++ interval = ipv6_addr_any(grp) ? +++ br->multicast_query_response_interval : +++ br->multicast_last_member_interval; +++ interval = jiffies_to_msecs(interval); +++ } +++ ++ *igmp_type = ICMPV6_MGM_QUERY; ++ switch (br->multicast_mld_version) { ++ case 1: ++@@ -535,7 +540,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, ++ mldq->mld_type = ICMPV6_MGM_QUERY; ++ mldq->mld_code = 0; ++ mldq->mld_cksum = 0; ++- mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval)); +++ mldq->mld_maxdelay = htons((u16)interval); ++ mldq->mld_reserved = 0; ++ mldq->mld_mca = *grp; ++ mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, ++@@ -584,7 +589,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br, ++ #if IS_ENABLED(CONFIG_IPV6) ++ case htons(ETH_P_IPV6): ++ return br_ip6_multicast_alloc_query(br, &addr->u.ip6, ++- igmp_type); +++ igmp_type, true); ++ #endif ++ } ++ return NULL; ++@@ -906,6 +911,172 @@ static void br_multicast_select_own_querier(struct net_bridge *br, ++ #endif ++ } ++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ +++#define BR_MC_WAKEUP_ID htons(0xEC6B) /* random identifier */ +++#define BR_MC_ETH_ZERO { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +++#define BR_MC_IN6_ZERO \ +++{ \ +++ .s6_addr32[0] = 0, .s6_addr32[1] = 0, \ +++ .s6_addr32[2] = 0, .s6_addr32[3] = 0, \ +++} +++ +++#define BR_MC_IN6_FE80 \ +++{ \ +++ .s6_addr32[0] = htonl(0xfe800000), \ +++ .s6_addr32[1] = 0, \ +++ .s6_addr32[2] = htonl(0x000000ff), \ +++ .s6_addr32[3] = htonl(0xfe000000), \ +++} +++ +++#define BR_MC_ECHO_LEN sizeof(pkt->echohdr) +++ +++static struct sk_buff *br_multicast_alloc_wakeupcall(struct net_bridge *br, +++ struct net_bridge_port *port, +++ u8 *eth_dst) +++{ +++ struct in6_addr ip6_src, ip6_dst = BR_MC_IN6_FE80; +++ struct sk_buff *skb; +++ __wsum csum_part; +++ __sum16 csum; +++ +++ struct wakeupcall_pkt { +++ struct ethhdr ethhdr; +++ struct ipv6hdr ip6hdr; +++ struct icmp6hdr echohdr; +++ } __packed; +++ +++ struct wakeupcall_pkt *pkt; +++ +++ static const struct wakeupcall_pkt __pkt_template = { +++ .ethhdr = { +++ .h_dest = BR_MC_ETH_ZERO, // update +++ .h_source = BR_MC_ETH_ZERO, // update +++ .h_proto = htons(ETH_P_IPV6), +++ }, +++ .ip6hdr = { +++ .priority = 0, +++ .version = 0x6, +++ .flow_lbl = { 0x00, 0x00, 0x00 }, +++ .payload_len = htons(BR_MC_ECHO_LEN), +++ .nexthdr = IPPROTO_ICMPV6, +++ .hop_limit = 1, +++ .saddr = BR_MC_IN6_ZERO, // update +++ .daddr = BR_MC_IN6_ZERO, // update +++ }, +++ .echohdr = { +++ .icmp6_type = ICMPV6_ECHO_REQUEST, +++ .icmp6_code = 0, +++ .icmp6_cksum = 0, // update +++ .icmp6_dataun.u_echo = { +++ .identifier = BR_MC_WAKEUP_ID, +++ .sequence = 0, +++ }, +++ }, +++ }; +++ +++ memcpy(&ip6_dst.s6_addr32[2], ð_dst[0], ETH_ALEN / 2); +++ memcpy(&ip6_dst.s6_addr[13], ð_dst[3], ETH_ALEN / 2); +++ ip6_dst.s6_addr[8] ^= 0x02; +++ if (ipv6_dev_get_saddr(dev_net(br->dev), br->dev, &ip6_dst, 0, +++ &ip6_src)) +++ return NULL; +++ +++ skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*pkt)); +++ if (!skb) +++ return NULL; +++ +++ skb->protocol = htons(ETH_P_IPV6); +++ skb->dev = port->dev; +++ +++ pkt = (struct wakeupcall_pkt *)skb->data; +++ *pkt = __pkt_template; +++ +++ ether_addr_copy(pkt->ethhdr.h_source, br->dev->dev_addr); +++ ether_addr_copy(pkt->ethhdr.h_dest, eth_dst); +++ +++ pkt->ip6hdr.saddr = ip6_src; +++ pkt->ip6hdr.daddr = ip6_dst; +++ +++ csum_part = csum_partial(&pkt->echohdr, sizeof(pkt->echohdr), 0); +++ csum = csum_ipv6_magic(&ip6_src, &ip6_dst, sizeof(pkt->echohdr), +++ IPPROTO_ICMPV6, csum_part); +++ pkt->echohdr.icmp6_cksum = csum; +++ +++ skb_reset_mac_header(skb); +++ skb_set_network_header(skb, offsetof(struct wakeupcall_pkt, ip6hdr)); +++ skb_set_transport_header(skb, offsetof(struct wakeupcall_pkt, echohdr)); +++ skb_put(skb, sizeof(*pkt)); +++ __skb_pull(skb, sizeof(pkt->ethhdr)); +++ +++ return skb; +++} +++ +++void br_multicast_send_wakeupcall(struct timer_list *t) +++{ +++ struct net_bridge_fdb_entry *fdb = from_timer(fdb, t, wakeupcall_timer); +++ struct net_bridge_port *port = fdb->dst; +++ struct net_bridge *br = port->br; +++ struct sk_buff *skb, *skb0; +++ int i; +++ +++ skb0 = br_multicast_alloc_wakeupcall(br, port, fdb->addr.addr); +++ if (!skb0) +++ return; +++ +++ for (i = port->wakeupcall_num_rings; i > 0; i--) { +++ if (i > 1) { +++ skb = skb_clone(skb0, GFP_ATOMIC); +++ if (!skb) { +++ kfree_skb(skb0); +++ break; +++ } +++ } else { +++ skb = skb0; +++ } +++ +++ NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, +++ dev_net(port->dev), NULL, skb, NULL, skb->dev, +++ br_dev_queue_push_xmit); +++ } +++} +++ +++static void br_multicast_schedule_wakeupcalls(struct net_bridge *br, +++ struct net_bridge_port *port, +++ const struct in6_addr *group) +++{ +++ struct net_bridge_fdb_entry *fdb; +++ unsigned long delay; +++ int i; +++ +++ rcu_read_lock(); +++ for (i = 0; i < BR_HASH_SIZE; i++) { +++ hlist_for_each_entry_rcu(fdb, &br->hash[i], hlist) { +++ if (!fdb->dst || fdb->dst->dev != port->dev) +++ continue; +++ +++ /* Wake-up calls to VLANs unsupported for now */ +++ if (fdb->vlan_id) +++ continue; +++ +++ /* Spread the ICMPv6 Echo Requests to avoid congestion. +++ * We then won't use a max response delay for the +++ * queries later, as that would be redundant. Spread +++ * randomly by a little less than max response delay to +++ * anticipate the extra round trip. +++ */ +++ delay = ipv6_addr_any(group) ? +++ br->multicast_query_response_interval : +++ br->multicast_last_member_interval; +++ delay = prandom_u32() % (3 * delay / 4); +++ +++ timer_reduce(&fdb->wakeupcall_timer, jiffies + delay); +++ } +++ } +++ rcu_read_unlock(); +++} +++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */ +++ ++ static void __br_multicast_send_query(struct net_bridge *br, ++ struct net_bridge_port *port, ++ struct br_ip *ip) ++@@ -924,6 +1095,13 @@ static void __br_multicast_send_query(struct net_bridge *br, ++ NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, ++ dev_net(port->dev), NULL, skb, NULL, skb->dev, ++ br_dev_queue_push_xmit); +++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ if (port->wakeupcall_num_rings && +++ ip->proto == htons(ETH_P_IPV6)) +++ br_multicast_schedule_wakeupcalls(br, port, +++ &ip->u.ip6); +++#endif ++ } else { ++ br_multicast_select_own_querier(br, ip, skb); ++ br_multicast_count(br, port, skb, igmp_type, ++@@ -1952,6 +2130,93 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, ++ return ret; ++ } ++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ +++static bool br_multicast_wakeupcall_check(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb, u16 vid) +++{ +++ struct ethhdr *eth = eth_hdr(skb); +++ const struct ipv6hdr *ip6h; +++ unsigned int offset, len; +++ struct icmp6hdr *icmp6h; +++ +++ /* Wake-up calls to VLANs unsupported for now */ +++ if (!port->wakeupcall_num_rings || vid || +++ eth->h_proto != htons(ETH_P_IPV6)) +++ return false; +++ +++ if (!ether_addr_equal(eth->h_dest, br->dev->dev_addr) || +++ is_multicast_ether_addr(eth->h_source) || +++ is_zero_ether_addr(eth->h_source)) +++ return false; +++ +++ offset = skb_network_offset(skb) + sizeof(*ip6h); +++ if (!pskb_may_pull(skb, offset)) +++ return false; +++ +++ ip6h = ipv6_hdr(skb); +++ +++ if (ip6h->version != 6) +++ return false; +++ +++ len = offset + ntohs(ip6h->payload_len); +++ if (skb->len < len || len <= offset) +++ return false; +++ +++ if (ip6h->nexthdr != IPPROTO_ICMPV6) +++ return false; +++ +++ skb_set_transport_header(skb, offset); +++ +++ if (ipv6_mc_check_icmpv6(skb) < 0) +++ return false; +++ +++ icmp6h = (struct icmp6hdr *)skb_transport_header(skb); +++ if (icmp6h->icmp6_type != ICMPV6_ECHO_REPLY || +++ icmp6h->icmp6_dataun.u_echo.identifier != BR_MC_WAKEUP_ID) +++ return false; +++ +++ return true; +++} +++ +++static void br_multicast_wakeupcall_send_mldq(struct net_bridge *br, +++ struct net_bridge_port *port, +++ const u8 *eth_dst) +++{ +++ const struct in6_addr grp = BR_MC_IN6_ZERO; +++ struct sk_buff *skb; +++ u8 igmp_type; +++ +++ /* we might have been triggered by multicast-address-specific query +++ * but reply with a general MLD query for now to keep things simple +++ */ +++ skb = br_ip6_multicast_alloc_query(br, &grp, &igmp_type, false); +++ if (!skb) +++ return; +++ +++ skb->dev = port->dev; +++ ether_addr_copy(eth_hdr(skb)->h_dest, eth_dst); +++ +++ br_multicast_count(br, port, skb, igmp_type, +++ BR_MCAST_DIR_TX); +++ NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, +++ dev_net(port->dev), NULL, skb, NULL, skb->dev, +++ br_dev_queue_push_xmit); +++} +++ +++void br_multicast_wakeupcall_rcv(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb, u16 vid) +++{ +++ if (!br_multicast_wakeupcall_check(br, port, skb, vid)) +++ return; +++ +++ br_multicast_wakeupcall_send_mldq(br, port, eth_hdr(skb)->h_source); +++} +++ +++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */ +++ ++ static void br_multicast_query_expired(struct net_bridge *br, ++ struct bridge_mcast_own_query *query, ++ struct bridge_mcast_querier *querier) ++@@ -2239,6 +2504,15 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val) ++ return err; ++ } ++ +++int br_multicast_set_wakeupcall(struct net_bridge_port *p, unsigned long val) +++{ +++ if (val > U8_MAX) +++ return -EINVAL; +++ +++ p->wakeupcall_num_rings = val; +++ return 0; +++} +++ ++ static void br_multicast_start_querier(struct net_bridge *br, ++ struct bridge_mcast_own_query *query) ++ { ++diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c ++index 2bf371d9c6d9..85f44a15a97a 100644 ++--- a/net/bridge/br_netlink.c +++++ b/net/bridge/br_netlink.c ++@@ -152,6 +152,9 @@ static inline size_t br_port_info_size(void) ++ + nla_total_size_64bit(sizeof(u64)) /* IFLA_BRPORT_HOLD_TIMER */ ++ #ifdef CONFIG_BRIDGE_IGMP_SNOOPING ++ + nla_total_size(sizeof(u8)) /* IFLA_BRPORT_MULTICAST_ROUTER */ +++#endif +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ + nla_total_size(sizeof(u8)) /* IFLA_BRPORT_MCAST_WAKEUPCALL */ ++ #endif ++ + 0; ++ } ++@@ -231,6 +234,11 @@ static int br_port_fill_attrs(struct sk_buff *skb, ++ p->multicast_router)) ++ return -EMSGSIZE; ++ #endif +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ if (nla_put_u8(skb, IFLA_BRPORT_MCAST_WAKEUPCALL, +++ p->wakeupcall_num_rings)) +++ return -EMSGSIZE; +++#endif ++ ++ return 0; ++ } ++@@ -637,6 +645,7 @@ static const struct nla_policy br_port_policy[IFLA_BRPORT_MAX + 1] = { ++ [IFLA_BRPORT_PROXYARP_WIFI] = { .type = NLA_U8 }, ++ [IFLA_BRPORT_MULTICAST_ROUTER] = { .type = NLA_U8 }, ++ [IFLA_BRPORT_MCAST_TO_UCAST] = { .type = NLA_U8 }, +++ [IFLA_BRPORT_MCAST_WAKEUPCALL] = { .type = NLA_U8 }, ++ [IFLA_BRPORT_MCAST_FLOOD] = { .type = NLA_U8 }, ++ [IFLA_BRPORT_BCAST_FLOOD] = { .type = NLA_U8 }, ++ [IFLA_BRPORT_ISOLATED] = { .type = NLA_U8 }, ++@@ -781,6 +790,16 @@ static int br_setport(struct net_bridge_port *p, struct nlattr *tb[]) ++ if (err) ++ return err; ++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ if (tb[IFLA_BRPORT_MCAST_WAKEUPCALL]) { +++ u8 wakeupcall = nla_get_u8(tb[IFLA_BRPORT_MCAST_WAKEUPCALL]); +++ +++ err = br_multicast_set_wakeupcall(p, wakeupcall); +++ if (err) +++ return err; +++ } +++#endif +++ ++ br_port_flags_change(p, old_flags ^ p->flags); ++ return 0; ++ } ++diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h ++index 32f0d61c1803..bbf7513c1d16 100644 ++--- a/net/bridge/br_private.h +++++ b/net/bridge/br_private.h ++@@ -178,6 +178,10 @@ struct net_bridge_fdb_entry { ++ unsigned long used; ++ ++ struct rcu_head rcu; +++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ struct timer_list wakeupcall_timer; +++#endif ++ }; ++ ++ #define MDB_PG_FLAGS_PERMANENT BIT(0) ++@@ -256,6 +260,7 @@ struct net_bridge_port { ++ struct timer_list multicast_router_timer; ++ struct hlist_head mglist; ++ struct hlist_node rlist; +++ u8 wakeupcall_num_rings; ++ #endif ++ ++ #ifdef CONFIG_SYSFS ++@@ -790,6 +795,20 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb) ++ } ++ #endif ++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++void br_multicast_wakeupcall_rcv(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb, u16 vid); +++void br_multicast_send_wakeupcall(struct timer_list *t); +++int br_multicast_set_wakeupcall(struct net_bridge_port *p, unsigned long val); +++#else +++static inline void br_multicast_wakeupcall_rcv(struct net_bridge *br, +++ struct net_bridge_port *port, +++ struct sk_buff *skb, u16 vid) +++{ +++} +++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS */ +++ ++ /* br_vlan.c */ ++ #ifdef CONFIG_BRIDGE_VLAN_FILTERING ++ bool br_allowed_ingress(const struct net_bridge *br, ++diff --git a/net/bridge/br_sysfs_if.c b/net/bridge/br_sysfs_if.c ++index b51aefa636b6..e9f1f5ccec3e 100644 ++--- a/net/bridge/br_sysfs_if.c +++++ b/net/bridge/br_sysfs_if.c ++@@ -194,6 +194,21 @@ BRPORT_ATTR_FLAG(multicast_fast_leave, BR_MULTICAST_FAST_LEAVE); ++ BRPORT_ATTR_FLAG(multicast_to_unicast, BR_MULTICAST_TO_UNICAST); ++ #endif ++ +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++static ssize_t show_multicast_wakeupcall(struct net_bridge_port *p, char *buf) +++{ +++ return sprintf(buf, "%d\n", p->wakeupcall_num_rings); +++} +++ +++static int store_multicast_wakeupcall(struct net_bridge_port *p, +++ unsigned long v) +++{ +++ return br_multicast_set_wakeupcall(p, v); +++} +++static BRPORT_ATTR(multicast_wakeupcall, 0644, show_multicast_wakeupcall, +++ store_multicast_wakeupcall); +++#endif +++ ++ static const struct brport_attribute *brport_attrs[] = { ++ &brport_attr_path_cost, ++ &brport_attr_priority, ++@@ -219,6 +234,9 @@ static const struct brport_attribute *brport_attrs[] = { ++ &brport_attr_multicast_router, ++ &brport_attr_multicast_fast_leave, ++ &brport_attr_multicast_to_unicast, +++#endif +++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING_WAKEUPCALLS +++ &brport_attr_multicast_wakeupcall, ++ #endif ++ &brport_attr_proxyarp, ++ &brport_attr_proxyarp_wifi, ++-- ++2.28.0.rc1 ++ diff --git a/patches/packages/packages/0001-fastd-update-to-v19.patch b/patches/packages/packages/0001-fastd-update-to-v19.patch index b96ef7e6..b478e8e1 100644 --- a/patches/packages/packages/0001-fastd-update-to-v19.patch +++ b/patches/packages/packages/0001-fastd-update-to-v19.patch @@ -3,6 +3,7 @@ Date: Fri, 22 May 2020 21:09:21 +0200 Subject: fastd: update to v19 Signed-off-by: Matthias Schiffer +(cherry picked from commit 598d4baa02d312d2bc68739a1f2f1f2cb3c91051) diff --git a/net/fastd/Config.in b/net/fastd/Config.in index 3350eb3099a26c870d70373c0712a8b59881ee5c..e6440075e561093c86543943cb982d010a4ef0e0 100644 diff --git a/patches/packages/packages/0002-fastd-update-to-v20.patch b/patches/packages/packages/0002-fastd-update-to-v20.patch new file mode 100644 index 00000000..d50808a5 --- /dev/null +++ b/patches/packages/packages/0002-fastd-update-to-v20.patch @@ -0,0 +1,218 @@ +From: Matthias Schiffer +Date: Sat, 10 Oct 2020 19:02:24 +0200 +Subject: fastd: update to v20 + +Signed-off-by: Matthias Schiffer +(cherry picked from commit a916211b670f1fa4aefc65f1d27f9afbf22e93a8) + +diff --git a/net/fastd/Config.in b/net/fastd/Config.in +index e6440075e561093c86543943cb982d010a4ef0e0..8302f7ee4dac874b1303ebeeb836551ef202c261 100644 +--- a/net/fastd/Config.in ++++ b/net/fastd/Config.in +@@ -69,7 +69,6 @@ config FASTD_WITH_CAPABILITIES + depends on PACKAGE_fastd + default n + +- + config FASTD_WITH_CMDLINE_USER + bool "Include support for setting user/group related options on the command line" + depends on PACKAGE_fastd +@@ -91,7 +90,7 @@ config FASTD_WITH_CMDLINE_COMMANDS + default n + + config FASTD_WITH_DYNAMIC_PEERS +- bool "Include support for on-verify handlers" ++ bool "Include support for dynamic peers (using on-verify handlers)" + depends on PACKAGE_fastd + default n + +@@ -100,5 +99,4 @@ config FASTD_WITH_STATUS_SOCKET + depends on PACKAGE_fastd + default y + +- + endmenu +diff --git a/net/fastd/Makefile b/net/fastd/Makefile +index 8eabc34db6f3b906ddb1b5df5c232309e85d2ffb..4645b290d1e49330f79b035d2b66d2b6d1c17604 100644 +--- a/net/fastd/Makefile ++++ b/net/fastd/Makefile +@@ -8,13 +8,12 @@ + include $(TOPDIR)/rules.mk + + PKG_NAME:=fastd +-PKG_VERSION:=19 +-PKG_RELEASE:=1 ++PKG_VERSION:=20 + + PKG_MAINTAINER:=Matthias Schiffer + PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz + PKG_SOURCE_URL:=https://github.com/NeoRaider/fastd/releases/download/v$(PKG_VERSION) +-PKG_HASH:=6054608e2103b634c9d19ecd1ae058d4ec694747047130719db180578729783a ++PKG_HASH:=56cab8639218d63237d9a5508fb2bf6fa637374d53fb7fa55b7e92e4d4dfeb00 + + PKG_LICENSE:=BSD-2-Clause + PKG_LICENSE_FILES:=COPYRIGHT +@@ -41,16 +40,16 @@ PKG_CONFIG_DEPENDS:=\ + CONFIG_FASTD_WITH_STATUS_SOCKET + + +-PKG_BUILD_DEPENDS:=nacl ++PKG_BUILD_DEPENDS:=meson/host nacl + PKG_BUILD_PARALLEL:=1 + + include $(INCLUDE_DIR)/package.mk +-include $(INCLUDE_DIR)/cmake.mk ++include ../../devel/meson/meson.mk + + define Package/fastd + SECTION:=net + CATEGORY:=Network +- DEPENDS:=+kmod-tun +librt +libpthread +libuecc +FASTD_WITH_STATUS_SOCKET:libjson-c +FASTD_WITH_CAPABILITIES:libcap ++ DEPENDS:=+kmod-tun +libpthread +libuecc +FASTD_WITH_STATUS_SOCKET:libjson-c +FASTD_WITH_CAPABILITIES:libcap + TITLE:=Fast and Secure Tunneling Daemon + URL:=https://github.com/NeoRaider/fastd/ + SUBMENU:=VPN +@@ -60,116 +59,33 @@ define Package/fastd/config + source "$(SOURCE)/Config.in" + endef + +-TARGET_CFLAGS += -ffunction-sections -fdata-sections +-TARGET_LDFLAGS += -Wl,--gc-sections +- +-CMAKE_OPTIONS += \ +- -DCMAKE_BUILD_TYPE:STRING=MINSIZEREL \ +- -DWITH_METHOD_CIPHER_TEST:BOOL=FALSE \ +- -DWITH_METHOD_COMPOSED_GMAC:BOOL=FALSE \ +- -DWITH_METHOD_COMPOSED_UMAC:BOOL=FALSE \ +- -DWITH_METHOD_GENERIC_GMAC:BOOL=FALSE \ +- -DWITH_METHOD_GENERIC_POLY1305:BOOL=FALSE \ +- -DWITH_METHOD_GENERIC_UMAC:BOOL=FALSE \ +- -DWITH_METHOD_NULL:BOOL=FALSE \ +- -DWITH_CIPHER_AES128_CTR:BOOL=FALSE \ +- -DWITH_CIPHER_NULL:BOOL=FALSE \ +- -DWITH_CIPHER_SALSA20:BOOL=FALSE \ +- -DWITH_CIPHER_SALSA2012:BOOL=FALSE \ +- -DWITH_MAC_GHASH:BOOL=FALSE \ +- -DWITH_MAC_UHASH:BOOL=FALSE \ +- -DWITH_CAPABILITIES:BOOL=FALSE \ +- -DWITH_CMDLINE_USER:BOOL=FALSE \ +- -DWITH_CMDLINE_LOGGING:BOOL=FALSE \ +- -DWITH_CMDLINE_OPERATION:BOOL=FALSE \ +- -DWITH_CMDLINE_COMMANDS:BOOL=FALSE \ +- -DWITH_DYNAMIC_PEERS:BOOL=FALSE \ +- -DWITH_STATUS_SOCKET:BOOL=FALSE \ +- -DENABLE_SYSTEMD:BOOL=FALSE \ +- -DENABLE_LIBSODIUM:BOOL=FALSE \ +- -DENABLE_LTO:BOOL=TRUE +- +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_CIPHER_TEST),y) +-CMAKE_OPTIONS += -DWITH_METHOD_CIPHER_TEST:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_COMPOSED_GMAC),y) +-CMAKE_OPTIONS += -DWITH_METHOD_COMPOSED_GMAC:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_COMPOSED_UMAC),y) +-CMAKE_OPTIONS += -DWITH_METHOD_COMPOSED_UMAC:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_GENERIC_GMAC),y) +-CMAKE_OPTIONS += -DWITH_METHOD_GENERIC_GMAC:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_GENERIC_POLY1305),y) +-CMAKE_OPTIONS += -DWITH_METHOD_GENERIC_POLY1305:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_GENERIC_UMAC),y) +-CMAKE_OPTIONS += -DWITH_METHOD_GENERIC_UMAC:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_METHOD_NULL),y) +-CMAKE_OPTIONS += -DWITH_METHOD_NULL:BOOL=TRUE +-endif +- +- +-ifeq ($(CONFIG_FASTD_ENABLE_CIPHER_NULL),y) +-CMAKE_OPTIONS += -DWITH_CIPHER_NULL:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_CIPHER_SALSA20),y) +-CMAKE_OPTIONS += -DWITH_CIPHER_SALSA20:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_CIPHER_SALSA2012),y) +-CMAKE_OPTIONS += -DWITH_CIPHER_SALSA2012:BOOL=TRUE +-endif +- +- +-ifeq ($(CONFIG_FASTD_ENABLE_MAC_GHASH),y) +-CMAKE_OPTIONS += -DWITH_MAC_GHASH:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_ENABLE_MAC_UHASH),y) +-CMAKE_OPTIONS += -DWITH_MAC_UHASH:BOOL=TRUE +-endif +- +- +-ifeq ($(CONFIG_FASTD_WITH_CAPABILITIES),y) +-CMAKE_OPTIONS += -DWITH_CAPABILITIES:BOOL=TRUE +-endif +- +- +-ifeq ($(CONFIG_FASTD_WITH_CMDLINE_USER),y) +-CMAKE_OPTIONS += -DWITH_CMDLINE_USER:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_WITH_CMDLINE_LOGGING),y) +-CMAKE_OPTIONS += -DWITH_CMDLINE_LOGGING:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_WITH_CMDLINE_OPERATION),y) +-CMAKE_OPTIONS += -DWITH_CMDLINE_OPERATION:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_WITH_CMDLINE_COMMANDS),y) +-CMAKE_OPTIONS += -DWITH_CMDLINE_COMMANDS:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_WITH_DYNAMIC_PEERS),y) +-CMAKE_OPTIONS += -DWITH_DYNAMIC_PEERS:BOOL=TRUE +-endif +- +-ifeq ($(CONFIG_FASTD_WITH_STATUS_SOCKET),y) +-CMAKE_OPTIONS += -DWITH_STATUS_SOCKET:BOOL=TRUE +-endif +- ++feature = $(if $(CONFIG_FASTD_$(1)),enabled,disabled) ++ ++MESON_ARGS += \ ++ -Dcapabilities=$(call feature,WITH_CAPABILITIES) \ ++ -Dcipher_aes128-ctr=disabled \ ++ -Dcipher_null=$(call feature,ENABLE_CIPHER_NULL) \ ++ -Dcipher_salsa20=$(call feature,ENABLE_CIPHER_SALSA20) \ ++ -Dcipher_salsa2012=$(call feature,ENABLE_CIPHER_SALSA2012) \ ++ -Dcmdline_commands=$(call feature,WITH_CMDLINE_COMMANDS) \ ++ -Dcmdline_logging=$(call feature,WITH_CMDLINE_LOGGING) \ ++ -Dcmdline_operation=$(call feature,WITH_CMDLINE_OPERATION) \ ++ -Dcmdline_user=$(call feature,WITH_CMDLINE_USER) \ ++ -Ddynamic_peers=$(call feature,WITH_DYNAMIC_PEERS) \ ++ -Dmac_ghash=$(call feature,ENABLE_MAC_GHASH) \ ++ -Dmac_uhash=$(call feature,ENABLE_MAC_UHASH) \ ++ -Dmethod_cipher-test=$(call feature,ENABLE_METHOD_CIPHER_TEST) \ ++ -Dmethod_composed-gmac=$(call feature,ENABLE_METHOD_COMPOSED_GMAC) \ ++ -Dmethod_composed-umac=$(call feature,ENABLE_METHOD_COMPOSED_UMAC) \ ++ -Dmethod_generic-gmac=$(call feature,ENABLE_METHOD_GENERIC_GMAC) \ ++ -Dmethod_generic-poly1305=$(call feature,ENABLE_METHOD_GENERIC_POLY1305) \ ++ -Dmethod_generic-umac=$(call feature,ENABLE_METHOD_GENERIC_UMAC) \ ++ -Dmethod_null=$(call feature,ENABLE_METHOD_NULL) \ ++ -Dstatus_socket=$(call feature,WITH_STATUS_SOCKET) \ ++ -Dsystemd=disabled \ ++ -Duse_nacl=true \ ++ -Db_lto=true \ ++ -Dprefix=/usr + + define Package/fastd/description + Fast and secure tunneling daemon, which is optimized on small code size and few dependencies diff --git a/patches/packages/routing/0002-batman-adv-compat-remove-ip_mc_check_igmp-ipv6_mc_check_mld.patch b/patches/packages/routing/0002-batman-adv-compat-remove-ip_mc_check_igmp-ipv6_mc_check_mld.patch new file mode 100644 index 00000000..1114be8e --- /dev/null +++ b/patches/packages/routing/0002-batman-adv-compat-remove-ip_mc_check_igmp-ipv6_mc_check_mld.patch @@ -0,0 +1,57 @@ +From: Linus Lüssing +Date: Sun, 5 Jul 2020 04:02:17 +0200 +Subject: batman-adv: compat: remove ip_mc_check_igmp() + ipv6_mc_check_mld() + +The upstream Linux patches which reduced the number of arguments for +these functions from two to one were added to OpenWrt. Therefore compat +code for them is no more needed. + +Signed-off-by: Linus Lüssing + +diff --git a/batman-adv/src/compat-hacks.h b/batman-adv/src/compat-hacks.h +index d8de483a10243c77b2c5f49720a39dedb1404f01..1cbcfcf070c7dd943574612c0d13f886c88ecc25 100644 +--- a/batman-adv/src/compat-hacks.h ++++ b/batman-adv/src/compat-hacks.h +@@ -53,39 +53,12 @@ int ipv6_mc_check_mld(struct sk_buff *skb); + + #elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) + ++#undef ip_mc_check_igmp ++#undef ipv6_mc_check_mld ++ + #include_next + #include_next + +-static inline int batadv_ipv6_mc_check_mld1(struct sk_buff *skb) +-{ +- return ipv6_mc_check_mld(skb, NULL); +-} +- +-static inline int batadv_ipv6_mc_check_mld2(struct sk_buff *skb, +- struct sk_buff **skb_trimmed) +-{ +- return ipv6_mc_check_mld(skb, skb_trimmed); +-} +- +-#define ipv6_mc_check_mld_get(_1, _2, ipv6_mc_check_mld_name, ...) ipv6_mc_check_mld_name +-#define ipv6_mc_check_mld(...) \ +- ipv6_mc_check_mld_get(__VA_ARGS__, batadv_ipv6_mc_check_mld2, batadv_ipv6_mc_check_mld1)(__VA_ARGS__) +- +-static inline int batadv_ip_mc_check_igmp1(struct sk_buff *skb) +-{ +- return ip_mc_check_igmp(skb, NULL); +-} +- +-static inline int batadv_ip_mc_check_igmp2(struct sk_buff *skb, +- struct sk_buff **skb_trimmed) +-{ +- return ip_mc_check_igmp(skb, skb_trimmed); +-} +- +-#define ip_mc_check_igmp_get(_1, _2, ip_mc_check_igmp_name, ...) ip_mc_check_igmp_name +-#define ip_mc_check_igmp(...) \ +- ip_mc_check_igmp_get(__VA_ARGS__, batadv_ip_mc_check_igmp2, batadv_ip_mc_check_igmp1)(__VA_ARGS__) +- + #endif /* < KERNEL_VERSION(4, 2, 0) */ + + #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) diff --git a/scripts/feature_lib.lua b/scripts/feature_lib.lua new file mode 100644 index 00000000..a5197e0a --- /dev/null +++ b/scripts/feature_lib.lua @@ -0,0 +1,78 @@ +local M = {} + +local function to_keys(t) + local ret = {} + for _, v in ipairs(t) do + ret[v] = true + end + return ret +end + +local function collect_keys(t) + local ret = {} + for v in pairs(t) do + table.insert(ret, v) + end + return ret +end + +function M.get_packages(files, features) + local enabled_features = to_keys(features) + local handled_features = {} + local packages = {} + + local funcs = {} + + local function add_pkgs(pkgs) + for _, pkg in ipairs(pkgs or {}) do + packages[pkg] = true + end + end + + function funcs._(feature) + return enabled_features[feature] ~= nil + end + + function funcs.feature(feature, pkgs) + assert( + type(feature) == 'string', + 'Incorrect use of feature(): pass a feature name without _ as first argument') + + if enabled_features[feature] then + handled_features[feature] = true + add_pkgs(pkgs) + end + + end + + function funcs.when(cond, pkgs) + assert( + type(cond) == 'boolean', + 'Incorrect use of when(): pass a locical expression of _-prefixed strings as first argument') + + if cond then + add_pkgs(pkgs) + end + end + + -- Evaluate the feature definition files + for _, file in ipairs(files) do + local f, err = loadfile(file) + if not f then + error('Failed to parse feature definition: ' .. err) + end + setfenv(f, funcs) + f() + end + + -- Handle default packages + for _, feature in ipairs(features) do + if not handled_features[feature] then + packages['gluon-' .. feature] = true + end + end + + return collect_keys(packages) +end + +return M diff --git a/scripts/features.sh b/scripts/features.sh deleted file mode 100755 index 1d7184ee..00000000 --- a/scripts/features.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash --norc - -set -e -shopt -s nullglob - - -nodefault() { - # We define a function instead of a variable, as variables could - # be predefined in the environment (in theory) - eval "gluon_feature_nodefault_$1() { - : - }" -} - -packages() { - : -} - -for f in package/features packages/*/features; do - . "$f" -done - - -# Shell variables can't contain minus signs, so we escape them -# using underscores (and also escape underscores to avoid mapping -# multiple inputs to the same output) -sanitize() { - local v="$1" - v="${v//_/_1}" - v="${v//-/_2}" - echo -n "$v" -} - -vars=() - -for feature in $1; do - if [ "$(type -t "gluon_feature_nodefault_${feature}")" != 'function' ]; then - echo "gluon-${feature}" - fi - - vars+=("$(sanitize "$feature")=1") -done - - -nodefault() { - : -} - -# shellcheck disable=SC2086 -packages() { - local cond="$(sanitize "$1")" - shift - - # We only allow variable names, parentheses and the operators: & | ! - if grep -q '[^A-Za-z0-9_()&|! ]' <<< "$cond"; then - exit 1 - fi - - # Let will return false when the result of the passed expression is 0, - # so we always add 1. This way false is only returned for syntax errors. - local ret="$(env -i "${vars[@]}" bash --norc -ec "let _result_='1+($cond)'; echo -n \"\$_result_\"" 2>/dev/null)" - case "$ret" in - 2) - for pkg in "$@"; do - echo "$pkg" - done - ;; - 1) - ;; - *) - exit 1 - esac -} - -for f in package/features packages/*/features; do - . "$f" -done diff --git a/scripts/generate_manifest.lua b/scripts/generate_manifest.lua index e289412e..efeac489 100755 --- a/scripts/generate_manifest.lua +++ b/scripts/generate_manifest.lua @@ -20,11 +20,8 @@ local function generate_line(model, dir, filename, filesize) end local file256sum = strip(lib.exec_capture {'scripts/sha256sum.sh', dir..'/'..filename}) - local file512sum = strip(lib.exec_capture {'scripts/sha512sum.sh', dir..'/'..filename}) io.stdout:write(string.format('%s %s %s %s %s\n', model, env.GLUON_RELEASE, file256sum, filesize, filename)) - io.stdout:write(string.format('%s %s %s %s\n', model, env.GLUON_RELEASE, file256sum, filename)) - io.stdout:write(string.format('%s %s %s %s\n', model, env.GLUON_RELEASE, file512sum, filename)) end local function generate(image) diff --git a/scripts/module_check.sh b/scripts/module_check.sh new file mode 100755 index 00000000..76e0e56a --- /dev/null +++ b/scripts/module_check.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +. scripts/modules.sh + +GLUONDIR="$(pwd)" + +if [ ! -d "$GLUONDIR/openwrt" ]; then + echo "You don't seem to have obtained the external repositories needed by Gluon; please call \`make update\` first!" + exit 1 +fi + +need_sync=false + +for module in $GLUON_MODULES; do + var=$(echo "$module" | tr '[:lower:]/' '[:upper:]_') + eval 'commit_expected=${'"${var}"'_COMMIT}' + + prefix=invalid + cd "$GLUONDIR/$module" 2>/dev/null && prefix="$(git rev-parse --show-prefix 2>/dev/null)" + if [ "$prefix" ]; then + echo "*** No Git repository found at '$module'." + need_sync=true + continue + fi + + commit_actual="$(git rev-parse heads/base 2>/dev/null)" + if [ -z "$commit_actual" ]; then + echo "*** No base branch found at '$module'." + need_sync=true + continue + fi + + if [ "$commit_expected" != "$commit_actual" ]; then + echo "*** base branch at '$module' did not match module file (expected: ${commit_expected}, actual: ${commit_actual})" + need_sync=true + continue + fi + + # Use git status instead of git diff -q, as the latter doesn't + # check for untracked files + if [ "$(git status --porcelain 2>/dev/null | wc -l)" -ne 0 ]; then + echo "*** Module '$module' has uncommitted changes:" + git status --short + fi +done + +if $need_sync; then + echo + # shellcheck disable=SC2016 + echo 'Run `make update` to sync dependencies.' + echo +fi diff --git a/scripts/sha512sum.sh b/scripts/sha512sum.sh deleted file mode 100755 index cb4b07e4..00000000 --- a/scripts/sha512sum.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -check_command() { - command -v "$1" >/dev/null 2>&1 -} - -if check_command sha512sum; then - ret="$(sha512sum "$@")" -elif check_command shasum; then - ret="$(shasum -a 512 "$@")" -elif check_command cksum; then - ret="$(cksum -q -a sha512 "$@")" -else - echo "$0: no suitable sha512sum implementation was found" >&2 - exit 1 -fi - -[ "$?" -eq 0 ] || exit 1 - -echo "$ret" | awk '{ print $1 }' diff --git a/scripts/target_config_lib.lua b/scripts/target_config_lib.lua index 346a18a7..4b107e2b 100644 --- a/scripts/target_config_lib.lua +++ b/scripts/target_config_lib.lua @@ -1,4 +1,5 @@ local lib = dofile('scripts/target_lib.lua') +local feature_lib = dofile('scripts/feature_lib.lua') local env = lib.env local target = env.GLUON_TARGET @@ -24,6 +25,8 @@ local function split(s) return ret end +local feeds = split(lib.exec_capture_raw('. scripts/modules.sh; echo "$FEEDS"')) + -- Strip leading '-' character local function strip_neg(s) if string.sub(s, 1, 1) == '-' then @@ -49,14 +52,26 @@ local function append_to_list(list, item, keep_neg) return ret end -local function compact_list(list, keep_neg) - local ret = {} - for _, el in ipairs(list) do +local function concat_list(a, b, keep_neg) + local ret = a + for _, el in ipairs(b) do ret = append_to_list(ret, el, keep_neg) end return ret end +local function compact_list(list, keep_neg) + return concat_list({}, list, keep_neg) +end + +local function file_exists(file) + local f = io.open(file) + if not f then + return false + end + f:close() + return true +end local function site_vars(var) return lib.exec_capture_raw(string.format( @@ -75,17 +90,19 @@ local function site_packages(image) return split(site_vars(string.format('$(GLUON_%s_SITE_PACKAGES)', image))) end --- TODO: Rewrite features.sh in Lua local function feature_packages(features) - -- Ugly hack: Lua doesn't give us the return code of a popened - -- command, so we match on a special __ERROR__ marker - local pkgs = lib.exec_capture({'scripts/features.sh', features}, '|| echo __ERROR__') - assert(string.find(pkgs, '__ERROR__') == nil, 'Error while evaluating features') - return pkgs + local files = {'package/features'} + for _, feed in ipairs(feeds) do + local path = string.format('packages/%s/features', feed) + if file_exists(path) then + table.insert(files, path) + end + end + + return feature_lib.get_packages(files, features) end --- This involves running lots of processes to evaluate site.mk, so we --- add a simple cache +-- This involves running a few processes to evaluate site.mk, so we add a simple cache local class_cache = {} local function class_packages(class) if class_cache[class] then @@ -93,12 +110,10 @@ local function class_packages(class) end local features = site_vars(string.format('$(GLUON_FEATURES) $(GLUON_FEATURES_%s)', class)) - features = table.concat(compact_list(split(features), false), ' ') + features = compact_list(split(features), false) local pkgs = feature_packages(features) - pkgs = pkgs .. ' ' .. site_vars(string.format('$(GLUON_SITE_PACKAGES) $(GLUON_SITE_PACKAGES_%s)', class)) - - pkgs = compact_list(split(pkgs)) + pkgs = concat_list(pkgs, split(site_vars(string.format('$(GLUON_SITE_PACKAGES) $(GLUON_SITE_PACKAGES_%s)', class)))) class_cache[class] = pkgs return pkgs @@ -178,9 +193,7 @@ else -- x86 fallback: no devices local target_pkgs = {} local function handle_pkgs(pkgs) - for _, pkg in ipairs(pkgs) do - target_pkgs = append_to_list(target_pkgs, pkg) - end + target_pkgs = concat_list(target_pkgs, pkgs) end -- Just hardcode the class for device-less targets to 'standard' diff --git a/scripts/target_lib.lua b/scripts/target_lib.lua index bb974659..babec324 100644 --- a/scripts/target_lib.lua +++ b/scripts/target_lib.lua @@ -21,7 +21,9 @@ assert(env.GLUON_RELEASE) assert(env.GLUON_DEPRECATED) -M.site_code = assert(assert(dofile('scripts/site_config.lua')('site.conf')).site_code) +M.site_code = assert( + dofile('scripts/site_config.lua')('site.conf').site_code, 'site_code missing in site.conf' +) M.target_packages = {} M.configs = {} M.devices = {} diff --git a/targets/ar71xx-generic b/targets/ar71xx-generic index a4a44e52..df7dbd97 100644 --- a/targets/ar71xx-generic +++ b/targets/ar71xx-generic @@ -126,6 +126,10 @@ device('gl.inet-gl-ar750', 'gl-ar750', { packages = ATH10K_PACKAGES_QCA9887, }) +device('gl.inet-gl-usb150', 'gl-usb150', { + factory = false, +}) + -- Linksys by Cisco @@ -251,18 +255,50 @@ if (env.GLUON_REGION or '') ~= '' then tplink_region_suffix = '-' .. env.GLUON_REGION end -device('tp-link-cpe210-v1.0', 'cpe210-220-v1', { - aliases = {'tp-link-cpe210-v1.1', 'tp-link-cpe220-v1.1'}, +device('tp-link-cpe210-v1', 'cpe210-220-v1', { + aliases = { + 'tp-link-cpe220-v1', + }, + manifest_aliases = { + 'tp-link-cpe210-v1.0', + 'tp-link-cpe210-v1.1', + 'tp-link-cpe220-v1.1', + }, }) -device('tp-link-cpe210-v2.0', 'cpe210-v2') -device('tp-link-cpe210-v3.0', 'cpe210-v3') - -device('tp-link-cpe510-v1.0', 'cpe510-520-v1', { - aliases = {'tp-link-cpe510-v1.1', 'tp-link-cpe520-v1.1'}, +device('tp-link-cpe210-v2', 'cpe210-v2', { + manifest_aliases = { + 'tp-link-cpe210-v2.0', + }, +}) +device('tp-link-cpe210-v3', 'cpe210-v3', { + manifest_aliases = { + 'tp-link-cpe210-v3.0', + 'tp-link-cpe210-v3.1', + 'tp-link-cpe210-v3.20', + }, }) -device('tp-link-wbs210-v1.20', 'wbs210-v1') -device('tp-link-wbs510-v1.20', 'wbs510-v1') +device('tp-link-cpe510-v1', 'cpe510-520-v1', { + aliases = { + 'tp-link-cpe520-v1', + }, + manifest_aliases = { + 'tp-link-cpe510-v1.0', + 'tp-link-cpe510-v1.1', + 'tp-link-cpe520-v1.1', + }, +}) + +device('tp-link-wbs210-v1', 'wbs210-v1', { + manifest_aliases = { + 'tp-link-wbs210-v1.20', + }, +}) +device('tp-link-wbs510-v1', 'wbs510-v1', { + manifest_aliases = { + 'tp-link-wbs510-v1.20', + }, +}) device('tp-link-tl-wr710n-v1', 'tl-wr710n-v1', { class = 'tiny', -- 32M ath9k @@ -391,10 +427,8 @@ device('ubiquiti-bullet-m', 'ubnt-bullet-m', { }) device('ubiquiti-rocket-m', 'ubnt-rocket-m', { - aliases = { - 'ubiquiti-rocket-m2', - 'ubiquiti-rocket-m5', - }, + aliases = {'ubiquiti-rocket-m2'}, + manifest_aliases = {'ubiquiti-rocket-m5'}, }) device('ubiquiti-nanostation-m', 'ubnt-nano-m', { diff --git a/targets/generic b/targets/generic index 89aba6f2..8b1d484f 100644 --- a/targets/generic +++ b/targets/generic @@ -3,7 +3,9 @@ assert(env.GLUON_LANGS) config('GLUON_SITEDIR', env.GLUON_SITEDIR) config('GLUON_RELEASE', env.GLUON_RELEASE) -try_config('GLUON_BRANCH', env.GLUON_BRANCH or '') + +try_config('GLUON_AUTOUPDATER_BRANCH', env.GLUON_AUTOUPDATER_BRANCH) +try_config('GLUON_AUTOUPDATER_ENABLED', istrue(env.GLUON_AUTOUPDATER_ENABLED)) for lang in string.gmatch(env.GLUON_LANGS, '%S+') do try_config('GLUON_WEB_LANG_' .. lang, true) diff --git a/targets/ipq40xx-generic b/targets/ipq40xx-generic index 0dca0426..b533ed2d 100644 --- a/targets/ipq40xx-generic +++ b/targets/ipq40xx-generic @@ -38,6 +38,10 @@ device('avm-fritz-box-4040', 'avm_fritzbox-4040', { }, }) +device('avm-fritz-box-7530', 'avm_fritzbox-7530', { + factory = false, +}) + device('avm-fritz-repeater-1200', 'avm_fritzrepeater-1200', { factory = false, }) diff --git a/targets/lantiq-xrx200 b/targets/lantiq-xrx200 index acd1c6fa..dd871e95 100644 --- a/targets/lantiq-xrx200 +++ b/targets/lantiq-xrx200 @@ -1,3 +1,17 @@ +packages { + '-ltq-vdsl-vr9-vectoring-fw-installer', + '-kmod-ltq-vdsl-vr9-mei', + '-kmod-ltq-vdsl-vr9', + '-kmod-ltq-atm-vr9', + '-kmod-ltq-ptm-vr9', + '-kmod-ltq-deu-vr9', + '-ltq-vdsl-app', + '-dsl-vrx200-firmware-xdsl-a', + '-dsl-vrx200-firmware-xdsl-b-patch', + '-ppp-mod-pppoa', +} + + device('avm-fritz-box-3370-rev-2-hynix-nand', 'avm_fritz3370-rev2-hynix', { factory = false, extra_images = { diff --git a/targets/ramips-mt7620 b/targets/ramips-mt7620 index ff2e4529..9adf27ee 100644 --- a/targets/ramips-mt7620 +++ b/targets/ramips-mt7620 @@ -20,6 +20,15 @@ device('gl-mt750', 'gl-mt750', { factory = false, }) +-- Netgear + +device('netgear-ex3700', 'ex3700-ex3800', { + aliases = { + 'netgear-ex3800', + 'netgear-ex3700-ex3800', + }, + factory_ext = '.chk', +}) -- Nexx