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
Original file line number Diff line number Diff line change
Expand Up @@ -723,8 +723,16 @@ isolate_guest_networks()
}


manage_device_groups()
{
if [ -x /usr/lib/gargoyle/manage_groups.sh ] ; then
/usr/lib/gargoyle/manage_groups.sh
fi
}

ifup_firewall()
{
manage_device_groups
insert_restriction_rules
initialize_quotas
insert_pf_loopback_rules
Expand Down
102 changes: 84 additions & 18 deletions package/gargoyle-firewall-util/src/make_nftables_rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Originally designed for use with Gargoyle router firmware (gargoyle-router.com)
*
*
* Copyright � 2009 by Eric Bishop <eric@gargoyle-router.com>
* Copyright � 2009 by Eric Bishop <eric@gargoyle-router.com>
* Rewritten for nftables 2025 by Michael Gray <support@lantisproject.com>
*
* This file is free software: you may copy, redistribute and/or modify it
Expand Down Expand Up @@ -49,6 +49,7 @@ char** parse_marks(char* list_str, unsigned long max_mask);
list* parse_quoted_list(char* list_str, char quote_char, char escape_char, char add_remainder_if_uneven_quotes);

int truncate_if_starts_with(char* test_str, char* prefix);
char* group_name_to_set_name(char* group_name);

char* invert_bitmask(const char* input, int force_32bit);

Expand Down Expand Up @@ -796,18 +797,56 @@ char* get_option_value_string(struct uci_option* uopt)
return opt_str;
}

char* group_name_to_set_name(char* group_name)
{
int src_len = strlen(group_name);
char* sanitized = (char*)malloc(src_len + 1);
int i;
int out = 0;
int last_was_under = 0;
for(i = 0; i < src_len; i++)
{
char c = group_name[i];
char out_c;
if(c >= 'A' && c <= 'Z') { out_c = c + 32; }
else if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { out_c = c; }
else { out_c = '_'; }
/* squeeze consecutive underscores (match shell: tr -cs 'a-z0-9_' '_') */
if(out_c == '_' && last_was_under) { continue; }
sanitized[out++] = out_c;
last_was_under = (out_c == '_');
}
sanitized[out] = '\0';
char* set_name = dynamic_strcat(2, "grp_", sanitized);
free(sanitized);
if(strlen(set_name) > 31)
{
set_name[31] = '\0';
}
return set_name;
}

char*** parse_ips_and_macs(char* addr_str)
{
unsigned long num_pieces;
char** addr_parts = split_on_separators(addr_str, ",", 1, -1, 0, &num_pieces);
list* ip_list = initialize_list();
list* ip_list = initialize_list(); /* plain IPv4 addresses/ranges/CIDRs */
list* set_ref_list = initialize_list(); /* @setname refs from GROUP: entries */
list* ip6_list = initialize_list();
list* mac_list = initialize_list();

int ip_part_index;
for(ip_part_index=0; addr_parts[ip_part_index] != NULL; ip_part_index++)
{
char* next_str = addr_parts[ip_part_index];
if(strncmp(next_str, "GROUP:", 6) == 0)
{
char* setname = group_name_to_set_name(next_str + 6);
char* setref = dynamic_strcat(2, "@", setname);
free(setname);
push_list(set_ref_list, setref);
continue;
}
//ipv4, ipv6 or any can be MAC address. Test it first
if(strchr(next_str, ':'))
{
Expand Down Expand Up @@ -858,20 +897,22 @@ char*** parse_ips_and_macs(char* addr_str)

char*** return_value = (char***)malloc(3*sizeof(char**));

unsigned long num1, num2, num3;
char** ip_strs = (char**)destroy_list(ip_list, DESTROY_MODE_RETURN_VALUES, &num1);
char** ip6_strs = (char**)destroy_list(ip6_list, DESTROY_MODE_RETURN_VALUES, &num2);
char** mac_strs = (char**)destroy_list(mac_list, DESTROY_MODE_RETURN_VALUES, &num3);
unsigned long num_plain_ip, num_set_refs, num2, num3;
char** plain_ip_strs = (char**)destroy_list(ip_list, DESTROY_MODE_RETURN_VALUES, &num_plain_ip);
char** set_ref_strs = (char**)destroy_list(set_ref_list, DESTROY_MODE_RETURN_VALUES, &num_set_refs);
char** ip6_strs = (char**)destroy_list(ip6_list, DESTROY_MODE_RETURN_VALUES, &num2);
char** mac_strs = (char**)destroy_list(mac_list, DESTROY_MODE_RETURN_VALUES, &num3);

char* match_ip_str = join_strs(",", ip_strs, -1, 1, 1);
char* match_ip6_str = join_strs(",", ip6_strs, -1, 1, 1);
char* match_mac_str = join_strs(",", mac_strs, -1, 1, 1);
/* plain IPs: join with commas and wrap in { } when multiple (nftables inline set) */
char* match_plain_ip_str = join_strs(",", plain_ip_strs, -1, 1, 1);
char* match_ip6_str = join_strs(",", ip6_strs, -1, 1, 1);
char* match_mac_str = join_strs(",", mac_strs, -1, 1, 1);

if(num1 > 1)
if(num_plain_ip > 1)
{
char* tmp = dynamic_strcat(3, "{", match_ip_str, "}");
free(match_ip_str);
match_ip_str = tmp;
char* tmp = dynamic_strcat(3, "{", match_plain_ip_str, "}");
free(match_plain_ip_str);
match_plain_ip_str = tmp;
}
if(num2 > 1)
{
Expand All @@ -885,20 +926,45 @@ char*** parse_ips_and_macs(char* addr_str)
free(match_mac_str);
match_mac_str = tmp;
}
if(num1 + num2 + num3 == 0)

if(num_plain_ip + num_set_refs + num2 + num3 == 0)
{
free(return_value);
return_value = NULL;
}
else
{
return_value[MATCH_IP_INDEX] = (char**)malloc(2*sizeof(char*));
/*
* Build the IPv4 match array so that:
* - plain IPs (if any) appear as one entry, already formatted as a
* single address or "{ip1,ip2,...}" inline set
* - each GROUP:-derived @setname is a SEPARATE entry so that
* compute_multi_rules emits one rule per set, letting the connmark
* OR logic handle them correctly. Mixing named sets with { } is
* not valid nftables syntax so they must never be combined.
*/
unsigned long ip_entry_count = (num_plain_ip > 0 ? 1 : 0) + num_set_refs;
char** ip_return = (char**)malloc((ip_entry_count + 1) * sizeof(char*));
unsigned long ip_idx = 0;
if(num_plain_ip > 0)
{
ip_return[ip_idx++] = match_plain_ip_str;
}
else
{
free(match_plain_ip_str); /* NULL-safe */
}
unsigned long sr;
for(sr = 0; sr < num_set_refs; sr++)
{
ip_return[ip_idx++] = set_ref_strs[sr];
}
ip_return[ip_idx] = NULL;

return_value[MATCH_IP_INDEX] = ip_return;
return_value[MATCH_IP6_INDEX] = (char**)malloc(2*sizeof(char*));
return_value[MATCH_MAC_INDEX] = (char**)malloc(2*sizeof(char*));

return_value[MATCH_IP_INDEX][0] = match_ip_str;
return_value[MATCH_IP_INDEX][1] = NULL;
return_value[MATCH_IP6_INDEX][0] = match_ip6_str;
return_value[MATCH_IP6_INDEX][1] = NULL;
return_value[MATCH_MAC_INDEX][0] = match_mac_str;
Expand Down
75 changes: 71 additions & 4 deletions package/gargoyle-firewall-util/src/restore_quotas.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ list* filter_group_from_list(list** orig_ip_list, char* ip_group_str);
int get_ipstr_family(char* ip_str);
char* invert_bitmask(const char* input, int force_32bit);

char* group_name_to_set_name(char* group_name);
void delete_chain_from_table(char* family, char* table, char* delete_chain);
void run_shell_command(char* command, int free_command_str);
void free_split_pieces(char** split_pieces);
Expand Down Expand Up @@ -330,16 +331,17 @@ int main(int argc, char** argv)
ip = dynamic_replace(ip, "- ", "-");
free(tmp_ip);
}



int is_group_target = (strncmp(ip, "GROUP:", 6) == 0) ? 1 : 0;

if( (strcmp(ip, "ALL_OTHERS_COMBINED") == 0 || strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0) && (!process_other_quota) )
{
push_list(other_quota_section_names, strdup(next_quota));
}
else
{
unsigned char is_individual_other = strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0 ? 1 : 0;
if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 )
if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 && !is_group_target )
{
/* this is an explicitly defined ip or ip range, so save it for later, to deal with individual other overlap problem */
push_list(defined_ip_groups, strdup(ip));
Expand Down Expand Up @@ -497,7 +499,7 @@ int main(int argc, char** argv)
int foundip6 = 0;
char* ip4_list = NULL;
char* ip6_list = NULL;
if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 )
if( strcmp(ip, "ALL_OTHERS_COMBINED") != 0 && strcmp(ip, "ALL_OTHERS_INDIVIDUAL") != 0 && strcmp(ip, "ALL") != 0 && !is_group_target )
{
char* src_test = dynamic_strcat(3, "saddr ", ip, " ");
char* dst_test = dynamic_strcat(3, "daddr ", ip, " ");
Expand Down Expand Up @@ -628,6 +630,33 @@ int main(int argc, char** argv)
free(dst_test);
free(src_test);
}
else if(is_group_target)
{
char* setname = group_name_to_set_name(ip + 6);
char* src_test = dynamic_strcat(3, " ip saddr @", setname, " ");
char* dst_test = dynamic_strcat(3, " ip daddr @", setname, " ");
free(setname);
foundip4 = 1;
foundip6 = 0;
if(strcmp(types[type_index], "combined_limit") == 0)
{
run_shell_command(dynamic_strcat(9, "nft add rule ", quota_family_table, " ", quota_chain_prefix, chains[type_index], " iifname ", wan_if, dst_test, " ct mark set ct mark \\& 0xF0FFFFFF \\| 0x0F000000 2>/dev/null"), 1);
run_shell_command(dynamic_strcat(9, "nft add rule ", quota_family_table, " ", quota_chain_prefix, chains[type_index], " oifname ", wan_if, src_test, " ct mark set ct mark \\& 0xF0FFFFFF \\| 0x0F000000 2>/dev/null"), 1);
char* rule_end = strdup(" ct mark \\& 0x0F000000 == 0x0F000000 ");
ip_test = dcat_and_free(&ip_test, &rule_end, 1, 1);
}
else if(strcmp(types[type_index], "egress_limit") == 0)
{
ip_test = dcat_and_free(&ip_test, &src_test, 1, 0);
}
else
{
ip_test = dcat_and_free(&ip_test, &dst_test, 1, 0);
}
run_shell_command(dynamic_strcat(7, "nft add rule ", quota_family_table, " ", quota_chain_prefix, chains[type_index], ip_test, " ct mark set ct mark \\& 0x0FFFFFFF \\| 0xF0000000 2>/dev/null"), 1);
free(dst_test);
free(src_test);
}
else if( strcmp(ip, "ALL_OTHERS_COMBINED") == 0 || strcmp(ip, "ALL_OTHERS_INDIVIDUAL") == 0 )
{
char* rule_end = strdup(" ct mark \\& 0xF0000000 == 0x0");
Expand Down Expand Up @@ -787,6 +816,15 @@ int main(int argc, char** argv)
{
run_shell_command(dynamic_strcat(7, "nft add rule ", quota_family_table, " nat_quota_redirects tcp dport {80,443} ", time_match_str, " ct mark \\& 0xF0000000 == 0x0 bandwidth bcheck id \\\"", type_id, "\\\" redirect"), 1);
}
else if(is_group_target)
{
char* setname = group_name_to_set_name(ip + 6);
run_shell_command(dynamic_strcat(5, "nft add rule ", quota_family_table, " nat_quota_redirects ip saddr @", setname, " ct mark set ct mark \\& 0xF0FFFFFF \\| 0x0F000000 2>/dev/null"), 1);
run_shell_command(dynamic_strcat(7, "nft add rule ", quota_family_table, " nat_quota_redirects tcp dport {80,443} ", time_match_str, " ct mark \\& 0x0F000000 == 0x0F000000 bandwidth bcheck id \\\"", type_id, "\\\" redirect"), 1);
run_shell_command(dynamic_strcat(3, "nft add rule ", quota_family_table, " nat_quota_redirects ct mark \\& 0x0F000000 == 0x0F000000 ct mark set ct mark \\& 0x0FFFFFFF \\| 0xF0000000 2>/dev/null"), 1);
run_shell_command(dynamic_strcat(3, "nft add rule ", quota_family_table, " nat_quota_redirects ct mark set ct mark \\& 0xF0FFFFFF \\| 0x0 2>/dev/null"), 1);
free(setname);
}
else
{
if(ip4_list != NULL)
Expand Down Expand Up @@ -1289,6 +1327,35 @@ char* invert_bitmask(const char* input, int force_32bit)
return result;
}

char* group_name_to_set_name(char* group_name)
{
int src_len = strlen(group_name);
char* sanitized = (char*)malloc(src_len + 1);
int i;
int out = 0;
int last_was_under = 0;
for(i = 0; i < src_len; i++)
{
char c = group_name[i];
char out_c;
if(c >= 'A' && c <= 'Z') { out_c = c + 32; }
else if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { out_c = c; }
else { out_c = '_'; }
/* squeeze consecutive underscores from any source (match shell: tr -cs 'a-z0-9_' '_') */
if(out_c == '_' && last_was_under) { continue; }
sanitized[out++] = out_c;
last_was_under = (out_c == '_');
}
sanitized[out] = '\0';
char* set_name = dynamic_strcat(2, "grp_", sanitized);
free(sanitized);
if(strlen(set_name) > 31)
{
set_name[31] = '\0';
}
return set_name;
}

void delete_chain_from_table(char* family, char* table, char* delete_chain)
{
char *command = dynamic_strcat(5, "nft -a list table ", family, " ", table, " 2>/dev/null");
Expand Down
7 changes: 7 additions & 0 deletions package/gargoyle/files/etc/uci-defaults/gargoyle-init
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,10 @@ cat << EOF >/etc/sysupgrade.conf
EOF



# Register dnsmasq lease hook for Device Groups (only if no existing script is set)
existing_dhcp_script=$(uci get dhcp.@dnsmasq[0].dhcpscript 2>/dev/null)
if [ -z "$existing_dhcp_script" ] ; then
uci set dhcp.@dnsmasq[0].dhcpscript=/usr/lib/gargoyle/post_lease.sh
uci commit dhcp
fi
Loading