简体   繁体   中英

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!

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
}

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)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM