This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH v2] Accept proxy protocol on unix sockets

Message ID
DKIM signature
Download raw message
Patch: +34 -8
Now soju accepts PROXY only on specified unix sockets.

Below are some testing commands that i used.

Example soju configuration
listen unix:///tmp/sojuaccept.socket
listen unix:///tmp/soju.socket
accept-proxy-from unix:///tmp/sojuaccept.socket

Quick testing

soju rejects PROXY
$ echo -ne "PROXY TCP4 44554 6697\r\nQUIT\r\n" | socat unix-client:/tmp/soju.socket -

soju must accept
$ echo -ne "PROXY TCP4 44554 6697\r\nQUIT\r\n" | socat unix-client:/tmp/sojuaccept.socket -

Example usage behind reverse proxy
stream {
	server {
		listen 55555;
		proxy_pass unix:/tmp/sojuaccept.socket;
		proxy_protocol on;

soju accepts PROXY from nginx. Attackers PROXY is parsed as a common irc
$ echo -ne "PROXY TCP4 43215 6697\r\nQUIT\r\n" | socat tcp-connect:soju.irc:55555 -

 cmd/soju/main.go |  8 +++++---
 config/config.go | 23 ++++++++++++++++++++---
 doc/soju.1.scd   | 10 ++++++++--
 server.go        |  1 +
 4 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/cmd/soju/main.go b/cmd/soju/main.go
index 0094381..7e57582 100644
--- a/cmd/soju/main.go
+++ b/cmd/soju/main.go
@@ -89,6 +89,7 @@ func loadConfig() (*config.Server, *soju.Config, error) {
		LogPath:         raw.MsgStore.Source,
		HTTPOrigins:     raw.HTTPOrigins,
		AcceptProxyIPs:  raw.AcceptProxyIPs,
		AcceptProxyUnix: raw.AcceptProxyUnix,
		MaxUserNetworks: raw.MaxUserNetworks,
		UpstreamUserIPs: raw.UpstreamUserIPs,
		MOTD:            motd,
@@ -345,10 +346,11 @@ func proxyProtoListener(ln net.Listener, srv *soju.Server) net.Listener {
		Listener: ln,
		Policy: func(upstream net.Addr) (proxyproto.Policy, error) {
			tcpAddr, ok := upstream.(*net.TCPAddr)
			if !ok {
				return proxyproto.IGNORE, nil
			if ok && srv.Config().AcceptProxyIPs.Contains(tcpAddr.IP) {
				return proxyproto.USE, nil
			if srv.Config().AcceptProxyIPs.Contains(tcpAddr.IP) {
			unixAddr, ok := ln.Addr().(*net.UnixAddr)
			if ok && srv.Config().AcceptProxyUnix.Contains(unixAddr.String()) {
				return proxyproto.USE, nil
			return proxyproto.IGNORE, nil
diff --git a/config/config.go b/config/config.go
index 25233dd..7b3fe13 100644
--- a/config/config.go
+++ b/config/config.go
@@ -5,6 +5,7 @@ import (

@@ -32,6 +33,17 @@ var loopbackIPs = IPSet{

type UnixSet []string

func (set UnixSet) Contains(socket string) bool {
	for _, s := range set {
		if s == "unix" || strings.TrimPrefix(s, "unix://") == socket {
			return true
	return false

type TLS struct {
	CertPath, KeyPath string
@@ -54,8 +66,9 @@ type Server struct {
	DB       DB
	MsgStore MsgStore

	HTTPOrigins    []string
	AcceptProxyIPs IPSet
	HTTPOrigins     []string
	AcceptProxyIPs  IPSet
	AcceptProxyUnix UnixSet

	MaxUserNetworks int
	UpstreamUserIPs []*net.IPNet
@@ -135,13 +148,17 @@ func parse(cfg scfg.Block) (*Server, error) {
		case "http-origin":
			srv.HTTPOrigins = d.Params
		case "accept-proxy-ip":
		case "accept-proxy-from", "accept-proxy-ip":
			srv.AcceptProxyIPs = nil
			for _, s := range d.Params {
				if s == "localhost" {
					srv.AcceptProxyIPs = append(srv.AcceptProxyIPs, loopbackIPs...)
				if strings.HasPrefix(s, "unix") {
					srv.AcceptProxyUnix = append(srv.AcceptProxyUnix, s)
				_, n, err := net.ParseCIDR(s)
				if err != nil {
					return nil, fmt.Errorf("directive %q: failed to parse CIDR: %v", d.Name, err)
diff --git a/doc/soju.1.scd b/doc/soju.1.scd
index b5e398e..080c446 100644
--- a/doc/soju.1.scd
+++ b/doc/soju.1.scd
@@ -145,14 +145,20 @@ The following directives are supported:
	By default, only the request host is authorized. Use this directive to
	enable cross-origin WebSockets.

*accept-proxy-ip* <cidr...>
*accept-proxy-from* <cidr...>
	Allow the specified IPs to act as a proxy. Proxys have the ability to
	overwrite the remote and local connection addresses (via the PROXY protocol,
	the Forwarded HTTP header field defined in RFC 7239 or the X-Forwarded-\*
	HTTP header fields). The special name "localhost" accepts the loopback
	addresses and ::1/128.

	By default, all IPs are rejected.
	The special name "unix" accepts PROXY protocol on all unix sockets that
	soju is listening on. Use the more specific format "unix:///path/to/socket"
	to accept just on that socket.

	By default, all IPs and sockets are rejected.

	(_accept-proxy-ip_ is a deprecated alias for this directive.)

*max-user-networks* <limit>
	Maximum number of networks per user. By default, there is no limit.
diff --git a/server.go b/server.go
index dc209a3..1cad9f1 100644
--- a/server.go
+++ b/server.go
@@ -138,6 +138,7 @@ type Config struct {
	LogPath         string
	HTTPOrigins     []string
	AcceptProxyIPs  config.IPSet
	AcceptProxyUnix config.UnixSet
	MaxUserNetworks int
	MOTD            string
	UpstreamUserIPs []*net.IPNet
Message ID
<20221019084751.48995-1-julio.bacel@gmail.com> (view parent)
DKIM signature
Download raw message
Hm, I'd prefer not to add the filtering based on Unix socket names.
The socket name is not a source address like an IP address is, it's a
destination address. If we were to add it, I think accepting a block
in listen directives would be better:

    listen unix://… {
        accept-proxy-from unix

But I'd rather not add it before someone comes with a use-case for it.
Reply to thread Export thread (mbox)