net.Listen -- How to listen to ipv6 & ipv4 local-loopback BUT NOT external interfaces

327 views
Skip to first unread message

Tony M

unread,
Jan 10, 2024, 3:54:12 PM1/10/24
to golang-nuts
How do I listen on all local / loopback ipv4 /ipv6 interfaces?  net.Listen('tcp', ':9999') listens on all interfaces. For my application (oauth token listener) I only want to listen to local / loopback

go doc Net.listen: 
 if the host in the address parameter is empty or a literal
    unspecified IP address, Listen listens on all available unicast and anycast
    IP addresses of the local system.


My current workarounds are not ideal : 
* implement MultiListener interface with 2 listeners\
* Listen on 2 net.Listen listeners with 2 http.Serve servers


 Example test with netstat showing the issue.
 SEE EXAMPLE CODE BELOW

# Listen :9999
$ go run . -i all & 
$ netstat -tulnp |grep v2
tcp6       0      0 :::9999                 :::*                    LISTEN      28107/v2   

# listen [127.0.0.1]:9999
$ go run . -i ipv4 &
$ netstat -tulnp |grep v2
tcp        0      0 127.0.0.1:9999          0.0.0.0:*               LISTEN      29244/v2     

# listen [::1]:9999
$ go run . -i ipv6
$ netstat -tulnp |grep v2
tcp6       0      0 ::1:9999                :::*                    LISTEN      30163/v2  

Telnet v4 does not work with v6 listen: 
$ go run . -i ipv6
$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused



Example Code

package main

import (
"flag"
"fmt"
"net"
"time"
)

func main() {
optListen := flag.String("i", "all", "all / ipv6, ipv4")

addrMap := map[string]string{
"all":  ":9999",
"ipv6": "[::1]:9999",
"ipv4": "[127.0.0.1]:9999",
}

flag.Parse()

fmt.Printf("Listen: %s\n", *optListen)
addr, ok := addrMap[*optListen]
if !ok {
panic("not found")
}
if l1, err := net.Listen("tcp", addr); err == nil {
fmt.Printf("l1: %s\n", l1.Addr().String())
} else {
panic(err)
}

time.Sleep(30 * time.Second)
}



p...@morth.org

unread,
Jan 11, 2024, 3:27:22 AM1/11/24
to golang-nuts
Hi,

Unfortunately, the only way to avoid having two sockets in this scenario, is to listen to only one of 127.0.0.1 or ::1 and live with that it's only one address family.
As you might've noticed, some tools still struggle with connecting to ::1 so listening to 127.0.0.1 is the most safe option.
Most client will try to connect to both ::1 and 127.0.0.1 when given localhost as target. In the case of browsers, they usually do it in parallel and use the quickest succeeding connection (this is called Happy Eyeballs if you wish to Google it).

The reason it works this way is that you bind the socket to an address, either ::1 or 127.0.0.1. There's no address that binds to multiple address families, except for :: that listen on all interfaces.

Regards,
Per Johansson

Tony M

unread,
Aug 6, 2025, 6:45:41 PMAug 6
to golang-nuts
I came up with a clever / elegant solution to this and I would like your feedback.    This approach minimizes the http.Server & http.handleFunc structs needed to listen on both local interfaces.  


func ListenWithDualListener() {
dual, err := multilistener.NewLocalLoopback("8080")
if err != nil {
panic(err)
}
defer dual.Close()
fmt.Printf("Serving HTTP %+v\n", dual.AllAddr())
fmt.Printf("Preferred Addr: %+v\n", dual.Addr())
if serveErr := http.Serve(dual, nil); serveErr != nil {
fmt.Println("http.Serve error:", serveErr)
}
Reply all
Reply to author
Forward
0 new messages