Golang net.Listen связывает с портом, который уже используется - PullRequest
0 голосов
/ 27 июня 2018

Порт 8888 уже связан с моей системой (OS X 10.13.5) процессом, выполняющимся внутри док-контейнера:

$ netstat -an | grep 8888
tcp6       0      0  ::1.8888               *.*                    LISTEN
tcp4       0      0  *.8888                 *.*                    LISTEN

Программа на Python, которая пытается привязаться к этому порту (используя как можно более близкие параметры сокетов golang), терпит неудачу так, как я ожидаю:

import socket
import fcntl
import os


def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    flag = fcntl.fcntl(sock.fileno(), fcntl.F_GETFL)
    fcntl.fcntl(sock.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    sock.bind(("0.0.0.0", 8888))
    sock.listen(5)


main()

терпит неудачу с:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    main()
  File "test.py", line 11, in main
    sock.bind(("0.0.0.0", 8888))
OSError: [Errno 48] Address already in use

Но программа go, создающая соединение через net.Listen, не перестает работать, как я ожидаю:

package main

import (
    "fmt"
    "net"
)

func main() {
    _, err := net.Listen("tcp", "0.0.0.0:8888")
    if err != nil {
        fmt.Printf("Connection error: %s\n", err)
    } else {
        fmt.Println("Listening")
    }
}

Успешно с:

$ go run test.go
Listening

Сотрудник сообщает, что при такой же настройке его система Ubuntu корректно завершает работу программы go.

Почему это удается на Mac и как я могу получить net.Listen, чтобы показать ошибку в привязке к порту 8888?

edit: если я занимаю порт 8888 с помощью простой программы go, например:

package main

import (
    "log"
    "net/http"
)

func main() {
    log.Fatal(http.ListenAndServe("0.0.0.0:8888", nil))
}

Тогда test.go корректно не привязывается к порту. Однако процесс докера (который в основном выполняется ^^^) не приводит к сбою.

edit 2: Если я укажу «tcp4», то программа действительно завершится с ошибкой, как я и ожидал. Если я укажу «tcp6», он будет успешным, но netstat говорит, что он связывается с * вместо ::1:

$ netstat -an | grep 8888
tcp6       0      0  *.8888                 *.*                    LISTEN
tcp6       0      0  ::1.8888               *.*                    LISTEN
tcp4       0      0  *.8888                 *.*                    LISTEN

Итак, указание «tcp4» решит мою настоящую проблему, но я действительно хочу понять, что происходит с типом соединения «tcp46», и я не могу найти никакой документации. Помогите!

Ответы [ 2 ]

0 голосов
/ 28 июня 2018

Относительно вывода tcp46 из netstat я не могу найти никакой документации, но нашел соответствующий источник.

С network_cmds-543 / netstat.proj / inet.c :

void
protopr(uint32_t proto,     /* for sysctl version we pass proto # */
        char *name, int af)
{

...

    struct xinpcb_n *inp = NULL;

...

            const char *vchar;

#ifdef INET6
            if ((inp->inp_vflag & INP_IPV6) != 0)
                vchar = ((inp->inp_vflag & INP_IPV4) != 0)
                ? "46" : "6 ";
            else
#endif
                vchar = ((inp->inp_vflag & INP_IPV4) != 0)
                ? "4 " : "  ";

xinpcb_n определено в bsd / netinet / in_pcb.h .

 * struct inpcb captures the network layer state for TCP, UDP and raw IPv6
 * and IPv6 sockets.

В другом месте в этом файле inp_vflag задокументировано как /* INP_IPV4 or INP_IPV6 */. Они, в свою очередь, определены как:

#define INP_IPV4    0x1
#define INP_IPV6    0x2

Таким образом, в основном, когда в сокете установлены биты v4 и v6, в столбце протокола будет отображаться 46.


Что касается Go, в сетевом пакете есть эта socket() функция :

func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {

...

if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {

setDefaultSockopts() имеет для каждой платформы определения, вот выдержка из варианта BSD :

func setDefaultSockopts(s, family, sotype int, ipv6only bool) error {

...

    if supportsIPv4map() && family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
        // Allow both IP versions even if the OS default
        // is otherwise. Note that some operating systems
        // never admit this option.
        syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))

Итак, разрешение обеих версий IP для сокета определяется логическим значением ipv6Only. После еще нескольких копаний я нашел , где это решено и включает подробное объяснение логики:

// favoriteAddrFamily returns the appropriate address family for the
// given network, laddr, raddr and mode.
//
// If mode indicates "listen" and laddr is a wildcard, we assume that
// the user wants to make a passive-open connection with a wildcard
// address family, both AF_INET and AF_INET6, and a wildcard address
// like the following:
//
//  - A listen for a wildcard communication domain, "tcp" or
//    "udp", with a wildcard address: If the platform supports
//    both IPv6 and IPv4-mapped IPv6 communication capabilities,
//    or does not support IPv4, we use a dual stack, AF_INET6 and
//    IPV6_V6ONLY=0, wildcard address listen. The dual stack
//    wildcard address listen may fall back to an IPv6-only,
//    AF_INET6 and IPV6_V6ONLY=1, wildcard address listen.
//    Otherwise we prefer an IPv4-only, AF_INET, wildcard address
//    listen.
//
//  - A listen for a wildcard communication domain, "tcp" or
//    "udp", with an IPv4 wildcard address: same as above.
//
//  - A listen for a wildcard communication domain, "tcp" or
//    "udp", with an IPv6 wildcard address: same as above.
//
//  - A listen for an IPv4 communication domain, "tcp4" or "udp4",
//    with an IPv4 wildcard address: We use an IPv4-only, AF_INET,
//    wildcard address listen.
//
//  - A listen for an IPv6 communication domain, "tcp6" or "udp6",
//    with an IPv6 wildcard address: We use an IPv6-only, AF_INET6
//    and IPV6_V6ONLY=1, wildcard address listen.
//
// Otherwise guess: If the addresses are IPv4 then returns AF_INET,
// or else returns AF_INET6. It also returns a boolean value what
// designates IPV6_V6ONLY option.
//
// Note that the latest DragonFly BSD and OpenBSD kernels allow
// neither "net.inet6.ip6.v6only=1" change nor IPPROTO_IPV6 level
// IPV6_V6ONLY socket option setting.
0 голосов
/ 28 июня 2018

ОК, я думаю, у меня есть история, чтобы рассказать о том, почему это происходит:

  1. Docker на Mac при подключении порта привязывается к IPv4 0.0.0.0:<port> и IPv6 [::1]:<port>. Обратите внимание, что в IPv6 он сопоставляется с эквивалентом localhost, а не 0.0.0.0, что будет [::]!
  2. Golang, открывая сокет для прослушивания, по умолчанию открывает сокет IPv6, который каким-то образом сопоставлен с прослушиванием IPv4. (Я до сих пор не совсем понимаю этот тип tcp46, поэтому, если у вас есть хорошие документы , пожалуйста, укажите мне на них!).
  3. Итак, моя программа golang открывала сокет IPv6 на [::]:8888, что эквивалентно 0.0.0.0:8888 в IPv6. Это удалось, потому что Docker прослушивал [::1] (эквивалент 127.0.0.1), not [::]
  4. Вот и все! Таким образом, программа golang завершилась успешно, хотя она будет подключена только к клиенту, подключающемуся по IPv6 с адреса без обратной связи (я думаю, я слишком устал, чтобы проверить это, дай мне перерыв)

Мой коллега сообщает, что в Ubuntu докер прослушивает [::], поэтому он не смог воспроизвести проблему, с которой я столкнулся. Это похоже на разумное поведение! И я понятия не имею, почему это не работает на Mac.

Я также думаю, что это удивительно и, возможно, немного неправильно, что Go успешно работает в этом случае, даже несмотря на то, что он создает сокет, к которому очень трудно получить доступ? Но я не могу сказать, что это определенно ошибка, и я определенно не чувствую, что пытаюсь сообщить об этом в проект go.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...