Skip to content

Instantly share code, notes, and snippets.

@geofffranks
Created August 29, 2023 16:03
Show Gist options
  • Save geofffranks/d534ee30e330847b7d608a81c01ecf1e to your computer and use it in GitHub Desktop.
Save geofffranks/d534ee30e330847b7d608a81c01ecf1e to your computer and use it in GitHub Desktop.
bind-wait-listen-later.go
package main
import (
"fmt"
"io"
"net"
"net/http"
"os"
"strconv"
"strings"
"syscall"
"time"
)
func main() {
go func() {
// run an equivalent command to what we're doing in BBS, on port 8081, for comparison
// `ss -tnlp` should show its listening with the same backlog as the socket on :8080
http.ListenAndServe("0.0.0.0:8081", nil)
}()
// create a socket file descriptor that we can use to bind/listen
sockfd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 6)
if err != nil {
panic(err)
}
// create the local address definition for our socket
sockaddr := &syscall.SockaddrInet4{
Addr: [4]byte(net.IPv4(0, 0, 0, 0)),
Port: 8080,
}
// bind the socket to this process - this will prevent other processes from using it
err = syscall.Bind(sockfd, sockaddr)
if err != nil {
panic(err)
}
fmt.Printf("Bound to socket: %v\n", sockaddr)
// simulate the time taken for BBS to gain the lock and become the primary/active instance
// during this period, no other processes can bind to :8080, but additionally nothing can connect
// to port 8080 (processes get connection refused errors)
time.Sleep(5 * time.Second)
// Finally listen on the socket. clients connecting will now get an established TCP connection
// this connection will hang until the http Server is serving though
err = syscall.Listen(sockfd, maxListenerBacklog())
if err != nil {
panic(err)
}
// create a Listner based off the socket file descriptor
listener, err := net.FileListener(os.NewFile(uintptr(sockfd), "server socket"))
if err != nil {
panic(err)
}
fmt.Printf("listening on socket: %v\n", sockaddr)
// simulate some kind of delay to view that tcp connections are now established but not responded to
time.Sleep(5 * time.Second)
// finally serve http requests. clients connected to in the previous 5 seconds will finally get responses unless the client had a <5s timeout
fmt.Printf("Serving HTTP requests\n")
server := &http.Server{}
err = server.Serve(listener)
if err != nil {
panic(err)
}
}
// pulls the somaxconn value configured in the kernel (typically 4096)
// if that fails, defaults to golangs syscall.SOMAXCONN (128)
// this logic was based off of how golang determines backlog on linux:
// https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/net/sock_linux.go;l=34
func maxListenerBacklog() int {
file, err := os.OpenFile("/proc/sys/net/core/somaxconn", os.O_RDONLY, 0x000)
if err != nil {
fmt.Printf("Couldn't open somaxconn: %s\n", err)
return syscall.SOMAXCONN
}
defer file.Close()
maxconnStr, err := io.ReadAll(file)
if err != nil {
fmt.Printf("Couldn't read somaxconn: %s\n", err)
return syscall.SOMAXCONN
}
n, err := strconv.Atoi(strings.TrimSpace(string(maxconnStr)))
if err != nil {
fmt.Printf("couldn't convert somaxconn to int: %s\n", err)
return syscall.SOMAXCONN
}
return n
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment