C语言SSH客户端二次send()数据包未被服务器接收问题排查
问题描述
用C语言开发SSH客户端,当前可正常发送SSH协议版本并接收服务器返回的版本信息,但发送密钥交换初始化(kex init)数据包时,服务器完全接收不到该数据包(已通过Wireshark验证)。客户端能正常接收服务器的kex init消息;若调换发送顺序(先发kex包再发协议包),服务器仅能收到格式错误的kex包,第二个包仍无法被接收。
优先解决数据包送达问题,暂不纠结内容格式,相关代码如下:
初始化TCP连接
int start_client(const char *host, const int port) { struct sockaddr_in address; int sock = 0; sock = socket(AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET; address.sin_port = htons(22); inet_pton(AF_INET, host, &address.sin_addr); int output = connect(sock, (struct sockaddr *)&address, sizeof(address)); if (output == 0) { printf("successful connection\n"); } else { printf("no connection\n"); } sendProtocol(sock); sendKexInit(sock); close(sock); return 0; }
发送协议数据包
int sendProtocol(int sock) { unsigned char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); // Clear the buffer char *protocol = "SSH-2.0-mySSH\r\n"; int sentBytes = send(sock, protocol, strlen(protocol), 0); if (sentBytes != -1) { printf("Successful protocol send! Number of protocol bytes sent: %i\n", sentBytes); } else { printf("Send did not complete successfully.\n"); } ssize_t bytesRecieved = recv(sock, buffer, BUFFER_SIZE, 0); if (bytesRecieved > 0) { printf("server protocol: %s", buffer); } else { printf("No server protocol recieved :(\n"); } return 0; }
发送(格式错误的)kex数据包
int sendKexInit (int sock) { unsigned char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); // Clear the buffer unsigned char *cookie = generateRandomBytes(16); // // this is hardcoded, just trying to get it to work uint32_t packetLen = 24; packetLen = htonl(packetLen); // fix endian-ness memcpy(buffer, &packetLen, sizeof(packetLen)); buffer[4] = 6; buffer[5] = SSH_MSG_KEXINIT; memcpy(buffer + 6, cookie, 16); free(cookie); memset(buffer + 22, 0, 2); int sentBytes = send(sock, buffer, 24, 0); if (sentBytes != -1) { printf("Successful kex send! Number of kex bytes sent: %i\n", sentBytes); } else { printf("Send did not complete successfully.\n"); } memset(buffer, 0, BUFFER_SIZE); // Clear the buffer ssize_t bytes_recieved = recv(sock, buffer, BUFFER_SIZE, 0); if (bytes_recieved > 0) { printf("response: %s\n", buffer); } else { printf("No server response recieved :(\n"); } // printing out some of the response (buffer is too small for all of it) // for (int i = 0; i < sizeof(buffer); i++) { // printf("%02x ", buffer[i]); // } // printf("\n"); return 0; }
疑问
send()返回值显示发送字节数正确,但始终只有第一个发送的数据包能被服务器接收,第二个无法送达。猜测可能是第一个send()未正确收尾,但不确定问题所在,请问是否有明显遗漏的点导致二次send()的数据包无法送达?
排查关键点
结合代码和现象,有几个需要优先验证的点:
- 禁用Nagle算法
TCP默认启用的Nagle算法会延迟小数据包发送,等待前一个数据包的ACK后再发送后续小数据包。虽然第一个包的ACK应该在sendProtocol的recv阶段已收到,但仍可能存在延迟。在创建socket后添加以下代码禁用Nagle算法:
int opt = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
检查
sendKexInit中的阻塞问题sendKexInit在send后立即调用recv,如果服务器未收到kex包(或包格式错误),不会返回响应,导致recv一直阻塞,程序无法走到close(sock)。此时内核可能不会强制发送缓冲区中的数据,Wireshark自然抓不到包。可以先注释掉sendKexInit中的recv代码,仅保留send逻辑,再用Wireshark验证是否能发送第二个包。确认
BUFFER_SIZE足够且无内存溢出
如果BUFFER_SIZE小于24,sendKexInit中构造数据包时会发生栈内存溢出,破坏socket相关结构,导致后续send操作异常。确保BUFFER_SIZE定义大于等于24。检查
generateRandomBytes函数的正确性
如果generateRandomBytes返回的内存存在越界或未正确初始化,可能会破坏栈上的socket描述符或其他关键数据,导致send失败。可以临时替换为固定的16字节测试数据,排除该函数的影响:
unsigned char cookie[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
- 确保TCP连接状态正常
在sendKexInit调用前,添加代码检查socket的连接状态,避免连接已意外断开:
int error = 0; socklen_t len = sizeof(error); getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len); if (error != 0) { printf("Socket error: %d\n", error); return -1; }
内容的提问来源于stack exchange,提问作者Ruben Boero

