golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.97k stars 17.53k forks source link

x/net/icmp: listen icmp in Windows not work properly #38427

Open lochv opened 4 years ago

lochv commented 4 years ago

What version of Go are you using (go version)?

$ go version
go version go1.13.8 darwin/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/censored/Library/Caches/go-build"
GOENV="/Users/censored/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/censored/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.13.8/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.13.8/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/censored/go/src/srt/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/4s/1bq_2censoredw0000gn/T/go-build488770590=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I write a func to detect who ping to my computer

conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
    if err != nil {
        log.Fatalf("listen err, %s", err)
    }
    defer conn.Close()

    if err != nil {
         return
    }
    bytes := make([]byte, 512)
    for {
        fmt.Println("recv")
        n,_, err := conn.ReadFrom(bytes)
        if err != nil {
            fmt.Println(err.Error())
            continue
        }
        fmt.Println(n)
    }

What did you expect to see?

fmt.Println(n) every ping.

What did you see instead?

sometime it work, sometime it didn't work. PoC video: https://youtu.be/AyQDH9AQSRc

lochv commented 4 years ago

@andybons hi,

it seems SIO_RCVALL flag wasn't set, so only recv when network adapter just re-enabled. https://github.com/golang/net/search?q=RCVALL&unscoped_q=RCVALL

andybons commented 4 years ago

@alexbrainman

alexbrainman commented 4 years ago

@lochv I don't know anything about icmp. I don't want to confuse you.

Alex

lochv commented 4 years ago

@alexbrainman the problem isn't icmp, problem 1: in windows, socket need set SIO_RCVALL flag to monitor raw sock (linux didn't). problem 2: in windows, socket bind on 0.0.0.0 canot set SIO_RCVALL flag. so

icmp.ListenPacket("ip4:icmp", "0.0.0.0")

will not work in windows.

Need to port this example code from C to Go for icmp.ListenPacket work in windows

int F() {
    WSADATA            wsd;
    char hostname[100];
    struct hostent* local;
    struct in_addr addr;

    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
    {
        return 0;
    }

    SOCKET RecvSockets[20];
    ZeroMemory(&RecvSockets, sizeof(RecvSockets));

    gethostname(hostname, sizeof(hostname));
    local = gethostbyname(hostname);
    struct sockaddr_in RecvAddr;

    fd_set readfds;

    char *RecvBuf;
    RecvBuf = (char*)malloc(1024*sizeof(char));
    int BufLen = 1024;
    int iResult;
    struct sockaddr_in SenderAddr;
    int SenderAddrSize = sizeof(SenderAddr);
        // need listen on all interfaces one by one
    for (int i = 0; local->h_addr_list[i] != 0; i++)
    {
        DWORD  flag = RCVALL_ON;
        memcpy(&addr, local->h_addr_list[i], sizeof(struct in_addr));
        memcpy(&RecvAddr.sin_addr.s_addr, local->h_addr_list[i], sizeof(RecvAddr.sin_addr.s_addr));
        RecvAddr.sin_family = AF_INET;
        RecvAddr.sin_port = htons(0);
        RecvSockets[i] = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        bind(RecvSockets[i], (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));
        ioctlsocket(RecvSockets[i], SIO_RCVALL, &flag);
    }
    int activity;
    WCHAR szAddr[20] = { 0 };
    while (1) {
        FD_ZERO(&readfds);
        for (int i = 0; local->h_addr_list[i] != 0; i++)
        {
            FD_SET(RecvSockets[i], &readfds);
        }
        printf("read active");
        activity = select(0, &readfds, NULL, NULL, NULL);
        if (activity == SOCKET_ERROR)
        {
            printf("activity");
            wprintf(L"recvfrom failed with error %d\n", WSAGetLastError());

        }
        for (int i = 0; local->h_addr_list[i] != 0; i++)
        {
            SOCKET s = RecvSockets[i];
            if (FD_ISSET(s, &readfds))
            {
                ZeroMemory(RecvBuf, BufLen);
                iResult = recv(s, RecvBuf, BufLen, 0);
                if (iResult == SOCKET_ERROR)
                    continue;
                if (iResult == 0)
                    continue;
                printf("%d", iResult);
            }
        }
    }
}
alexbrainman commented 4 years ago

the problem isn't icmp, problem 1: in windows, socket need set SIO_RCVALL flag to monitor raw sock (linux didn't). problem 2: in windows, socket bind on 0.0.0.0 canot set SIO_RCVALL flag.

@lochv I know nothing about these things. If you want to fix the code, here is how to contribute

https://golang.org/doc/contribute.html

Maybe altogether we can fix the code.

Thank you.

Alex

dhaavi commented 4 years ago

If anyone is still interesting in this issue, I managed to somewhat find a fix. Substantial caveat: Receiving ICMP packets from any source only seems to work if the Windows firewall is disabled. 🤷‍♂️ If someone has more insight into this, I'd appreciate a pointer to more information.

Here is the function that creates the listener that can send and receive ICMP packets from any source. Here is our special (partial) traceroute function using it.

lochv commented 4 years ago

@dhaavi dhaa i think the problem is how to modify "net" package: can listen icmp in windows + keep design pattern