fix(config): refuse a mark with bit 25 only where NFQUEUE mode newly takes it

From Copilot's review of 50e71425: the save-time refusal also turned
down a TUN configuration's mark such as 0x2008000, although TUN adds
0x10000000 to its packets and never 0x2000000, and loading such a
configuration from disk only warns in NFQUEUE mode. The refusal now
applies when a save leaves NFQUEUE mode with a mark that has bit
0x2000000 and the running configuration did not already run NFQUEUE
mode with that mark, so leaving TUN mode with one is still refused.
marks.md (EN, RU) says NFQUEUE mode.
This commit is contained in:
Daniel Lavrushin 2026-10-17 10:55:54 +02:00
parent 50e71425b5
commit 0dcc93088d
No known key found for this signature in database
GPG key ID: 57F1CAB57AD35056
4 changed files with 25 additions and 18 deletions

View file

@ -201,9 +201,9 @@ b4 refuses a value that:
- is made only of bits of `0x27fff` and `0x1000000`, the bits of set marks;
- overlaps `0x70000000` in TUN mode;
- has bit `0x20000000` in NFQUEUE mode while **NAT Masquerade** or **Set DSCP** is on, or while an enabled set has **Enable per-set DSCP** on;
- has bit `0x2000000`, the bit b4 adds to the packets it sends into a connection, when the
value is saved as a new one; a configuration that already holds such a value still loads,
and b4 logs a warning;
- has bit `0x2000000` in NFQUEUE mode, the bit b4 adds there to the packets it sends into a
connection. A configuration that already runs NFQUEUE mode with such a value still loads
and saves, and b4 logs a warning;
- has `0x24bab`, the Telegram over WebSocket mark, as its bits under `0x27fff`;
- equals the flow or injected mark of Discovery;
- is above `0xffffffff`, or, while either Discovery mark is left out of the configuration, is

View file

@ -205,9 +205,9 @@ b4 не принимает значение, которое:
- состоит только из битов `0x27fff` и `0x1000000`, битов меток сетов;
- пересекается с `0x70000000` в режиме TUN;
- содержит бит `0x20000000` в режиме NFQUEUE, пока включено **NAT Masquerade** или **Задать DSCP** либо у включённого сета включено **Включить DSCP для сета**;
- содержит бит `0x2000000`, который b4 добавляет к пакетам, отправленным в соединение, если
значение сохраняется как новое; конфигурация, в которой такое значение уже есть, по-прежнему
загружается, а b4 пишет предупреждение в журнал;
- содержит бит `0x2000000` в режиме NFQUEUE, где b4 добавляет этот бит к пакетам, отправленным
в соединение. Конфигурация, которая уже работает в режиме NFQUEUE с таким значением,
по-прежнему загружается и сохраняется, а b4 пишет предупреждение в журнал;
- имеет под `0x27fff` биты `0x24bab`, метку Telegram через WebSocket;
- совпадает с меткой потока или меткой отправленных пакетов Дискавери;
- больше `0xffffffff`, либо, пока в конфигурации не задана хотя бы одна из меток Дискавери,

View file

@ -698,7 +698,10 @@ func (a *API) pushConfigLocked(newCfg *config.Config) error {
func queueMarkRefusal(oldCfg, newCfg *config.Config) error {
mark := newCfg.MainInjectedMark()
if mark&engine.InjectedMarkBit == 0 || (oldCfg != nil && oldCfg.MainInjectedMark() == mark) {
if newCfg.Queue.Mode == "tun" || mark&engine.InjectedMarkBit == 0 {
return nil
}
if oldCfg != nil && oldCfg.Queue.Mode != "tun" && oldCfg.MainInjectedMark() == mark {
return nil
}
return ErrValidation("The packet mark has a bit b4 reserves", FieldError{

View file

@ -23,22 +23,26 @@ func TestSavingAQueueMarkWithTheInjectedBitIsRefused(t *testing.T) {
}
func TestAQueueMarkThatAlreadyHasTheInjectedBitKeepsSaving(t *testing.T) {
old := config.NewConfig()
old.Queue.Mark = 0x2008000
for _, c := range []struct {
name string
mark uint
want bool
name string
oldMode, newMode string
oldMark, newMark uint
want bool
}{
{"the same mark, while other settings change", 0x2008000, false},
{"another mark with the bit", 0x2010000, true},
{"a mark without the bit", 0x10000, false},
{"the default", 0, false},
{"the same mark, while other settings change", "", "", 0x2008000, 0x2008000, false},
{"another mark with the bit", "", "", 0x2008000, 0x2010000, true},
{"a mark without the bit", "", "", 0x2008000, 0x10000, false},
{"the default", "", "", 0x2008000, 0, false},
{"TUN mode, where b4 does not add the bit", "", "tun", 0x8000, 0x2008000, false},
{"TUN mode keeping such a mark", "tun", "tun", 0x2008000, 0x2008000, false},
{"leaving TUN mode with such a mark", "tun", "", 0x2008000, 0x2008000, true},
} {
old := config.NewConfig()
old.Queue.Mode, old.Queue.Mark = c.oldMode, c.oldMark
cur := old.Clone()
cur.Queue.Mark = c.mark
cur.Queue.Mode, cur.Queue.Mark = c.newMode, c.newMark
if refused := queueMarkRefusal(&old, cur) != nil; refused != c.want {
t.Errorf("%s (0x%x): refused %v, want %v; a configuration that already holds such a mark must keep loading and saving", c.name, c.mark, refused, c.want)
t.Errorf("%s (0x%x): refused %v, want %v; only a save that newly puts such a mark on NFQUEUE mode is refused, so a configuration that already runs with one keeps loading and saving", c.name, c.newMark, refused, c.want)
}
}
}