2016-03-23 2 views
0

Я пытаюсь отправить сообщение icmp, чей TTL равен 1, и ожидаем получить сообщение с превышенным временем. это сообщение действительно (я вижу это из wirehark), но моя программа блокирует syscall.Recvfrom. Кто-нибудь знает почему?
icmp.goraw socket не получил ответ icmp

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

кроме того, я использую header.go и helper.go из https://github.com/golang/net/tree/master/ipv4

ответ

0

Как Энди указал, raw(7) man page говорит:

IPPROTO_RAW сокет отправить только. Если вы действительно хотите получать все IP-пакеты , используйте пакетный (7) сокет с протоколом ETH_P_IP. Обратите внимание, что пакеты сокетов не собирают IP-фрагменты, в отличие от исходных сокетов .

Я знаю, что могу получить ответ ICMP, если я изложу IPPROTO_ICMP как протокол, когда я создаю сокет, но мне нужно установить TTL к 1, которое должно быть сделано в слое IP. Поэтому я отправляю ICMP-запрос с помощью сокета IPPROTO_RAW, после чего я использую net.ListenIP для приема сообщений ICMP. Вот код:

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

На самом деле, я пишу трассировку в дороге, если кому-то интересно о том, что весь код находится в github.

1

Я думаю, что нужно дать IPPROTO_ICMP в качестве протокола при создании вашего сокета. raw(7) man page говорит, что только сотовый телефон IPPROTO_RAW отправляется. Кроме того, если вы используете IPPROTO_ICMP, вы не даете IP-заголовок. (Примечание: На самом деле я не пробовал это в Go.)

+0

Спасибо за подсказку, я не могу установить 'IPPROTO_ICMP' в качестве протокола при создании сокета, так как мне нужно установить' TTL' в 1, что должно быть сделано в IP-слое. – jfly

+0

Нет ли опции сокета для установки TTL? –

+0

нет такого метода в стандартной библиотеке. – jfly