Index: trunk/cmd/soju/main.go
===================================================================
--- trunk/cmd/soju/main.go	(revision 704)
+++ trunk/cmd/soju/main.go	(revision 705)
@@ -94,4 +94,5 @@
 		MaxUserNetworks: raw.MaxUserNetworks,
 		MultiUpstream:   raw.MultiUpstream,
+		UpstreamUserIPs: raw.UpstreamUserIPs,
 		Debug:           debug,
 		MOTD:            motd,
Index: trunk/config/config.go
===================================================================
--- trunk/config/config.go	(revision 704)
+++ trunk/config/config.go	(revision 705)
@@ -53,4 +53,5 @@
 	MaxUserNetworks int
 	MultiUpstream   bool
+	UpstreamUserIPs []*net.IPNet
 }
 
@@ -151,4 +152,27 @@
 			}
 			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)
Index: trunk/doc/soju.1.scd
===================================================================
--- trunk/doc/soju.1.scd	(revision 704)
+++ trunk/doc/soju.1.scd	(revision 705)
@@ -157,4 +157,13 @@
 	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
 
Index: trunk/server.go
===================================================================
--- trunk/server.go	(revision 704)
+++ trunk/server.go	(revision 705)
@@ -65,4 +65,5 @@
 	MultiUpstream   bool
 	MOTD            string
+	UpstreamUserIPs []*net.IPNet
 }
 
Index: trunk/upstream.go
===================================================================
--- trunk/upstream.go	(revision 704)
+++ trunk/upstream.go	(revision 705)
@@ -141,4 +141,9 @@
 		}
 
+		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)
 
@@ -175,6 +180,13 @@
 	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)
 		}
 
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 704)
+++ trunk/user.go	(revision 705)
@@ -7,4 +7,6 @@
 	"encoding/hex"
 	"fmt"
+	"math/big"
+	"net"
 	"time"
 
@@ -957,2 +959,45 @@
 	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
+}
