AjMyyra
AjMyyra

Reputation: 93

HTTP server from TCP socket (in Go)

I'm trying to create a TCP socket in Go, bind it into a VRF interface and to establish a HTTP server in that specific interface. The VRF binding works correctly, but starting the HTTP server returns an error stating "accept tcp 127.0.0.1:80: accept: invalid argument". Am I right to assume, that the socket is somehow defective and I'm creating it wrong?

Below is the simplified version reproducing the problem. VRF part is commented out as it doesn't affect the actual problem, but I'm leaving it here as I'm trying to avoid people telling me to just use net.Listen instead of sockets. VRF needs to be binded into first before it can be used so net.Listen isn't unfortunately an option.

package main

import (
    "fmt"
    "net"
    "net/http"
    "os"
    "syscall"
)

func main() {
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err != nil {
        fmt.Printf("Error creating socket: %v", err)
        os.Exit(1)
    }

    // if err = syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "vrfiface"); err != nil {
    //  fmt.Printf("Error binding to vrf: %v", err)
    //  os.Exit(1)
    // }

    sockAddr := &syscall.SockaddrInet4{
        Port: 80,
        Addr: [4]byte{127, 0, 0, 1},
    }

    if err = syscall.Bind(fd, sockAddr); err != nil {
        fmt.Printf("Error binding to IP and port: %v", err)
        os.Exit(1)
    }

    file := os.NewFile(uintptr(fd), "socketfile")
    if file == nil {
        fmt.Println("Error creating file")
        os.Exit(1)
    }

    listener, err := net.FileListener(file)
    if err != nil {
        fmt.Printf("Error creating a listener: %v", err)
        os.Exit(1)
    }

    http.HandleFunc("/", TestServer)
    if err = http.Serve(listener, nil); err != nil {
        fmt.Printf("Error serving HTTP requests: %v", err)
    }
}

func TestServer(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Test, %s!", r.URL.Path[1:])
}

Any pointers on solving this would be appreciated. Thank you!

Upvotes: 4

Views: 3184

Answers (2)

JimB
JimB

Reputation: 109327

You can use a net.ListenConfig to inject the socket options you want before syscall.Bind is called. This also makes sure the socket setup is completed correctly, and in the same manner as expected by the net package.

The ListenConfig.Control function gives you a syscall.RawConn on which to call Control with a closure, where you have access to the raw file descriptor being using during the socket setup.

func main() {
    lc := net.ListenConfig{Control: controlOnConnSetup}

    ln, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:80")
    if err != nil {
        log.Fatal(err)
    }
    ln.Close()
}

func controlOnConnSetup(network string, address string, c syscall.RawConn) error {
    var operr error
    fn := func(fd uintptr) {
        operr = syscall.SetsockoptString(int(fd), syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "vrfiface")
    }
    if err := c.Control(fn); err != nil {
        return err
    }
    if operr != nil {
        return operr
    }
    return nil
}

Upvotes: 5

Steffen Ullrich
Steffen Ullrich

Reputation: 123260

As mentioned in a comment by C Han already: you need to listen. The sequence of creating a server is to create the socket, bind to the IP and port, call listen and then accept incoming connections. If you forget to listen then you'll get the error you see. Thus:

if err = syscall.Bind(fd, sockAddr); err != nil {
     ...
}

if err = syscall.Listen(fd, 10); err != nil {
    fmt.Printf("Error listening %v", err)
    os.Exit(1)
}

Upvotes: 1

Related Questions