diff --git a/irc/client.go b/irc/client.go index b56a1768..c199739d 100644 --- a/irc/client.go +++ b/irc/client.go @@ -74,7 +74,7 @@ type Client struct { // NewClient returns a client with all the appropriate info setup. func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { now := time.Now() - socket := NewSocket(conn) + socket := NewSocket(conn, server.MaxSendQBytes) go socket.RunSocketWriter() client := &Client{ atime: now, diff --git a/irc/config.go b/irc/config.go index 395c8d54..ac66cb7f 100644 --- a/irc/config.go +++ b/irc/config.go @@ -14,6 +14,8 @@ import ( "strings" "time" + "code.cloudfoundry.org/bytefmt" + "github.com/DanielOaks/oragono/irc/custime" "github.com/DanielOaks/oragono/irc/logger" "gopkg.in/yaml.v2" @@ -171,6 +173,8 @@ type Config struct { RestAPI RestAPIConfig `yaml:"rest-api"` CheckIdent bool `yaml:"check-ident"` MOTD string + MaxSendQString string `yaml:"max-sendq"` + MaxSendQBytes uint64 ConnectionLimits ConnectionLimitsConfig `yaml:"connection-limits"` ConnectionThrottle ConnectionThrottleConfig `yaml:"connection-throttling"` } @@ -433,5 +437,10 @@ func LoadConfig(filename string) (config *Config, err error) { } config.Logging = newLogConfigs + config.Server.MaxSendQBytes, err = bytefmt.ToBytes(config.Server.MaxSendQString) + if err != nil { + return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error()) + } + return config, nil } diff --git a/irc/server.go b/irc/server.go index 8d4466ba..2c8238fe 100644 --- a/irc/server.go +++ b/irc/server.go @@ -104,6 +104,7 @@ type Server struct { listeners map[string]ListenerInterface listenerUpdateMutex sync.Mutex logger *logger.Manager + MaxSendQBytes uint64 monitoring map[string][]Client motdLines []string name string @@ -211,6 +212,7 @@ func NewServer(configFilename string, config *Config, logger *logger.Manager) (* }, listeners: make(map[string]ListenerInterface), logger: logger, + MaxSendQBytes: config.Server.MaxSendQBytes, monitoring: make(map[string][]Client), name: config.Server.Name, nameCasefolded: casefoldedName, @@ -1413,6 +1415,18 @@ func (server *Server) rehash() error { accountReg := NewAccountRegistration(config.Accounts.Registration) server.accountRegistration = &accountReg + // set new sendqueue size + if config.Server.MaxSendQBytes != server.MaxSendQBytes { + server.MaxSendQBytes = config.Server.MaxSendQBytes + + // update on all clients + server.clients.ByNickMutex.RLock() + for _, sClient := range server.clients.ByNick { + sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes + } + server.clients.ByNickMutex.RUnlock() + } + // set RPL_ISUPPORT oldISupportList := server.isupport server.setISupport() diff --git a/irc/socket.go b/irc/socket.go index 3c76649e..37c7cd0f 100644 --- a/irc/socket.go +++ b/irc/socket.go @@ -30,16 +30,19 @@ type Socket struct { conn net.Conn reader *bufio.Reader + MaxSendQBytes uint64 + lineToSendExists chan bool linesToSend []string linesToSendMutex sync.Mutex } // NewSocket returns a new Socket. -func NewSocket(conn net.Conn) Socket { +func NewSocket(conn net.Conn, maxSendQBytes uint64) Socket { return Socket{ conn: conn, reader: bufio.NewReader(conn), + MaxSendQBytes: maxSendQBytes, lineToSendExists: make(chan bool), } } @@ -130,6 +133,19 @@ func (socket *Socket) RunSocketWriter() { case <-socket.lineToSendExists: socket.linesToSendMutex.Lock() + // check sendq + var sendQBytes uint64 + for _, line := range socket.linesToSend { + sendQBytes += uint64(len(line)) + if socket.MaxSendQBytes < sendQBytes { + break + } + } + if socket.MaxSendQBytes < sendQBytes { + socket.conn.Write([]byte("\r\nERROR :SendQ Exceeded\r\n")) + break + } + // get data data := socket.linesToSend[0] if len(socket.linesToSend) > 1 { diff --git a/oragono.yaml b/oragono.yaml index d9eec794..5d8cbf7f 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -65,6 +65,9 @@ server: # if you change the motd, you should move it to ircd.motd motd: oragono.motd + # maximum length of clients' sendQ in bytes + max-sendq: 16k + # maximum number of connections per subnet connection-limits: # whether to throttle limits or not