2016-03-23 7 views
0

Ich versuche, eine ICMP-Nachricht zu senden, deren TTL nur 1 ist, und erwarte, dass eine Zeitüberschreitungsnachricht empfangen wird. diese Nachricht kommt (ich sehe es von wireshark), aber mein Programm blockiert auf syscall.Recvfrom. Wer weiß warum?
icmp.goRaw-Socket hat keine ICMP-Antwort erhalten

package main 

import (
    "bytes" 
    "encoding/binary" 
    "fmt" 
    "net" 
    "os" 
    "syscall" 
) 

type ICMP struct { 
    Type  uint8 
    Code  uint8 
    Checksum uint16 
    Identifier uint16 
    SeqNo  uint16 
} 

func Checksum(data []byte) uint16 { 
    var (
     sum uint32 
     length int = len(data) 
     index int 
    ) 

    for length > 1 { 
     sum += uint32(data[index])<<8 + uint32(data[index+1]) 
     index += 2 
     length -= 2 
    } 

    if length > 0 { 
     sum += uint32(data[index]) 
    } 

    sum += (sum >> 16) 

    return uint16(^sum) 
} 

func main() { 
    h := Header{ 
     Version: 4, 
     Len:  20, 
     TotalLen: 20 + 8, 
     TTL:  1, 
     Protocol: 1, 
     // Dst: 
    } 

    argc := len(os.Args) 
    if argc < 2 { 
     fmt.Println("usage: program + host") 
     return 
    } 

    ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1]) 
    h.Dst = ipAddr.IP 

    icmpReq := ICMP{ 
     Type:  8, 
     Code:  0, 
     Identifier: 0, 
     SeqNo:  0, 
    } 

    out, err := h.Marshal() 
    if err != nil { 
     fmt.Println("ip header error", err) 
     return 
    } 

    var icmpBuf bytes.Buffer 
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq) 
    icmpReq.Checksum = Checksum(icmpBuf.Bytes()) 

    icmpBuf.Reset() 
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq) 

    fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) 
    addr := syscall.SockaddrInet4{ 
     Port: 0, 
    } 

    copy(addr.Addr[:], ipAddr.IP[12:16]) 
    pkg := append(out, icmpBuf.Bytes()...) 

    fmt.Println("ip length", len(pkg)) 

    if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil { 
     fmt.Println("Sendto err:", err) 
    } 

    var recvBuf []byte 
    if nBytes, rAddr, err := syscall.Recvfrom(fd, recvBuf, 0); err == nil { 
     fmt.Printf("recv %d bytes from %v\n", nBytes, rAddr) 
    } 
} 

zusätzlich verwende ich header.go und helper.go von https://github.com/golang/net/tree/master/ipv4

Antwort

0

Wie Andy wies darauf hin, die raw(7) man page sagt:

Eine IPPROTO_RAW Buchse ist nur senden. Wenn Sie wirklich alle IP-Pakete mit empfangen möchten, verwenden Sie einen Packet (7) -Socket mit dem ETH_P_IP-Protokoll. Beachten Sie, dass Paket-Sockets IP-Fragmente nicht neu zusammensetzen, im Gegensatz zu rohen Sockets.

Ich weiß, ich kann ICMP-Antwort erhalten, wenn ich IPPROTO_ICMP als Protokoll festgelegt, wenn ich den Socket zu erstellen, aber ich brauche TTL-1 zu setzen, die in IP-Schicht erfolgen muss. Deshalb sende ich die ICMP Anfrage mit IPPROTO_RAW Socket, danach benutze ich net.ListenIP um ICMP Nachrichten zu empfangen. Hier ist der Code:

package main 

import (
    "bytes" 
    "encoding/binary" 
    "log" 
    "net" 
    "os" 
    "syscall" 
) 

const icmpID uint16 = 43565 // use a magic number for now 

type ICMP struct { 
    Type  uint8 
    Code  uint8 
    Checksum uint16 
    Identifier uint16 
    SeqNo  uint16 
} 

func Checksum(data []byte) uint16 { 
    var (
     sum uint32 
     length int = len(data) 
     index int 
    ) 

    for length > 1 { 
     sum += uint32(data[index])<<8 + uint32(data[index+1]) 
     index += 2 
     length -= 2 
    } 

    if length > 0 { 
     sum += uint32(data[index]) 
    } 

    sum += (sum >> 16) 

    return uint16(^sum) 
} 

func main() { 
    h := Header{ 
     Version: 4, 
     Len:  20, 
     TotalLen: 20 + 8, 
     TTL:  1, 
     Protocol: 1, 
    } 

    argc := len(os.Args) 
    if argc < 2 { 
     log.Println("usage: program + host") 
     return 
    } 

    ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1]) 
    h.Dst = ipAddr.IP 

    icmpReq := ICMP{ 
     Type:  8, 
     Code:  0, 
     Identifier: icmpID, 
     SeqNo:  1, 
    } 

    out, err := h.Marshal() 
    if err != nil { 
     log.Println("ip header error", err) 
     return 
    } 

    var icmpBuf bytes.Buffer 
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq) 
    icmpReq.Checksum = Checksum(icmpBuf.Bytes()) 

    icmpBuf.Reset() 
    binary.Write(&icmpBuf, binary.BigEndian, icmpReq) 

    fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) 
    addr := syscall.SockaddrInet4{ 
     Port: 0, 
    } 

    copy(addr.Addr[:], ipAddr.IP[12:16]) 
    pkg := append(out, icmpBuf.Bytes()...) 

    if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil { 
     log.Println("Sendto err:", err) 
    } 

    laddr, err := net.ResolveIPAddr("ip4:icmp", "0.0.0.0") 
    if err != nil { 
     log.Fatal(err) 

    } 

    c, err := net.ListenIP("ip4:icmp", laddr) 
    if err != nil { 
     log.Fatal(err) 
    } 

    for { 
     buf := make([]byte, 2048) 
     n, raddr, err := c.ReadFrom(buf) 
     if err != nil { 
      log.Println(err) 
      continue 
     } 
     icmpType := buf[0] 
     if icmpType == 11 { 
      if n == 36 { // Time exceeded messages 
       // A time exceeded message contain IP header(20 bytes) and first 64 bits of the original payload 
       id := binary.BigEndian.Uint16(buf[32:34]) 
       log.Println("recv id", id) 
       if id == icmpID { 
        log.Println("recv Time Exceeded from", raddr) 
       } 
      } 
     } 
    } 
} 

Eigentlich bin ich ein trace in go schreiben, wenn jemand daran interessiert, das heißt, ist der gesamte Code in github.

1

Ich glaube, Sie IPPROTO_ICMP als Protokoll geben müssen, wenn Sie Ihre Steckdose erstellen. Die raw(7) man page besagt, dass ein IPPROTO_RAW Socket nur gesendet wird. Wenn Sie IPPROTO_ICMP verwenden, geben Sie außerdem den IP-Header nicht an. (Anmerkung: Ich habe nicht wirklich versucht, diese in Go.)

+0

Danke für den Tipp, ich kann 'IPPROTO_ICMP' nicht als Protokoll festlegen, wenn ich den Socket erstellen, da ich' TTL' auf 1 setzen muss, was in IP-Ebene getan werden muss. – jfly

+0

Gibt es keine Socket-Option zum Einstellen der TTL? –

+0

keine solche Methode in der Standardbibliothek. – jfly