fix: exclude dummy interfaces from system interfaces list and ensure … (#244)

This commit is contained in:
Daniel Lavrushin 2026-06-08 16:06:55 +02:00 committed by GitHub
parent 33bb8eb07b
commit d267d12083
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 29 additions and 62 deletions

View file

@ -1,5 +1,10 @@
# B4 - Bye Bye Big Bro
## [1.66.1] - 2026-06-08
- FIXED: **Dummy network interfaces were missing from the interface lists** - a dummy interface (for example `dummy0`) did not appear in Settings, so it could not be picked for monitoring or NAT masquerade. Any dummy interface that is up now shows up in both lists.
- FIXED: **Telegram desktop kept reconnecting every 30-60 seconds over the Telegram bridge** - a background keep-alive added in 1.66.0 to hold idle connections open actually sent a signal Telegram's servers rejected, so the connection dropped and rebuilt itself about twice a minute (you would see parts of the Telegram window flicker). That keep-alive has been removed, so connections stay healthy on their own again.
## [1.66.0] - 2026-06-07
- ADDED: **Blocking stats on the Dashboard** - when a set uses Block (blackhole) mode and actually blocks something, the Dashboard now shows a "Blackhole" panel with the total number of blocked attempts, the most-blocked domains, and which devices ran into the most blocks. The panel stays hidden until there is something to show, so it never clutters the page when nothing is being blocked. Blocked connections are also tagged with a "block" label on the Traffic page so you can spot them in the live feed.
@ -7,9 +12,7 @@
- FIXED: **Set Routing now respects your device choices** - if you used Settings -> Devices to make b4 work for only some devices (or to exclude some), that choice was ignored by a set's Routing tab. Whatever the set did with the traffic (send it to another network interface or an upstream proxy, run it through the Telegram bridge, or block it) happened for every device regardless. Routing now applies only to the devices you picked. You can also give an individual set its own device list in its settings, so a single set can route, bridge, or block traffic for just specific devices without affecting the rest.
- FIXED: **The router itself showed up as a wrongly-named device on the Traffic page** - b4 mistook the router's own address for a regular client and listed it as a separate device (sometimes guessed as a phone brand), filing the router's own connections under it. b4 now recognizes its own interfaces and labels that traffic simply as "Router".
- FIXED: **Discord voice and video calls could drop when blocking UDP** - on a set with UDP set to Drop or Reject, Discord calls could break because b4 did not recognize Discord's call traffic. b4 now lets it through (when "Filter STUN" is on, the default), so calls keep working.
- FIXED: **b4 would not start on some Asus Merlin routers** - on certain firmware (for example the MerlinWRT) b4 quit right after starting and showed a confusing message about a missing `xt_connbytes` feature, even though the router actually supported it. The real cause was b4 using a newer firewall command option that the router's built-in tool did not understand. b4 now adapts to the router's own tools automatically, so it starts normally with nothing extra to install.
- FIXED: **Telegram no longer disconnects with a "proxy is not configured correctly" message after sitting idle** - when a Telegram connection through b4 was left idle for a while, your network could quietly drop it in the background, and Telegram would show "The proxy you are using is not configured correctly and will be disabled" even though the proxy was working fine. b4 now keeps these connections alive on its own, so they stay open and reconnect cleanly instead of being dropped.
- FIXED: **Connections page showed nothing when the device clock was out of sync** - the time filter (30s / 1m / 5m / 15m) and the per-connection activity graph compared the live data against the browser's own clock, so a computer whose clock was off by even ~30 seconds could see an empty list and empty activity bars. Filtering and the activity graph now use the timestamps in the connection data itself, so they work regardless of the device clock or its timezone.
- FIXED: **b4 would not start on some Asus Merlin routers** - on certain firmware (for example the MerlinWRT) b4 quit right after starting and showed a confusing message about a missing `xt_connbytes` feature, even though the router actually supported it. The real cause was b4 using a newer firewall command option that the router's built-in tool did not understand. b4 now adapts to the router's own tools automatically, so it starts normally with nothing extra to install.- FIXED: **Connections page showed nothing when the device clock was out of sync** - the time filter (30s / 1m / 5m / 15m) and the per-connection activity graph compared the live data against the browser's own clock, so a computer whose clock was off by even ~30 seconds could see an empty list and empty activity bars. Filtering and the activity graph now use the timestamps in the connection data itself, so they work regardless of the device clock or its timezone.
- FIXED: **A device could still show up twice on the Traffic page** - one device sometimes appeared as two rows, once under its name and once under its bare IP. b4 now recognizes these as the same device and shows it only once.
- FIXED: **Discovery found no working strategy when site names were entered with quotes** - if you added domains to Discovery wrapped in quotation marks (for example `"discord.com","youtube.com"`, as happens when pasting a copied list), b4 kept the quote marks as part of each name, so every lookup failed and Discovery reported nothing working for any site. Surrounding quotes are now removed automatically. [#241](https://github.com/DanielLavrushin/b4/issues/241)

View file

@ -185,7 +185,7 @@ func getSystemInterfaces() ([]string, error) {
// Known virtual/internal interface prefixes to exclude
excludePrefixes := []string{
"lo", "dummy", "gre", "erspan", "ifb", "imq",
"lo", "gre", "erspan", "ifb", "imq",
"ip6_vti", "ip6gre", "ip6tnl", "ip_vti", "sit",
"spu_", "bcmsw", "blog",
}
@ -220,12 +220,13 @@ func getSystemInterfaces() ([]string, error) {
} else {
// On host, require IP or known useful prefix
addrs, _ := iface.Addrs()
isBridgeOrWireless := strings.HasPrefix(iface.Name, "br") ||
allowedWithoutIP := strings.HasPrefix(iface.Name, "br") ||
strings.HasPrefix(iface.Name, "wl") ||
strings.HasPrefix(iface.Name, "tun") ||
strings.HasPrefix(iface.Name, "tap")
strings.HasPrefix(iface.Name, "tap") ||
strings.HasPrefix(iface.Name, "dummy")
if len(addrs) > 0 || isBridgeOrWireless {
if len(addrs) > 0 || allowedWithoutIP {
ifaceNames = append(ifaceNames, iface.Name)
}
}

View file

@ -16,9 +16,8 @@ import (
)
const (
maxConnections = 512
relayBufSize = 65536
wsKeepaliveInterval = 30 * time.Second
maxConnections = 512
relayBufSize = 65536
)
type Server struct {
@ -290,7 +289,7 @@ func (s *Server) handleConn(raw net.Conn) {
if _, ok := dcConn.Conn.(*wsConn); ok {
splitter = newMsgSplitter(result.ProtoTag)
}
s.relay(result.Conn, dcConn, splitter, fmt.Sprintf("%s %s<->DC%d", tag, clientAddr, result.DC))
s.relay(result.Conn, dcConn, splitter, fmt.Sprintf("%s %s<->DC%d via %s", tag, clientAddr, result.DC, transport))
}
func (s *Server) relay(client, dc io.ReadWriteCloser, splitter *msgSplitter, label string) {
@ -298,7 +297,11 @@ func (s *Server) relay(client, dc io.ReadWriteCloser, splitter *msgSplitter, lab
}
func relayConns(client, dc io.ReadWriteCloser, splitter *msgSplitter, label string, bufPool *sync.Pool) {
errCh := make(chan error, 2)
type relayEnd struct {
dir string
err error
}
endCh := make(chan relayEnd, 2)
start := time.Now()
var upBytes, downBytes atomic.Int64
@ -324,7 +327,7 @@ func relayConns(client, dc io.ReadWriteCloser, splitter *msgSplitter, label stri
}
counter.Store(total)
log.Debugf("%s %s: %d bytes, err=%v", label, dir, total, err)
errCh <- err
endCh <- relayEnd{dir: dir, err: err}
}
cpSplit := func(dst io.Writer, src io.Reader, dir string, counter *atomic.Int64) {
@ -354,7 +357,7 @@ func relayConns(client, dc io.ReadWriteCloser, splitter *msgSplitter, label stri
}
counter.Store(total)
log.Debugf("%s %s: %d bytes, err=%v", label, dir, total, err)
errCh <- err
endCh <- relayEnd{dir: dir, err: err}
}
if splitter != nil {
@ -364,48 +367,15 @@ func relayConns(client, dc io.ReadWriteCloser, splitter *msgSplitter, label stri
}
go cp(client, dc, "DC->client", &downBytes)
stopKA := startWSKeepalive(dc, label)
<-errCh
close(stopKA)
first := <-endCh
_ = client.Close()
_ = dc.Close()
<-errCh
log.Infof("%s closed: up=%d down=%d in %dms", label, upBytes.Load(), downBytes.Load(), time.Since(start).Milliseconds())
}
<-endCh
func asWSConn(c io.ReadWriteCloser) *wsConn {
switch v := c.(type) {
case *wsConn:
return v
case *ObfuscatedConn:
if w, ok := v.Conn.(*wsConn); ok {
return w
}
up, down := upBytes.Load(), downBytes.Load()
stale := ""
if first.dir == "DC->client" && down == 0 {
stale = " stale-upstream?"
}
return nil
}
func startWSKeepalive(dc io.ReadWriteCloser, label string) chan struct{} {
stop := make(chan struct{})
ws := asWSConn(dc)
if ws == nil {
return stop
}
go func() {
t := time.NewTicker(wsKeepaliveInterval)
defer t.Stop()
for {
select {
case <-stop:
return
case <-t.C:
if err := ws.sendPing(); err != nil {
log.Debugf("%s ws keepalive ping failed: %v -> closing upstream", label, err)
_ = dc.Close()
return
}
}
}
}()
return stop
log.Infof("%s closed: first=%s err=%v up=%d down=%d in %dms%s", label, first.dir, first.err, up, down, time.Since(start).Milliseconds(), stale)
}

View file

@ -158,7 +158,7 @@ func (b *TransparentBridge) Handle(client net.Conn, origIP net.IP, origPort int)
}
defer dcConn.Close()
label := fmt.Sprintf("%s %s<->DC%d(transparent)", tag, client.RemoteAddr(), dc)
label := fmt.Sprintf("%s %s<->DC%d(transparent via %s)", tag, client.RemoteAddr(), dc, transport)
log.Infof("%s bridge relay %s:%d -> DC%d via %s [dc-from=%s]", tag, origIP, origPort, dc, transport, dcSrc)
var splitter *msgSplitter

View file

@ -271,13 +271,6 @@ func (c *wsConn) writeFrame(op byte, payload []byte) error {
return err
}
func (c *wsConn) sendPing() error {
if c.closed.Load() {
return net.ErrClosed
}
return c.writeFrame(wsOpcodePing, nil)
}
func dialWS(host, sni, path string, timeout time.Duration, mark uint) (net.Conn, error) {
if path == "" {
path = "/apiws"