I'm trying configuring programmatically the MSS of my TCP connection on a GNU/Linux system, specifically Ubuntu 12.04, kernel 3.2.0-68-generic
According to man 7 tcp
TCP_MAXSEG The maximum segment size for outgoing TCP packets. If this option is set before connection establishment, it also changes the MSS value announced to the other end in the initial packet.Values greater than the (eventual) interface MTU have no effect. TCP will also impose its minimum and maximum bounds over the value provided.
this let me think I can configure the value before I connect the TCP socket. I wrote a small chunk of code to create a socket and I used setsockopt() to configure the MSS. In my test I'm setting the mss to 1000B.
The code calls setsockopt() followed by getsockopt() to double check the value has been properly configured. Both syscalls return 0 (no error). After that I connect to a remote host to verify via tcpdump the used mss is correct. Here what I see:
- returned MSS value from getsockopt() is always 536 Byte
- tcpdump shows the configured MSS in the syn packet
Modified my code to configure the MSS after I connect the socket.
- returned MSS value from getsockopt() is 1448 Bye
Is there a correct way to explain such behavior?
A couple of notes:
- according to wikipedia 536 B = MaxIPDatagramSize - IPHeaderSize - TcpHeaderSize this is to avoid IP packet fragmentation.
- creating a socket and connecting it (no setsockopt() call), displays an mss of 536 B returned by getsockopt() but tcpdump shows an announced mss of 1460 B in the SYN packet, that makes sense being 1500 - IpHeader - TcpHeader
Below, if you are interested, is my code:
int setSocketMss( int i_sd, int i_mss )
{
int res = 0;
int mss = i_mss;
socklen_t len = sizeof( mss );
res = ::setsockopt( i_sd, IPPROTO_TCP, TCP_MAXSEG, &mss, len );
if ( res < 0 )
{
qDebug() << "error: cannot configure mss for socket" << i_sd;
return -1;
}
res = ::getsockopt( i_sd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len );
if ( mss != i_mss )
{
qDebug() << "MSS set to" << i_mss << "but read value is" << mss;
}
else
{
qDebug() << "MSS for socket" << i_sd << " has been set to" << mss;
}
return mss;
}
void configureAddrStruct( const QString & i_ipAddress,
quint16 i_port,
struct sockaddr_in & o_sockaddr )
{
o_sockaddr.sin_addr.s_addr = htonl( QHostAddress(i_ipAddress).toIPv4Address() );
o_sockaddr.sin_port = htons( i_port );
o_sockaddr.sin_family = PF_INET;
memset( o_sockaddr.sin_zero, 0, sizeof(o_sockaddr.sin_zero) );
}
int main(int argc, char *argv[])
{
int sd = ::socket( PF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto );
if ( -1 == sd )
{
qDebug() << "erro creating socket";
exit (1);
}
else
{
qDebug() << "created socket:" << sd;
}
setSocketMss( sd, 1000 );
struct sockaddr_in localAddress;
struct sockaddr_in remoteAddress;
configureAddrStruct( "192.168.23.7", 0, localAddress );
configureAddrStruct( "192.168.23.176", 9999, remoteAddress );
int res = ::bind( sd,
reinterpret_cast<const sockaddr *>( &localAddress ),
sizeof(localAddress) );
if ( -1 == res )
{
qDebug() << "error binding socket to local address";
exit(2);
}
//setSocketMss( sd, 1000 );
res = ::connect( sd,
reinterpret_cast<const sockaddr*>( &remoteAddress ),
sizeof(remoteAddress) );
if ( -1 == res )
{
qDebug() << "error connecting to remote host";
::perror( "connect()" );
exit(2);
}
//setSocketMss( sd, 1000 );
return 0;
}