Changeset 684 in code for trunk


Ignore:
Timestamp:
Nov 15, 2021, 1:34:04 PM (4 years ago)
Author:
contact
Message:

Add support for MONITOR

Add support for MONITOR in single-upstream mode.

Each downstream has its own set of monitored targets. These sets
are merged together to compute the MONITOR commands to send to
upstream.

Each upstream has a set of monitored targets accepted by the server
alongside with their status (online/offline). This is used to
directly send replies to downstreams adding a target another
downstream has already added, and send MONITOR S[TATUS] replies.

Co-authored-by: delthas <delthas@…>

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/downstream.go

    r683 r684  
    229229        "MAXTARGETS":    true,
    230230        "MODES":         true,
     231        "MONITOR":       true,
    231232        "NAMELEN":       true,
    232233        "NETWORK":       true,
     
    265266        lastBatchRef uint64
    266267
     268        monitored casemapMap
     269
    267270        saslServer sasl.Server
    268271}
     
    277280                supportedCaps: make(map[string]string),
    278281                caps:          make(map[string]bool),
     282                monitored:     newCasemapMap(0),
    279283        }
    280284        dc.hostname = remoteAddr
     
    22542258                        Params:  []string{upstreamUser, upstreamChannel},
    22552259                })
     2260        case "MONITOR":
     2261                // MONITOR is unsupported in multi-upstream mode
     2262                uc := dc.upstream()
     2263                if uc == nil {
     2264                        return newUnknownCommandError(msg.Command)
     2265                }
     2266
     2267                var subcommand string
     2268                if err := parseMessageParams(msg, &subcommand); err != nil {
     2269                        return err
     2270                }
     2271
     2272                switch strings.ToUpper(subcommand) {
     2273                case "+", "-":
     2274                        var targets string
     2275                        if err := parseMessageParams(msg, nil, &targets); err != nil {
     2276                                return err
     2277                        }
     2278                        for _, target := range strings.Split(targets, ",") {
     2279                                if subcommand == "+" {
     2280                                        // Hard limit, just to avoid having downstreams fill our map
     2281                                        if len(dc.monitored.innerMap) >= 1000 {
     2282                                                dc.SendMessage(&irc.Message{
     2283                                                        Prefix:  dc.srv.prefix(),
     2284                                                        Command: irc.ERR_MONLISTFULL,
     2285                                                        Params:  []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
     2286                                                })
     2287                                                continue
     2288                                        }
     2289
     2290                                        dc.monitored.SetValue(target, nil)
     2291
     2292                                        if uc.monitored.Has(target) {
     2293                                                cmd := irc.RPL_MONOFFLINE
     2294                                                if online := uc.monitored.Value(target); online {
     2295                                                        cmd = irc.RPL_MONONLINE
     2296                                                }
     2297
     2298                                                dc.SendMessage(&irc.Message{
     2299                                                        Prefix:  dc.srv.prefix(),
     2300                                                        Command: cmd,
     2301                                                        Params:  []string{dc.nick, target},
     2302                                                })
     2303                                        }
     2304                                } else {
     2305                                        dc.monitored.Delete(target)
     2306                                }
     2307                        }
     2308                        uc.updateMonitor()
     2309                case "C": // clear
     2310                        dc.monitored = newCasemapMap(0)
     2311                        uc.updateMonitor()
     2312                case "L": // list
     2313                        // TODO: be less lazy and pack the list
     2314                        for _, entry := range dc.monitored.innerMap {
     2315                                dc.SendMessage(&irc.Message{
     2316                                        Prefix:  dc.srv.prefix(),
     2317                                        Command: irc.RPL_MONLIST,
     2318                                        Params:  []string{dc.nick, entry.originalKey},
     2319                                })
     2320                        }
     2321                        dc.SendMessage(&irc.Message{
     2322                                Prefix:  dc.srv.prefix(),
     2323                                Command: irc.RPL_ENDOFMONLIST,
     2324                                Params:  []string{dc.nick, "End of MONITOR list"},
     2325                        })
     2326                case "S": // status
     2327                        // TODO: be less lazy and pack the lists
     2328                        for _, entry := range dc.monitored.innerMap {
     2329                                target := entry.originalKey
     2330
     2331                                cmd := irc.RPL_MONOFFLINE
     2332                                if online := uc.monitored.Value(target); online {
     2333                                        cmd = irc.RPL_MONONLINE
     2334                                }
     2335
     2336                                dc.SendMessage(&irc.Message{
     2337                                        Prefix:  dc.srv.prefix(),
     2338                                        Command: cmd,
     2339                                        Params:  []string{dc.nick, target},
     2340                                })
     2341                        }
     2342                }
    22562343        case "CHATHISTORY":
    22572344                var subcommand string
  • trunk/irc.go

    r673 r684  
    409409}
    410410
     411func generateMonitor(subcmd string, targets []string) []*irc.Message {
     412        maxLength := maxMessageLength - len("MONITOR "+subcmd+" ")
     413
     414        var msgs []*irc.Message
     415        var buf []string
     416        n := 0
     417        for _, target := range targets {
     418                if n+len(target)+1 > maxLength {
     419                        msgs = append(msgs, &irc.Message{
     420                                Command: "MONITOR",
     421                                Params:  []string{subcmd, strings.Join(buf, ",")},
     422                        })
     423                        buf = buf[:0]
     424                        n = 0
     425                }
     426
     427                buf = append(buf, target)
     428                n += len(target) + 1
     429        }
     430
     431        if len(buf) > 0 {
     432                msgs = append(msgs, &irc.Message{
     433                        Command: "MONITOR",
     434                        Params:  []string{subcmd, strings.Join(buf, ",")},
     435                })
     436        }
     437
     438        return msgs
     439}
     440
    411441type joinSorter struct {
    412442        channels []string
     
    635665}
    636666
     667type monitorCasemapMap struct{ casemapMap }
     668
     669func (cm *monitorCasemapMap) Value(name string) (online bool) {
     670        entry, ok := cm.innerMap[cm.casemap(name)]
     671        if !ok {
     672                return false
     673        }
     674        return entry.value.(bool)
     675}
     676
    637677func isWordBoundary(r rune) bool {
    638678        switch r {
  • trunk/upstream.go

    r682 r684  
    104104        account       string
    105105        nextLabelID   uint64
     106        monitored     monitorCasemapMap
    106107
    107108        saslClient  sasl.Client
     
    210211                isupport:              make(map[string]*string),
    211212                pendingCmds:           make(map[string][]pendingUpstreamCommand),
     213                monitored:             monitorCasemapMap{newCasemapMap(0)},
    212214        }
    213215        return uc, nil
     
    14141416                        })
    14151417                })
     1418        case irc.RPL_MONONLINE, irc.RPL_MONOFFLINE:
     1419                var targetsStr string
     1420                if err := parseMessageParams(msg, nil, &targetsStr); err != nil {
     1421                        return err
     1422                }
     1423                targets := strings.Split(targetsStr, ",")
     1424
     1425                online := msg.Command == irc.RPL_MONONLINE
     1426                for _, target := range targets {
     1427                        prefix := irc.ParsePrefix(target)
     1428                        uc.monitored.SetValue(prefix.Name, online)
     1429                }
     1430
     1431                uc.forEachDownstream(func(dc *downstreamConn) {
     1432                        for _, target := range targets {
     1433                                prefix := irc.ParsePrefix(target)
     1434                                if dc.monitored.Has(prefix.Name) {
     1435                                        dc.SendMessage(&irc.Message{
     1436                                                Prefix:  dc.srv.prefix(),
     1437                                                Command: msg.Command,
     1438                                                Params:  []string{dc.nick, target},
     1439                                        })
     1440                                }
     1441                        }
     1442                })
     1443        case irc.ERR_MONLISTFULL:
     1444                var limit, targetsStr string
     1445                if err := parseMessageParams(msg, nil, &limit, &targetsStr); err != nil {
     1446                        return err
     1447                }
     1448
     1449                targets := strings.Split(targetsStr, ",")
     1450                uc.forEachDownstream(func(dc *downstreamConn) {
     1451                        for _, target := range targets {
     1452                                if dc.monitored.Has(target) {
     1453                                        dc.SendMessage(&irc.Message{
     1454                                                Prefix:  dc.srv.prefix(),
     1455                                                Command: msg.Command,
     1456                                                Params:  []string{dc.nick, limit, target},
     1457                                        })
     1458                                }
     1459                        }
     1460                })
    14161461        case irc.RPL_AWAY:
    14171462                var nick, reason string
     
    19131958        uch.updateAutoDetach(ch.DetachAfter)
    19141959}
     1960
     1961func (uc *upstreamConn) updateMonitor() {
     1962        add := make(map[string]struct{})
     1963        var addList []string
     1964        seen := make(map[string]struct{})
     1965        uc.forEachDownstream(func(dc *downstreamConn) {
     1966                for targetCM := range dc.monitored.innerMap {
     1967                        if !uc.monitored.Has(targetCM) {
     1968                                if _, ok := add[targetCM]; !ok {
     1969                                        addList = append(addList, targetCM)
     1970                                }
     1971                                add[targetCM] = struct{}{}
     1972                        } else {
     1973                                seen[targetCM] = struct{}{}
     1974                        }
     1975                }
     1976        })
     1977
     1978        removeAll := true
     1979        var removeList []string
     1980        for targetCM, entry := range uc.monitored.innerMap {
     1981                if _, ok := seen[targetCM]; ok {
     1982                        removeAll = false
     1983                } else {
     1984                        removeList = append(removeList, entry.originalKey)
     1985                }
     1986        }
     1987
     1988        // TODO: better handle the case where len(uc.monitored) + len(addList)
     1989        // exceeds the limit, probably by immediately sending ERR_MONLISTFULL?
     1990
     1991        if removeAll && len(addList) == 0 && len(removeList) > 0 {
     1992                // Optimization when the last MONITOR-aware downstream disconnects
     1993                uc.SendMessage(&irc.Message{
     1994                        Command: "MONITOR",
     1995                        Params:  []string{"C"},
     1996                })
     1997        } else {
     1998                msgs := generateMonitor("-", removeList)
     1999                msgs = append(msgs, generateMonitor("+", addList)...)
     2000                for _, msg := range msgs {
     2001                        uc.SendMessage(msg)
     2002                }
     2003        }
     2004
     2005        for _, target := range removeList {
     2006                uc.monitored.Delete(target)
     2007        }
     2008}
  • trunk/user.go

    r682 r684  
    343343        net.channels.SetCasemapping(newCasemap)
    344344        net.delivered.m.SetCasemapping(newCasemap)
    345         if net.conn != nil {
    346                 net.conn.channels.SetCasemapping(newCasemap)
    347                 for _, entry := range net.conn.channels.innerMap {
     345        if uc := net.conn; uc != nil {
     346                uc.channels.SetCasemapping(newCasemap)
     347                for _, entry := range uc.channels.innerMap {
    348348                        uch := entry.value.(*upstreamChannel)
    349349                        uch.Members.SetCasemapping(newCasemap)
    350350                }
    351         }
     351                uc.monitored.SetCasemapping(newCasemap)
     352        }
     353        net.forEachDownstream(func(dc *downstreamConn) {
     354                dc.monitored.SetCasemapping(newCasemap)
     355        })
    352356}
    353357
     
    520524
    521525                        uc.updateAway()
     526                        uc.updateMonitor()
    522527
    523528                        netIDStr := fmt.Sprintf("%v", uc.network.ID)
     
    589594                        dc := e.dc
    590595
     596                        if dc.network != nil {
     597                                dc.monitored.SetCasemapping(dc.network.casemap)
     598                        }
     599
    591600                        if err := dc.welcome(); err != nil {
    592601                                dc.logger.Printf("failed to handle new registered connection: %v", err)
     
    621630                        u.forEachUpstream(func(uc *upstreamConn) {
    622631                                uc.updateAway()
     632                                uc.updateMonitor()
    623633                        })
    624634                case eventDownstreamMessage:
Note: See TracChangeset for help on using the changeset viewer.