Reputation: 93
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
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
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