Add per-user IP addresses
The new upstream-user-ip directive allows bouncer operators to assign one IP address per user.
This commit is contained in:
parent
97152191ad
commit
55840312b4
@ -93,6 +93,7 @@ func loadConfig() (*config.Server, *soju.Config, error) {
|
||||
AcceptProxyIPs: raw.AcceptProxyIPs,
|
||||
MaxUserNetworks: raw.MaxUserNetworks,
|
||||
MultiUpstream: raw.MultiUpstream,
|
||||
UpstreamUserIPs: raw.UpstreamUserIPs,
|
||||
Debug: debug,
|
||||
MOTD: motd,
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ type Server struct {
|
||||
|
||||
MaxUserNetworks int
|
||||
MultiUpstream bool
|
||||
UpstreamUserIPs []*net.IPNet
|
||||
}
|
||||
|
||||
func Defaults() *Server {
|
||||
@ -150,6 +151,29 @@ func parse(cfg scfg.Block) (*Server, error) {
|
||||
return nil, fmt.Errorf("directive %q: %v", d.Name, err)
|
||||
}
|
||||
srv.MultiUpstream = v
|
||||
case "upstream-user-ip":
|
||||
if len(srv.UpstreamUserIPs) > 0 {
|
||||
return nil, fmt.Errorf("directive %q: can only be specified once", d.Name)
|
||||
}
|
||||
var hasIPv4, hasIPv6 bool
|
||||
for _, s := range d.Params {
|
||||
_, n, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("directive %q: failed to parse CIDR: %v", d.Name, err)
|
||||
}
|
||||
if n.IP.To4() == nil {
|
||||
if hasIPv6 {
|
||||
return nil, fmt.Errorf("directive %q: found two IPv6 CIDRs", d.Name)
|
||||
}
|
||||
hasIPv6 = true
|
||||
} else {
|
||||
if hasIPv4 {
|
||||
return nil, fmt.Errorf("directive %q: found two IPv4 CIDRs", d.Name)
|
||||
}
|
||||
hasIPv4 = true
|
||||
}
|
||||
srv.UpstreamUserIPs = append(srv.UpstreamUserIPs, n)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown directive %q", d.Name)
|
||||
}
|
||||
|
@ -156,6 +156,15 @@ The following directives are supported:
|
||||
Globally enable or disable multi-upstream mode. By default, multi-upstream
|
||||
mode is enabled.
|
||||
|
||||
*upstream-user-ip* <cidr...>
|
||||
Enable per-user IP addresses. One IPv4 range and/or one IPv6 range can be
|
||||
specified in CIDR notation. One IP address per range will be assigned to
|
||||
each user and will be used as the source address when connecting to an
|
||||
upstream network.
|
||||
|
||||
This can be useful to avoid having the whole bouncer banned from an upstream
|
||||
network because of one malicious user.
|
||||
|
||||
# IRC SERVICE
|
||||
|
||||
soju exposes an IRC service called *BouncerServ* to manage the bouncer.
|
||||
|
@ -64,6 +64,7 @@ type Config struct {
|
||||
MaxUserNetworks int
|
||||
MultiUpstream bool
|
||||
MOTD string
|
||||
UpstreamUserIPs []*net.IPNet
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
|
16
upstream.go
16
upstream.go
@ -140,6 +140,11 @@ func connectToUpstream(network *network) (*upstreamConn, error) {
|
||||
addr = u.Host + ":6697"
|
||||
}
|
||||
|
||||
dialer.LocalAddr, err = network.user.localTCPAddrForHost(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pick local IP for remote host %q: %v", host, err)
|
||||
}
|
||||
|
||||
logger.Printf("connecting to TLS server at address %q", addr)
|
||||
|
||||
tlsConfig := &tls.Config{ServerName: host, NextProtos: []string{"irc"}}
|
||||
@ -174,8 +179,15 @@ func connectToUpstream(network *network) (*upstreamConn, error) {
|
||||
netConn = tls.Client(netConn, tlsConfig)
|
||||
case "irc+insecure":
|
||||
addr := u.Host
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
addr = addr + ":6667"
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
host = u.Host
|
||||
addr = u.Host + ":6667"
|
||||
}
|
||||
|
||||
dialer.LocalAddr, err = network.user.localTCPAddrForHost(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pick local IP for remote host %q: %v", host, err)
|
||||
}
|
||||
|
||||
logger.Printf("connecting to plain-text server at address %q", addr)
|
||||
|
45
user.go
45
user.go
@ -6,6 +6,8 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"gopkg.in/irc.v3"
|
||||
@ -956,3 +958,46 @@ func (u *user) hasPersistentMsgStore() bool {
|
||||
_, isMem := u.msgStore.(*memoryMessageStore)
|
||||
return !isMem
|
||||
}
|
||||
|
||||
// localAddrForHost returns the local address to use when connecting to host.
|
||||
// A nil address is returned when the OS should automatically pick one.
|
||||
func (u *user) localTCPAddrForHost(host string) (*net.TCPAddr, error) {
|
||||
upstreamUserIPs := u.srv.Config().UpstreamUserIPs
|
||||
if len(upstreamUserIPs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wantIPv6 := false
|
||||
for _, ip := range ips {
|
||||
if ip.To4() == nil {
|
||||
wantIPv6 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var ipNet *net.IPNet
|
||||
for _, in := range upstreamUserIPs {
|
||||
if wantIPv6 == (in.IP.To4() == nil) {
|
||||
ipNet = in
|
||||
break
|
||||
}
|
||||
}
|
||||
if ipNet == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ipInt big.Int
|
||||
ipInt.SetBytes(ipNet.IP)
|
||||
ipInt.Add(&ipInt, big.NewInt(u.ID+1))
|
||||
ip := net.IP(ipInt.Bytes())
|
||||
if !ipNet.Contains(ip) {
|
||||
return nil, fmt.Errorf("IP network %v too small", ipNet)
|
||||
}
|
||||
|
||||
return &net.TCPAddr{IP: ip}, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user