Tcl Source Code

Check-in [a1abfd5e94]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix [socket -async] for DNS names with more than one address
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | rmax-ipv6-branch
Files: files | file ages | folders
SHA1: a1abfd5e949a642319a002f21c39907dfb5c300b
User & Date: max 2011-05-27 18:36:26
Context
2011-05-30
18:04
  • Fix setting up of [fileevent] while an async socket is still in progress
  • Cache async sock...
check-in: 6d66913621 user: max tags: rmax-ipv6-branch
2011-05-27
18:36
Fix [socket -async] for DNS names with more than one address check-in: a1abfd5e94 user: max tags: rmax-ipv6-branch
17:50
fix a timing issue in socket-12.3 check-in: 188a795873 user: max tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ChangeLog.









1
2
3
4
5
6
7








2011-05-25  Don Porter  <[email protected]>

	* library/msgcat/msgcat.tcl:	Bump to msgcat 1.4.4.
	* library/msgcat/pkgIndex.tcl:
	* unix/Makefile.in
	* win/Makefile.in

>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2011-05-27  Reinhard Max  <[email protected]>

	* unix/tclUnixSock.c: Fix [socket -async], so that all addresses
	returned by getaddrinfo() are tried, not just the first one. This
	requires the event loop to be running while the async connection
	is in progress. ***POTENTIAL INCOMPATIBILITY***
	* tests/socket.test: Add a test for the above.

2011-05-25  Don Porter  <[email protected]>

	* library/msgcat/msgcat.tcl:	Bump to msgcat 1.4.4.
	* library/msgcat/pkgIndex.tcl:
	* unix/Makefile.in
	* win/Makefile.in

Changes to tests/socket.test.

1695
1696
1697
1698
1699
1700
1701






















1702
1703
1704
1705
1706
1707
1708
# cleanup
if {$remoteProcChan ne ""} {
    catch {sendCommand exit}
}
catch {close $commandSocket}
catch {close $remoteProcChan}
}






















::tcltest::cleanupTests
flush stdout
return

# Local Variables:
# mode: tcl
# fill-column: 78







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
# cleanup
if {$remoteProcChan ne ""} {
    catch {sendCommand exit}
}
catch {close $commandSocket}
catch {close $remoteProcChan}
}
unset ::tcl::unsupported::socketAF
test socket-14.0 {async when server only listens on one address family} \
    -constraints [list socket supported_any] \
    -setup {
        proc accept {s a p} {
            global x
            puts $s bye
            close $s
            set x ok
        }
        set server [socket -server accept -myaddr 127.0.0.1 0]
        set port [lindex [fconfigure $server -sockname] 2]
    } -body {
        set client [socket -async localhost $port]
        # fileevent $client readable [list set x [fconfigure $client -error]]
        after 1000 {set x [fconfigure $client -error]}
        vwait x
        set x
    } -cleanup {
        close $server
        close $client
    } -result ok
::tcltest::cleanupTests
flush stdout
return

# Local Variables:
# mode: tcl
# fill-column: 78

Changes to unix/tclUnixSock.c.

45
46
47
48
49
50
51



52
53
54










55
56
57
58
59
60
61
} TcpFdList;

struct TcpState {
    Tcl_Channel channel;	/* Channel associated with this file. */
    TcpFdList *fds;		/* The file descriptors of the sockets. */
    int flags;			/* ORed combination of the bitfields defined
				 * below. */



    Tcl_TcpAcceptProc *acceptProc;
				/* Proc to call on accept. */
    ClientData acceptProcData;	/* The data for the accept proc. */










};

/*
 * These bits may be ORed together into the "flags" field of a TcpState
 * structure.
 */








>
>
>
|

|
>
>
>
>
>
>
>
>
>
>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
} TcpFdList;

struct TcpState {
    Tcl_Channel channel;	/* Channel associated with this file. */
    TcpFdList *fds;		/* The file descriptors of the sockets. */
    int flags;			/* ORed combination of the bitfields defined
				 * below. */
    union {
        struct {
            /* Only needed for server sockets */
            Tcl_TcpAcceptProc *acceptProc;
				/* Proc to call on accept. */
            ClientData acceptProcData;
				/* The data for the accept proc. */
        };
        struct {
            /* Only needed for client sockets */
            struct addrinfo *addrlist;
            struct addrinfo *myaddrlist;
            struct addrinfo *addr;
            struct addrinfo *myaddr;
        };
    };
};

/*
 * These bits may be ORed together into the "flags" field of a TcpState
 * structure.
 */

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

#define SOCKET_BUFSIZE	4096

/*
 * Static routines for this file:
 */

static TcpState *	CreateClientSocket(Tcl_Interp *interp, int port,
			    const char *host, const char *myaddr,
			    int myport, int async);
static void		TcpAccept(ClientData data, int mask);
static int		TcpBlockModeProc(ClientData data, int mode);
static int		TcpCloseProc(ClientData instanceData,
			    Tcl_Interp *interp);
static int		TcpClose2Proc(ClientData instanceData,
			    Tcl_Interp *interp, int flags);
static int		TcpGetHandleProc(ClientData instanceData,







|
|
<







98
99
100
101
102
103
104
105
106

107
108
109
110
111
112
113

#define SOCKET_BUFSIZE	4096

/*
 * Static routines for this file:
 */

static int		CreateClientSocket(Tcl_Interp *interp,
                                           TcpState *state);

static void		TcpAccept(ClientData data, int mask);
static int		TcpBlockModeProc(ClientData data, int mode);
static int		TcpCloseProc(ClientData instanceData,
			    Tcl_Interp *interp);
static int		TcpClose2Proc(ClientData instanceData,
			    Tcl_Interp *interp, int flags);
static int		TcpGetHandleProc(ClientData instanceData,
824
825
826
827
828
829
830










831
832
833
834
835
836
837
838
839
840

841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873

874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919

920
921
922
923
924
925
926
927
928
929
930
931

932
933









934


935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
    ClientData *handlePtr)	/* Where to store the handle. */
{
    TcpState *statePtr = (TcpState *) instanceData;

    *handlePtr = INT2PTR(statePtr->fds->fd);
    return TCL_OK;
}











/*
 *----------------------------------------------------------------------
 *
 * CreateSocket --
 *
 *	This function opens a new socket in client or server mode and
 *	initializes the TcpState structure.
 *
 * Results:

 *	Returns a new TcpState, or NULL with an error in the interp's result,
 *	if interp is not NULL.
 *
 * Side effects:
 *	Opens a socket.
 *
 *----------------------------------------------------------------------
 */

static TcpState *
CreateClientSocket(
    Tcl_Interp *interp,		/* For error reporting; can be NULL. */
    int port,			/* Port number to open. */
    const char *host,		/* Name of host on which to open port. */
    const char *myaddr,		/* Optional client-side address.
                                 * NULL implies INADDR_ANY/in6addr_any */
    int myport,			/* Optional client-side port */
    int async)			/* If nonzero and creating a client socket,
				 * attempt to do an async connect. Otherwise
				 * do a synchronous connect or bind. */
{
    int status = -1, connected = 0, sock = -1;
    struct addrinfo *addrlist = NULL, *addrPtr;
                                /* Socket address */
    struct addrinfo *myaddrlist = NULL, *myaddrPtr;
                                /* Socket address for client */
    TcpState *statePtr;
    const char *errorMsg = NULL;

    if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) {
	goto error;
    }
    if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, &errorMsg)) {

	goto error;
    }

    for (addrPtr = addrlist; addrPtr != NULL;
         addrPtr = addrPtr->ai_next) {
        for (myaddrPtr = myaddrlist; myaddrPtr != NULL;
             myaddrPtr = myaddrPtr->ai_next) {
            int reuseaddr;
            
	    /*
	     * No need to try combinations of local and remote addresses of
	     * different families.
	     */

	    if (myaddrPtr->ai_family != addrPtr->ai_family) {
		continue;
	    }

	    sock = socket(addrPtr->ai_family, SOCK_STREAM, 0);
	    if (sock < 0) {
		continue;
	    }

	    /*
	     * Set the close-on-exec flag so that the socket will not get
	     * inherited by child processes.
	     */
	    
	    fcntl(sock, F_SETFD, FD_CLOEXEC);
	    
	    /*
	     * Set kernel space buffering
	     */
	    
	    TclSockMinimumBuffers(INT2PTR(sock), SOCKET_BUFSIZE);
    
	    if (async) {
		status = TclUnixSetBlockingMode(sock, TCL_MODE_NONBLOCKING);
		if (status < 0) {
		    goto looperror;
		}
	    }

            reuseaddr = 1;
            (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                    (char *) &reuseaddr, sizeof(reuseaddr));

            status = bind(sock, myaddrPtr->ai_addr, myaddrPtr->ai_addrlen);
            if (status < 0) {
                goto looperror;
            }

	    /*
	     * Attempt to connect. The connect may fail at present with an
	     * EINPROGRESS but at a later time it will complete. The caller
	     * will set up a file handler on the socket if she is interested
	     * in being informed when the connect completes.
	     */
	    

	    status = connect(sock, addrPtr->ai_addr, addrPtr->ai_addrlen);
	    if (status < 0 && errno == EINPROGRESS) {









		status = 0;


	    }
	    if (status == 0) {
		connected = 1;
		break;
	    }
	looperror:
	    if (sock != -1) {
		close(sock);
		sock = -1;
	    }
	}
	if (connected) {
	    break;
	} 
	status = -1;
	if (sock >= 0) {
	    close(sock);
	    sock = -1;
	}
    }
    if (async) {
	/*
	 * Restore blocking mode.
	 */

	status = TclUnixSetBlockingMode(sock, TCL_MODE_BLOCKING);
    }

error:
    if (addrlist) {
	freeaddrinfo(addrlist);
    }
    if (myaddrlist) {
	freeaddrinfo(myaddrlist);
    }
    
    if (status < 0) {
	if (interp != NULL) {
	    Tcl_AppendResult(interp, "couldn't open socket: ",
		    Tcl_PosixError(interp), NULL);
	    if (errorMsg != NULL) {
		Tcl_AppendResult(interp, " (", errorMsg, ")", NULL);
	    }
	}
	if (sock != -1) {
	    close(sock);
	}
	return NULL;
    }

    /*
     * Allocate a new TcpState for this socket.
     */

    statePtr = ckalloc(sizeof(TcpState));
    statePtr->flags = async ? TCP_ASYNC_CONNECT : 0;
    statePtr->fds = ckalloc(sizeof(TcpFdList));
    memset(statePtr->fds, (int) 0, sizeof(TcpFdList));
    statePtr->fds->fd = sock;

    return statePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_OpenTcpClient --
 *







>
>
>
>
>
>
>
>
>
>




|

|
<


>
|
|







|


|
<
<
<
<
<
<
<

|
<
<
<
<
<
<
|
<
<
|
<
>
|

|
|
|
|
|







|



|
|








|





|


|






|

>
|











>
|

>
>
>
>
>
>
>
>
>
|
>
>
|





|
|
|






|
|
|







|


<
<
|
<
<
|
|
<
|
|

|
<
<
|
<
|
|

<
<
|
<
|
<
|
<
<
<
<
<
|
|







836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875







876
877






878


879

880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982


983


984
985

986
987
988
989


990

991
992
993


994

995

996





997
998
999
1000
1001
1002
1003
1004
1005
    ClientData *handlePtr)	/* Where to store the handle. */
{
    TcpState *statePtr = (TcpState *) instanceData;

    *handlePtr = INT2PTR(statePtr->fds->fd);
    return TCL_OK;
}

static void
TcpAsyncCallback(
    ClientData clientData,	/* The socket state. */
    int mask)			/* Events of interest; an OR-ed combination of
				 * TCL_READABLE, TCL_WRITABLE and
				 * TCL_EXCEPTION. */
{
    CreateClientSocket(NULL, clientData);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateClientSocket --
 *
 *	This function opens a new socket in client mode.

 *
 * Results:
 *      TCL_OK, if the socket was successfully connected or an asynchronous
 *      connection is in progress. If an error occurs, TCL_ERROR is returned
 *      and an error message is left in interp.
 *
 * Side effects:
 *	Opens a socket.
 *
 *----------------------------------------------------------------------
 */

static int
CreateClientSocket(
    Tcl_Interp *interp,		/* For error reporting; can be NULL. */
    TcpState *state)







{
    int status = -1, connected = 0;






    int async = state->flags & TCP_ASYNC_CONNECT;




    if (state->addr != NULL) {
        goto coro_continue;
    }
    
    for (state->addr = state->addrlist; state->addr != NULL;
         state->addr = state->addr->ai_next) {
        for (state->myaddr = state->myaddrlist; state->myaddr != NULL;
             state->myaddr = state->myaddr->ai_next) {
            int reuseaddr;
            
	    /*
	     * No need to try combinations of local and remote addresses of
	     * different families.
	     */

	    if (state->myaddr->ai_family != state->addr->ai_family) {
		continue;
	    }

	    state->fds->fd = socket(state->addr->ai_family, SOCK_STREAM, 0);
	    if (state->fds->fd < 0) {
		continue;
	    }

	    /*
	     * Set the close-on-exec flag so that the socket will not get
	     * inherited by child processes.
	     */
	    
	    fcntl(state->fds->fd, F_SETFD, FD_CLOEXEC);
	    
	    /*
	     * Set kernel space buffering
	     */
	    
	    TclSockMinimumBuffers(INT2PTR(state->fds->fd), SOCKET_BUFSIZE);
    
	    if (async) {
		status = TclUnixSetBlockingMode(state->fds->fd, TCL_MODE_NONBLOCKING);
		if (status < 0) {
		    goto looperror;
		}
	    }

            reuseaddr = 1;
            (void) setsockopt(state->fds->fd, SOL_SOCKET, SO_REUSEADDR,
                    (char *) &reuseaddr, sizeof(reuseaddr));
            status = bind(state->fds->fd, state->myaddr->ai_addr,
                          state->myaddr->ai_addrlen);
            if (status < 0) {
                goto looperror;
            }

	    /*
	     * Attempt to connect. The connect may fail at present with an
	     * EINPROGRESS but at a later time it will complete. The caller
	     * will set up a file handler on the socket if she is interested
	     * in being informed when the connect completes.
	     */
	    
	    status = connect(state->fds->fd, state->addr->ai_addr,
                             state->addr->ai_addrlen);
	    if (status < 0 && errno == EINPROGRESS) {
                Tcl_CreateFileHandler(state->fds->fd, TCL_WRITABLE,
                                      TcpAsyncCallback, state);
                // fprintf(stderr, "here: %d \n", state->fds->fd);
                return TCL_OK;
            coro_continue:
                do {
                    socklen_t optlen = sizeof(int);
                    Tcl_DeleteFileHandler(state->fds->fd);
                    getsockopt(state->fds->fd, SOL_SOCKET, SO_ERROR,
                               (char *)&status, &optlen);             
                    // fprintf(stderr, "there: %d \n", state->fds->fd);
                } while (0);
            }
	    if (status == 0) {
		connected = 1;
		break;
	    }
	looperror:
	    if (state->fds->fd != -1) {
		close(state->fds->fd);
		state->fds->fd = -1;
	    }
	}
	if (connected) {
	    break;
	} 
	status = -1;
	if (state->fds->fd >= 0) {
	    close(state->fds->fd);
	    state->fds->fd = -1;
	}
    }
    if (async) {
	/*
	 * Restore blocking mode.
	 */

	status = TclUnixSetBlockingMode(state->fds->fd, TCL_MODE_BLOCKING);
    }



    freeaddrinfo(state->addrlist);


    freeaddrinfo(state->myaddrlist);


    if (status < 0 && !async) {
        if (interp != NULL) {
	    Tcl_AppendResult(interp, "couldn't open socket: ",
                             Tcl_PosixError(interp), NULL);


	}

	if (state->fds->fd != -1) {
	    close(state->fds->fd);
	}


        ckfree(state->fds);

        ckfree(state);

	return TCL_ERROR;





    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_OpenTcpClient --
 *
1019
1020
1021
1022
1023
1024
1025
1026


1027

























1028
1029
1030
1031
1032
1033
1034
1035

1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050










1051
1052
1053
1054
1055
1056
1057
    const char *host,		/* Host on which to open port. */
    const char *myaddr,		/* Client-side address */
    int myport,			/* Client-side port */
    int async)			/* If nonzero, attempt to do an asynchronous
				 * connect. Otherwise we do a blocking
				 * connect. */
{
    TcpState *statePtr;


    char channelName[16 + TCL_INTEGER_SPACE];


























    /*
     * Create a new client socket and wrap it in a channel.
     */

    statePtr = CreateClientSocket(interp, port, host, myaddr, myport, async);
    if (statePtr == NULL) {
	return NULL;

    }

    statePtr->acceptProc = NULL;
    statePtr->acceptProcData = NULL;

    sprintf(channelName, "sock%d", statePtr->fds->fd);

    statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName,
	    statePtr, (TCL_READABLE | TCL_WRITABLE));
    if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation",
	    "auto crlf") == TCL_ERROR) {
	Tcl_Close(NULL, statePtr->channel);
	return NULL;
    }
    return statePtr->channel;










}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_MakeTcpClientChannel --
 *







|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




<
|
<
<
>


<
<
<
|

|
|
|

|


|
>
>
>
>
>
>
>
>
>
>







1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061

1062


1063
1064
1065



1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
    const char *host,		/* Host on which to open port. */
    const char *myaddr,		/* Client-side address */
    int myport,			/* Client-side port */
    int async)			/* If nonzero, attempt to do an asynchronous
				 * connect. Otherwise we do a blocking
				 * connect. */
{
    TcpState *state;
    const char *errorMsg = NULL;
    struct addrinfo *addrlist, *myaddrlist;
    char channelName[4+16+1];	/* "sock" + up to 16 hex chars + \0 */


    /*
     * Do the name lookups for the local and remote addresses.
     */
    if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) {
	goto error;
    }
    if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1,
                                &errorMsg)) {
        freeaddrinfo(addrlist);
        goto error;
    }

    /*
     * Allocate a new TcpState for this socket.
     */
    state = ckalloc(sizeof(TcpState));
    memset(state, 0, sizeof(TcpState));
    state->flags = async ? TCP_ASYNC_CONNECT : 0;
    state->addrlist = addrlist;
    state->myaddrlist = myaddrlist;
    state->fds = ckalloc(sizeof(TcpFdList));
    memset(state->fds, (int) 0, sizeof(TcpFdList));
    state->fds->fd = -1;

    /*
     * Create a new client socket and wrap it in a channel.
     */

    if (CreateClientSocket(interp, state) != TCL_OK) {


        goto error;
    }




    sprintf(channelName, "sock%lx", (long)state);

    state->channel = Tcl_CreateChannel(&tcpChannelType, channelName,
                                       state, (TCL_READABLE | TCL_WRITABLE));
    if (Tcl_SetChannelOption(interp, state->channel, "-translation",
	    "auto crlf") == TCL_ERROR) {
	Tcl_Close(NULL, state->channel);
	return NULL;
    }
    return state->channel;

error:
    if (interp != NULL) {
        Tcl_AppendResult(interp, "couldn't open socket: ",
                         Tcl_PosixError(interp), NULL);
        if (errorMsg != NULL) {
            Tcl_AppendResult(interp, " (", errorMsg, ")", NULL);
        }
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_MakeTcpClientChannel --
 *