原始套接字未收到 ICMP 响应。

huangapple go评论109阅读模式
英文:

raw socket didn't receive icmp response

问题

我正在尝试发送一个TTL为1的ICMP消息,并期望接收到一个超时消息。我可以从Wireshark中看到该消息的到达,但我的程序在syscall.Recvfrom上阻塞。有人知道为什么吗?

以下是你提供的代码的翻译:

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

另外,我使用了来自https://github.com/golang/net/tree/master/ipv4的header.gohelper.go文件。

英文:

I'm trying to send an icmp message whose TTL is just 1, and expect to receive a time exceeded message. that message does come(I see it from wireshark), but my program blocks on syscall.Recvfrom. Anyone knows why?
icmp.go

package main
import (
&quot;bytes&quot;
&quot;encoding/binary&quot;
&quot;fmt&quot;
&quot;net&quot;
&quot;os&quot;
&quot;syscall&quot;
)
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 &gt; 1 {
sum += uint32(data[index])&lt;&lt;8 + uint32(data[index+1])
index += 2
length -= 2
}
if length &gt; 0 {
sum += uint32(data[index])
}
sum += (sum &gt;&gt; 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 &lt; 2 {
fmt.Println(&quot;usage: program + host&quot;)
return
}
ipAddr, _ := net.ResolveIPAddr(&quot;ip&quot;, 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(&quot;ip header error&quot;, err)
return
}
var icmpBuf bytes.Buffer
binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
icmpReq.Checksum = Checksum(icmpBuf.Bytes())
icmpBuf.Reset()
binary.Write(&amp;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(&quot;ip length&quot;, len(pkg))
if err := syscall.Sendto(fd, pkg, 0, &amp;addr); err != nil {
fmt.Println(&quot;Sendto err:&quot;, err)
}
var recvBuf []byte
if nBytes, rAddr, err := syscall.Recvfrom(fd, recvBuf, 0); err == nil {
fmt.Printf(&quot;recv %d bytes from %v\n&quot;, nBytes, rAddr)
}
}

additionally, I use header.go and helper.go from https://github.com/golang/net/tree/master/ipv4

答案1

得分: 2

正如Andy指出的,raw(7) man page中提到:

> IPPROTO_RAW套接字只能用于发送。如果你真的想接收所有的IP数据包,请使用具有ETH_P_IP协议的packet(7)套接字。请注意,与原始套接字不同,数据包套接字不会重新组装IP片段。

我知道如果我在创建套接字时将协议设置为IPPROTO_ICMP,我可以接收到ICMP回复,但是我需要在IP层设置TTL为1。因此,我使用IPPROTO_RAW套接字发送ICMP请求,然后使用net.ListenIP接收ICMP消息。以下是代码:

package main

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

const icmpID uint16 = 43565 // 暂时使用一个魔术数

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

实际上,我正在使用Go语言编写一个traceroute程序,如果有人对此感兴趣,完整的代码可以在GitHub上找到。

英文:

As Andy pointed out, the raw(7) man page says:

> An IPPROTO_RAW socket is send only. If you really want to receive
all IP packets, use a packet(7) socket with the ETH_P_IP protocol.
Note that packet sockets don't reassemble IP fragments, unlike raw
sockets.

I know I can receive ICMP reply if I set IPPROTO_ICMP as the protocol when I create the socket, but I need to set TTL to 1 which must be done in IP layer. Therefore I send the ICMP request with IPPROTO_RAW socket, after that I use net.ListenIP to receive ICMP messages. Here is the code:

package main
import (
&quot;bytes&quot;
&quot;encoding/binary&quot;
&quot;log&quot;
&quot;net&quot;
&quot;os&quot;
&quot;syscall&quot;
)
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 &gt; 1 {
sum += uint32(data[index])&lt;&lt;8 + uint32(data[index+1])
index += 2
length -= 2
}
if length &gt; 0 {
sum += uint32(data[index])
}
sum += (sum &gt;&gt; 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 &lt; 2 {
log.Println(&quot;usage: program + host&quot;)
return
}
ipAddr, _ := net.ResolveIPAddr(&quot;ip&quot;, 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(&quot;ip header error&quot;, err)
return
}
var icmpBuf bytes.Buffer
binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
icmpReq.Checksum = Checksum(icmpBuf.Bytes())
icmpBuf.Reset()
binary.Write(&amp;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, &amp;addr); err != nil {
log.Println(&quot;Sendto err:&quot;, err)
}
laddr, err := net.ResolveIPAddr(&quot;ip4:icmp&quot;, &quot;0.0.0.0&quot;)
if err != nil {
log.Fatal(err)
}
c, err := net.ListenIP(&quot;ip4:icmp&quot;, 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(&quot;recv id&quot;, id)
if id == icmpID {
log.Println(&quot;recv Time Exceeded from&quot;, raddr)
}
}
}
}
}

Actually, I am writing a traceroute in go, if anyone is interested about that, the whole code is in github.

答案2

得分: 1

我认为在创建套接字时,你需要将协议设置为IPPROTO_ICMPraw(7) man page中提到,IPPROTO_RAW套接字只能用于发送。此外,如果你使用IPPROTO_ICMP,就不需要提供IP头部。(注意:我没有在Go中实际尝试过这个。)

英文:

I think you need to give IPPROTO_ICMP as the protocol when you create your socket. The raw(7) man page says that an IPPROTO_RAW socket is send only. Also, if you use IPPROTO_ICMP, you don't give the IP header. (Note: I haven't actually tried this in Go.)

huangapple
  • 本文由 发表于 2016年3月23日 12:31:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/36169890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定