HTTP-сервер из сокета TCP (в Go) - PullRequest
3 голосов
/ 21 февраля 2020

Я пытаюсь создать TCP-сокет в Go, связать его с VRF-интерфейсом и установить sh HTTP-сервер в этом указанном c интерфейсе. Привязка VRF работает правильно, но при запуске HTTP-сервера возвращается сообщение об ошибке "accept tcp 127.0.0.1:80: accept: неверный аргумент" . Правильно ли я предполагаю, что сокет каким-то образом неисправен, и я создаю его неправильно?

Ниже приведена упрощенная версия, воспроизводящая проблему. Часть VRF закомментирована, так как она не влияет на реальную проблему, но я оставляю ее здесь, так как стараюсь избегать, чтобы люди говорили мне просто использовать net .Listen вместо сокетов. VRF необходимо связать с первым, прежде чем его можно будет использовать, поэтому net .Listen, к сожалению, не вариант.

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

Буду признателен за любые указания по решению этой проблемы. Спасибо!

Ответы [ 2 ]

2 голосов
/ 21 февраля 2020

Вы можете использовать net.ListenConfig, чтобы ввести нужные параметры сокета до вызова syscall.Bind. Это также гарантирует, что настройка сокета завершена правильно и таким же образом, как и ожидалось пакетом net.

Функция ListenConfig.Control дает вам syscall.RawConn, на котором для вызова Control с закрытием, где у вас есть доступ к необработанному файловому дескриптору, используемому во время установки сокета.

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
}
2 голосов
/ 21 февраля 2020

Как уже упоминалось в комментарии C Хан уже: вы должны слушать. Последовательность создания сервера заключается в создании сокета, привязке к IP-адресу и порту, вызове listen и принятии входящих подключений. Если вы забудете listen , вы получите сообщение об ошибке. Таким образом:

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