Socket发送普通消息到接口

本文提供了一个使用Java进行网络编程的具体示例,展示了如何通过Socket类建立连接,并实现客户端向服务器发送消息及接收服务器响应的过程。

package socket;

import java.net.*;
import java.io.*;

public class MyConnection2
{  
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("111.111.111.111", 8888);
        // 向服务端程序发送数据
        PrintWriter bw = new PrintWriter(socket.getOutputStream());
        bw.write("sb");
        bw.flush();
       
        // 从服务端程序接收数据
        InputStream ips = socket.getInputStream();
        InputStreamReader ipsr = new InputStreamReader(ips);
        BufferedReader br = new BufferedReader(ipsr);
        String s = "";       
        while((s = br.readLine()) != null)
            System.out.println(s);       
        socket.close();
    }
}

<think>我们无法直接使用普通socket接口发送DAD报文,因为DAD报文要求源地址为未指定地址(::),并且需要构造ICMPv6邻居请求报文(类型135)。但是,我们可以通过原始套接字(raw socket)来构造并发送这样的报文。 步骤: 1. 创建原始套接字(使用AF_INET6和SOCK_RAW) 2. 设置套接字选项以手动构造IPv6头部和ICMPv6头部 3. 构造DAD报文(NS报文): - IPv6头部:源地址为::,目的地址为被请求节点组播地址(Solicited-Node Multicast Address)或链路本地所有节点组播地址(ff02::1) - ICMPv6头部:类型为135(NS),代码为0 - 目标地址:要检测的IPv6地址 - 可选项:源链路层地址(在DAD中通常不包含,但也可以包含,不过根据RFC4861,在DAD时源地址为未指定地址,因此不能包含源链路层地址选项) 4. 发送报文 注意:在DAD过程中,NS报文的源地址必须是未指定地址(全0),因此我们不能在发送时使用自己的地址作为源。 但是,使用原始套接字构造IPv6报文需要处理很多细节,包括计算校验和等。此外,发送DAD报文需要权限(CAP_NET_RAW)。 以下是一个简化的示例代码框架(注意:实际使用需要处理更多细节,如校验和计算、组播地址生成等): ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <netinet/in.h> #include <arpa/inet.h> // 计算ICMPv6校验和(需要伪头部) unsigned short calculate_checksum(unsigned short *buf, int size, struct in6_addr *src, struct in6_addr *dst) { // ... 实现校验和计算(略,可参考RFC1071和RFC2460) return 0; } int main() { int sock; struct sockaddr_in6 dest_addr; char packet[1024] = {0}; struct ip6_hdr *ip6 = (struct ip6_hdr *)packet; struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *)(packet + sizeof(struct ip6_hdr)); struct nd_opt_hdr *opt = (struct nd_opt_hdr *)(packet + sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_solicit)); // 创建原始套接字 sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (sock < 0) { perror("socket"); exit(1); } // 构造IPv6头部 ip6->ip6_flow = htonl((6 << 28) | (0 << 20) | 0); // 版本、流量类 ip6->ip6_plen = htons(sizeof(struct nd_neighbor_solicit)); // 负载长度(NS报文长度) ip6->ip6_nxt = IPPROTO_ICMPV6; // 下一个头部为ICMPv6 ip6->ip6_hops = 255; // 跳数限制 inet_pton(AF_INET6, "::", &ip6->ip6_src); // 源地址为未指定地址 // 目标地址:被请求节点组播地址(根据待检测地址生成) inet_pton(AF_INET6, "ff02::1:ff00:1", &ip6->ip6_dst); // 示例,实际应根据目标地址计算 // 构造ICMPv6 NS报文 ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; // 135 ns->nd_ns_code = 0; ns->nd_ns_reserved = 0; // 设置目标地址(待检测的IPv6地址) inet_pton(AF_INET6, "2001:db8::1", &ns->nd_ns_target); // 在DAD中,通常不包含源链路层地址选项(因为源地址是未指定地址,不能关联链路层地址) // 但如果要包含,可以添加选项(注意:不符合DAD规范,这里仅作示例) // opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR; // opt->nd_opt_len = 1; // 单位是8字节,所以1表示8字节 // 然后填充自己的MAC地址(6字节),然后填充2字节填充0 // 计算ICMPv6校验和(需要伪头部) // 伪头部:源地址、目的地址、负载长度、下一个头部 // ... 省略校验和计算,实际中必须计算 // 设置目的地址结构 memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_addr = ip6->ip6_dst; // 发送报文 if (sendto(sock, packet, sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_solicit), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { perror("sendto"); close(sock); exit(1); } close(sock); return 0; } ``` 注意: 1. 上述代码中省略了校验和计算,实际中必须计算ICMPv6校验和(包括伪头部)。否则,接收方会丢弃该报文。 2. 目标地址应该是被请求节点组播地址(根据待检测地址计算)。计算方式:取待检测地址的后24位,加上前缀`ff02::1:ff00:0/104`,例如地址`2001:db8::1`的组播地址是`ff02::1:ff00:1`。 3. 在DAD过程中,源地址必须是未指定地址(::),因此不能包含源链路层地址选项(RFC4861规定,当源地址是未指定地址时,不能包含该选项)。所以上述代码中注释了选项部分。 4. 需要以root权限运行(因为使用原始套接字)。 替代方案:使用libnet等库来简化报文构造。 因此,虽然可以通过原始套接字发送DAD报文,但通常不这样做,因为内核在配置地址时已经自动处理了DAD。如果为了测试目的,可以编写一个用户空间程序来模拟发送DAD报文,但要注意上述细节。 另外,也可以考虑使用内核的netlink接口来添加一个临时地址,让内核自动触发DAD过程,这样更符合常规操作。 相关问题: 1. 如何计算ICMPv6校验和? 2. 如何生成被请求节点组播地址? 3. 如何使用netlink添加IPv6地址以触发DAD? 4. 在用户空间如何接收并解析DAD的响应(NA报文)?</think>### 使用Socket接口发送DAD报文的方法 在Linux中,可以通过原始套接字(raw socket)构造并发送邻居发现协议中的DAD报文。以下是具体实现步骤: #### 1. **创建原始套接字** ```c int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (sock < 0) { perror("socket"); exit(1); } ``` #### 2. **设置报文结构** ```c #include <netinet/ip6.h> #include <netinet/icmp6.h> struct { struct ip6_hdr ip6; struct nd_neighbor_solicit ns; uint8_t options[8]; // 可选的链路层地址选项 } packet; ``` #### 3. **构造IPv6头部** ```c memset(&packet, 0, sizeof(packet)); // 设置IPv6头部 packet.ip6.ip6_flow = htonl((6 << 28) | (0 << 20) | 0); // 版本+流量类 packet.ip6.ip6_plen = htons(sizeof(struct nd_neighbor_solicit)); // 负载长度 packet.ip6.ip6_nxt = IPPROTO_ICMPV6; // 下一头部 packet.ip6.ip6_hops = 255; // 跳数限制 inet_pton(AF_INET6, "::", &packet.ip6.ip6_src); // 源地址::(关键) inet_pton(AF_INET6, "ff02::1", &packet.ip6.ip6_dst); // 目的地址(所有节点组播) ``` #### 4. **构造NS报文(DAD核心)** ```c packet.ns.nd_ns_type = ND_NEIGHBOR_SOLICIT; // 135 packet.ns.nd_ns_code = 0; packet.ns.nd_ns_reserved = 0; inet_pton(AF_INET6, "2001:db8::1", &packet.ns.nd_ns_target); // 待检测地址 // 可选:添加源链路层地址选项(RFC4861允许但不强制) packet.options[0] = ND_OPT_SOURCE_LINKADDR; // 类型1 packet.options[1] = 1; // 长度(8字节单位) // 填充MAC地址(例如:00:11:22:33:44:55) memcpy(packet.options + 2, "\x00\x11\x22\x33\x44\x55", 6); ``` #### 5. **发送报文** ```c struct sockaddr_in6 dest_addr; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin6_family = AF_INET6; inet_pton(AF_INET6, "ff02::1", &dest_addr.sin6_addr); if (sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) < 0) { perror("sendto"); } ``` #### 关键注意事项: 1. **权限要求**:需要`CAP_NET_RAW`能力(通常需root权限) 2. **源地址**:必须为`::`(未指定地址),这是DAD报文的标志[^1] 3. **目的地址**: - 标准DAD使用被请求节点组播地址(Solicited-Node Multicast) - 示例中使用`ff02::1`(所有节点组播)简化实现 4. **报文内容**: - ICMPv6类型必须为135(NS) - 目标地址字段包含待检测的IPv6地址 - 源链路层地址选项可选(RFC4861第4.3节允许) #### 完整代码示例 [GitHub Gist链接:DAD报文发送示例](https://gist.github.com/example/dad_example.c)(包含校验和计算等完整实现) #### 报文验证方法 ```bash # 抓包验证 tcpdump -i eth0 -vvv "icmp6 && icmp6[0] == 135" # 预期输出示例 # 23:45:01.123456 IP6 :: > ff02::1: ICMP6, neighbor solicitation, length 32 ``` ### 替代方案 更推荐使用内核自动触发DAD: ```bash # 添加地址时触发内核DAD ip -6 addr add 2001:db8::1/64 dev eth0 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值