Search code examples
socketstcpnetwork-programming

getpeername() always fails with error code WSAENOTCONN


I'm trying to use getpeerinfo to ensure i can get peer info after connecting.

It fails with:

WSAENOTCONN (10057)

Socket is not connected.

A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using sendto) no address was supplied.

The basic flow is:

  • WSAStartup
  • socket()
  • connect()
  • getpeerinfo()

What am i doing wrong?

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Winapi.Winsock2;

procedure Main;
var
    hSocket: TSocket;
    wsData: TWSAData;
    nodeName: string;
    serviceName: string;
    localAddressLength: Cardinal;
    localAddress: TSockAddr;
    remoteAddressLength: Cardinal;
    remoteAddress: TSockAddr;
    name: TSockAddr;
    nameLen: Integer;
    errorCode: Integer;
    bRes: Boolean;
begin
    WSAStartup($0202, {var}wsData);

    hSocket := socket(AF_INET, SOCK_STREAM, 0);

    nodeName := 'stackoverflow.com';
    serviceName := '80';

    bRes := WSAConnectByNameW(hSocket, PChar(nodeName), PChar(serviceName),
            {var}localAddressLength, {var}localAddress,
            {var}remoteAddressLength, {var}remoteAddress,
            nil, nil);
    if not bRes then
    begin
        errorCode := WSAGetLastError;
        RaiseLastOSError(errorCode);
    end;

    //If no error occurs, getpeername returns zero.
    //Otherwise, a value of SOCKET_ERROR is returned,
    //and a specific error code can be retrieved by calling WSAGetLastError.
    nameLen := sizeof(name);
    errorCode := getpeername(hSocket, {var}name, {var}nameLen);
    if errorCode <> 0 then
    begin
        errorCode := WSAGetLastError;
        RaiseLastOSError(errorCode);
    end;
end;

begin
    try
        Main;
    except
        on E: Exception do
            Writeln(E.ClassName, ': ', E.Message);
    end;
end.

We know the connection is connected because:

  • we just connected
  • we did the thing to check if a socket is connected

Bonus Reading


Solution

  • The answer is in the WinSock documentation.

    WSAConnectByNameW() function

    When the WSAConnectByName function returns TRUE, the socket s is in the default state for a connected socket. The socket s does not enable previously set properties or options until SO_UPDATE_CONNECT_CONTEXT is set on the socket. Use the setsockopt function to set the SO_UPDATE_CONNECT_CONTEXT option.

    So, when WSAConnectByNameW() returns TRUE, getpeername() fails with WSAENOTCONN because you are not calling setsockopt(SO_UPDATE_CONNECT_CONTEXT) to put the socket into the proper state. This is clarified in the SOL_SOCKET Socket Options documentation:

    SO_UPDATE_CONNECT_CONTEXT

    This option is used with the ConnectEx, WSAConnectByList, and WSAConnectByName functions. This option updates the properties of the socket after the connection is established. This option should be set if the getpeername, getsockname, getsockopt, setsockopt, or shutdown functions are to be used on the connected socket.

    Try this:

    bRes := WSAConnectByNameW(hSocket, PChar(nodeName), PChar(serviceName),
      {var}localAddressLength, {var}localAddress,
      {var}remoteAddressLength, {var}remoteAddress,
      nil, nil);
    if not bRes then
    begin
      errorCode := WSAGetLastError;
      RaiseLastOSError(errorCode);
    end;
    
    // ADD THIS..
    errorCode := setsockopt(hSocket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nil, 0);
    if errorCode <> 0 then
    begin
      errorCode := WSAGetLastError;
      RaiseLastOSError(errorCode);
    end;
    
    ...
    

    That being said, there is no need to use getpeername() in your example, because it returns the same info that WSAConnectByNameW() already returns in the variable you are passing to its RemoteAddress parameter.