Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 89 additions & 31 deletions package/gargoyle/files/usr/lib/gargoyle/known_devices.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,32 @@
# option name 'Johns-Laptop'
# option mac 'AA:BB:CC:DD:EE:FF'
# option ip '192.168.1.100' # optional static IP
# option group 'family' # optional group membership
# list group 'family' # 0+ group memberships (a plain
# list group 'streaming' # `option group` scalar -- the
# # pre-multi-group shape -- still
# # reads back fine: `uci show`
# # renders a single-value list
# # identically to a scalar option,
# # one line either way, so no
# # migration is needed for old
# # devices with exactly one group.
#
# Groups are identified purely by their name string — there is no separate
# 'config group' section. All unique group values across all host sections
# are the set of defined groups.
# config group 'group_1'
# option name 'family'
#
# A group's name string is the identity that ties everything together
# (host.group values, restriction/quota/QoS rules' GROUP:<name> targets, and
# nftables set naming below) -- 'config group' sections do not change that,
# they only let a group be DECLARED independent of any device carrying it.
# The full set of defined groups is therefore the union of every unique
# host.group value AND every declared group's option name (get_all_groups
# below) -- a name present in both counts once. A group with no declaration
# still works exactly as before: it exists only as long as >=1 host section
# carries it, and vanishes the moment the last one stops (unchanged,
# pre-existing behaviour). A group WITH a declaration persists at zero
# devices instead -- see docs/standalone-device-groups-plan.md for why this
# was added and why it's safe (manage_groups.sh already creates a group's
# nftables set before any rule can reference it, regardless of device count).
#
# nftables set naming:
# Group names are sanitized to lowercase, with any character outside
Expand Down Expand Up @@ -49,42 +70,81 @@ get_device_field()
uci get "dhcp.$1.$2" 2>/dev/null
}

# get_device_group <section>
# Returns the group name for a host section, or empty string if unset.
get_device_group()
# get_device_groups <section>
# Prints every group name a host section belongs to (0, 1, or many --
# one per line). Reads via `uci show` rather than `uci get`. Verified
# against the real router (not assumed): a `list` option with multiple
# values renders as ONE "key='A' 'B'" line, space-separated with each
# value individually quoted -- NOT one line per value. Since group names
# are charset-restricted to [a-zA-Z0-9_-] (no spaces, no quotes -- see the
# dev_new_group validation in dhcp.js), stripping quotes and splitting on
# the remaining spaces is a safe, portable parse (no GNU-sed-specific
# escapes needed). A legacy single-value `option group 'family'` line
# already has no interior spaces, so it passes through unsplit -- one
# consistent pipeline for both shapes.
get_device_groups()
{
uci show dhcp 2>/dev/null | grep "^dhcp\.$1\.group=" | sed "s/^[^=]*=//; s/'//g" | tr ' ' '\n'
}

# get_all_declared_group_sections
# Prints UCI section names for every 'group' section in /etc/config/dhcp.
get_all_declared_group_sections()
{
get_device_field "$1" "group"
uci show dhcp 2>/dev/null | grep '=group$' | sed 's/dhcp\.\(.*\)=group/\1/'
}

# set_device_group <section> <group_name>
# Assigns a host section to a group. Pass empty string to remove.
set_device_group()
# find_declared_group_section <group_name>
# Prints the UCI section name of the 'group' section whose option name
# exactly matches (case-sensitive, matching every other group-name
# comparison in this codebase), or nothing + failure if none declares it.
find_declared_group_section()
{
local section="$1"
local group="$2"
if [ -z "$group" ]
then
uci del "dhcp.$section.group" 2>/dev/null
else
uci set "dhcp.$section.group=$group"
fi
local target="$1"
local gsections
gsections=$(get_all_declared_group_sections)
local gsection
for gsection in $gsections
do
if [ "$(get_device_field "$gsection" "name")" = "$target" ]
then
echo "$gsection"
return 0
fi
done
return 1
}

# get_all_groups
# Prints each unique group name (one per line) found across all host sections.
# Prints each unique group name (one per line): every host section's group
# value, unioned with every declared group's name, so a zero-device
# declared group still appears and a name present in both counts once.
get_all_groups()
{
local sections
sections=$(get_all_known_device_sections)
local section
for section in $sections
do
get_device_group "$section"
done | sort -u | grep -v '^$'
{
local sections
sections=$(get_all_known_device_sections)
local section
for section in $sections
do
get_device_groups "$section"
done

local gsections
gsections=$(get_all_declared_group_sections)
local gsection
for gsection in $gsections
do
get_device_field "$gsection" "name"
done
} | sort -u | grep -v '^$'
}

# get_sections_in_group <group_name>
# Prints the UCI section names of every host that belongs to the given group.
# Prints the UCI section names of every host that belongs to the given
# group -- a host with several groups matches every one of them, not just
# a single "the" group, so it's counted (and its IP seeded into the
# nftables set) for each.
get_sections_in_group()
{
local target_group="$1"
Expand All @@ -93,9 +153,7 @@ get_sections_in_group()
local section
for section in $sections
do
local grp
grp=$(get_device_group "$section")
if [ "$grp" = "$target_group" ]
if get_device_groups "$section" | grep -qxF "$target_group"
then
echo "$section"
fi
Expand Down
22 changes: 12 additions & 10 deletions package/gargoyle/files/usr/lib/gargoyle/post_lease.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,23 @@ ip="$3"
[ "$ip" = "*" ] && exit 0
[ "$event" != "add" ] && [ "$event" != "del" ] && [ "$event" != "old" ] && exit 0

# Find which group this MAC belongs to by scanning UCI host sections
# Find which section this MAC belongs to, then update EVERY group it's a
# member of (a device can belong to more than one) -- each is an
# independent nftables set, so its IP needs adding to/removing from all of
# them, not just the first.
for section in $(get_all_known_device_sections); do
stored_mac=$(get_device_field "$section" "mac" | tr 'a-z' 'A-Z')
[ "$stored_mac" != "$mac" ] && continue

group=$(get_device_group "$section")
[ -z "$group" ] && exit 0
for group in $(get_device_groups "$section"); do
setname=$(group_to_set_name "$group")

setname=$(group_to_set_name "$group")

if [ "$event" = "add" ] || [ "$event" = "old" ]; then
nft add element "$NFT_FAMILY" "$NFT_TABLE" "$setname" \{ "$ip" \} 2>/dev/null
else
nft delete element "$NFT_FAMILY" "$NFT_TABLE" "$setname" \{ "$ip" \} 2>/dev/null
fi
if [ "$event" = "add" ] || [ "$event" = "old" ]; then
nft add element "$NFT_FAMILY" "$NFT_TABLE" "$setname" \{ "$ip" \} 2>/dev/null
else
nft delete element "$NFT_FAMILY" "$NFT_TABLE" "$setname" \{ "$ip" \} 2>/dev/null
fi
done

exit 0
done
Expand Down
39 changes: 38 additions & 1 deletion package/gargoyle/files/www/dhcp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
ip -6 neigh | grep -v "FAILED" | grep -v "^fe80:" | awk '{print "ip6neighData[\""$1"\"] = \""$5"\";"};'

echo "var knownDeviceGroups = [];"
uci show dhcp 2>/dev/null | grep '^dhcp\.[^.]*\.group=' | sed "s/^[^=]*=//; s/'//g" | sort -u | grep -v '^$' | while read grp ; do
. /usr/lib/gargoyle/known_devices.sh
get_all_groups | while read grp ; do
escaped=$(printf '%s' "$grp" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf 'knownDeviceGroups.push("%s");\n' "$escaped"
done
Expand Down Expand Up @@ -252,6 +253,24 @@ ipHostHash["::1"] = "localhost6";
</div>
</div>

<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><%~ dhcp.Grps %></h3>
</div>
<div class="panel-body">
<div class="row form-group">
<span class="col-xs-12"><button id="add_group_button" class="btn btn-default btn-add" onclick="addGroupModal()"><%~ dhcp.AdGrp %></button></span>
</div>
<div class="row form-group">
<div id="groups_table_container" class="table-responsive col-xs-12"></div>
</div>
</div>
</div>
</div>
</div>

<div id="firefox3_bug_correct" style="display:none">
<input type="text" value="firefox3_bug" />
</div>
Expand All @@ -276,6 +295,24 @@ ipHostHash["::1"] = "localhost6";
</div>
</div>

<div class="modal fade" tabindex="-1" role="dialog" id="group_modal" aria-hidden="true" aria-labelledby="group_modal_title">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 id="group_modal_title" class="panel-title"></h3>
</div>
<div class="modal-body">
<div class="row form-group">
<label class="col-xs-4" for="group_name" id="group_name_label"><%~ dhcp.GrpNm %>:</label>
<span class="col-xs-8"><input type="text" class="form-control" id="group_name" /></span>
</div>
</div>
<div class="modal-footer" id="group_modal_button_container">
</div>
</div>
</div>
</div>

<script>
<!--
resetData();
Expand Down
Loading
Loading