Name-based Virtual Hosting in TCP | ||
Hubert Chao | Brian Kowolowski | |
Jed Liu | Jeffrey M. Vinocur |
Our project involves the addition of name-based virtual hosting support to the Transport Control Protocol [TCP]. This involves sending hostname information at the beginning of each connection, similar in spirit to virtual hosting on the web [HTTP/1.1] but at a lower level.
There are a few problems which can be solved with the modification we describe.
We have designed the modifications to TCP necessary to ``cure'' all three of these problems, and implemented them in the Linux 2.4 kernel. We also present as proof-of-concept the minimal modifications to several applications necessary to make use of these changes. We did not investigate the changes to the kernel's routing functionality required to implement point (3) above.
In principle, IP-based virtual hosting might be sufficient for point (1) above; simply have one IP address for each virtual host. But in practice, this is inadequate:
(In fact, there might be no limit to the number of virtual hosts desired; imagine the administrator of some example.tld wanting requests for any host in the example.tld domain to be handled dynamically, generating content depending on the hostname. This is not even possible using IP-based virtual hosting.)
Certainly name-based virtual hosting is far cleaner and more useful, in principle, than IP-based virtual hosting. But the domain name used in the request is not available to the server from TCP/IP. As a result, protocol support is required in order to do name-based virtual hosting. But it is too late to get this sort of support in all of the application protocols. The solution is a method which requires software support only (that is, no protocol modifications). In addition, putting support in TCP reduces the overall amount of duplication of design and code.
We chose to add the data to the initial SYN because the option is directly related to establishing a connection with the remote host.
Thus we have added our host request to the data portion of the SYN segment while maintaining complete backward compatibility with existing IPv4 implementations.
However, future additions beyond current usage may need to use the full binary octet capabilities in names, so attempts to store domain names in 7-bit ASCII or use of special bytes to terminate labels, etc., should be avoided. [DNS]
We take their advice and avoid null-terminated strings.
length
of 6 bytes: the 8-bit TCP kind
field, the
8-bit TCP length
field, a 16-bit field specifying the location of the
relevant section (see below) in the data section, and two 8-bit fields
specifying the lengths of the receiver name and sender name,
respectively.
+--------+--------+---------+--------+--------+--------+ |00101010|00000110| offset | rcvlen | sndlen | +--------+--------+---------+--------+--------+--------+ Kind=42 Length=6
As there are currently no other options which involve putting data in a SYN segment, the offset will likely always be zero. However, implementations should handle the offset in the event it becomes useful.
offset
bytes into the data
section, there should be a 16-bit checksum (in usual TCP ones-complement
fashion), followed by the two variable length fields for receiver and
sender hostname, respectively (the lengths, of course, are found in the
TCP header as described above).
+--------+--------+--- ---+--- ---+ | option checksum | ...rcv host... | ...snd host... | +--------+--------+--- ---+--- ---+
The checksum is, of course, stored in network byte order.
host_info
structhost_info
struct:
struct host_info { __u8 rcv_host[TCP_MAX_HOST_LEN + 1]; __u8 rcv_host_len; __u8 snd_host[TCP_MAX_HOST_LEN + 1]; __u8 snd_host_len; };
This defines the basic data structure that an API programmer would use to interface with our TCP option.
setsockopt
setsockopt(2)
API
function is used to set the sender and receiver hostnames for a socket.
At present, this is how the client indicates, before the call to
connect(2), which hostname it used to obtain the server's IP address.
It might also be useful in the future on the server, before the call to
bind(2), as described in Future Work below.
Usage looks like:
struct host_info hosti; socklen_t optlen = sizeof(struct host_info); /* initialize fields of "hosti" here, including length fields */ if (setsockopt(sockfd, SOL_TCP, TCP_HOSTS, &hosti, optlen) < 0) { /* an error occurred */ }
The kernel may also set the sender and receiver hostnames for a socket without the application calling setsockopt(2), for example if an incoming SYN segment includes the HOSTS option.
Calls to setsockopt(2)
for the TCP_HOSTS option are not useful after the connection has
been initiated. The only possible error (other than the normal
setsockopt(2)
errors) is
EINVAL, indicating that the optlen passed in was not acceptable.
getsockopt
getsockopt(2)
API
function is used to recover the current sender and reciever hostnames
for a socket. This is how the server determines what hostnames, if any,
the client specified when initiating the connection. Usage looks like:
struct host_info hosti; socklen_t optlen = sizeof(struct host_info); if (getsockopt(sockfd, SOL_TCP, TCP_HOSTS, &hosti, &optlen) < 0) { /* an error occurred */ } /* use fields of "hosti" here */
The caller should be warned that because of the potential for binary data (see the discussion in Design decisions), the hostname strings are not guaranteed to be terminated by a '\0' character.
The length returned in the final argument
to getsockopt(2)
is the
length of the first string in the host_info
struct (that
is, the value of the rcv_host_len
field), provided that the
input length was sufficient to store at least that string. This means
that if the server is only interested in the receiver hostname field,
the following idiom is possible:
socklen_t optlen = TCP_MAX_HOST_LEN; char hostname[optlen]; if (getsockopt(sockfd, SOL_TCP, TCP_HOSTS, hostname, &optlen) < 0) { /* an error occurred */ } /* use "optlen" and "hostname" fields here */
The only possible error (other than the normal
getsockopt(2)
errors)
is EOPNOTSUPP, which indicates that no hostname information is
currently available for this socket (for example, the client did
not send the HOSTS option). Note that
getsockopt(2)
will return
EOPNOTSUPP before examining the optval
parameter; thus an
input of NULL
will allow the caller to determine if hostname
information is available without allocating any storage.
host_info.h
setsockopt(2)
and
getsockopt(2)
will
return ENOPROTOOPT if the TCP_HOSTS option is not supported.
The modifications necessary to the Linux 2.4 kernel fall into several categories:
tcp_opt
struct associated with each
socket. This data structure is used to keep track of a variety of TCP
features which can be enabled or disabled in certain circumstances.
setsockopt(2)
and
getsockopt(2)
function that
were extended to handle our new option. See
Networking API Modifications
for details.
% sysctl net.ipv4.tcp_hosts
# sysctl -w net.ipv4.tcp_hosts=1
# sysctl -w net.ipv4.tcp_hosts=0
tclient
and tserver
tclient
and tserver
. tclient
is a simple,
telnet-like client. It simply listens on stdin
and sends the input
to the server. tserver
is a telnet-like server: it waits for a
connection and writes any data received from the socket to stdout
.
thttpd
Modifying the code to support our option consisted of about 10 lines of changes. This involved getting the receive host information out of the option, adding a field to pass the hostname along to where it is needed, and then using the receive host information. We first check the option's receive host for a virtual host. If the option is not present, then we fall back on the default thttpd behavior.
wget
wget
.
The modification involved less than 10 lines of changes, consisting of
initializing the receiver host information in the host_info
struct
with the hostname of the machine being contacted and setting the socket
option before connecting to the server.
To test this, we brought up a thttpd
server within a user-mode
Linux kernel that supported our extension. This server had virtual
hosts bound to the names foobaar
, 192.168.20.20
, 127.0.0.1
,
and localhost
. Each virtual host had /index.html
file which
announced the virtual host on which the file was located.
On a separate machine running a copy of our kernel, we used wget
to
contact the server at 192.168.20.20. As expected, the page returned
indicated that it was being served by the appropriate virtual host.
To test the other virtual hosts, we used tclient
, which allowed us
to specify a value for the rcv_host
field in the outgoing connection.
We have tested a variety of interactions. We can connect with ssh from a non-compliant system to a compliant one, and we can ssh from a compliant system to a non-compliant one. We can connect to a compliant system running our modified thttpd server from a non-compliant system, from a compliant system with a non-compliant application, and from a compliant system with a compliant application, such as our modified wget.
bind(2)
to allow each endpoint to be specified as a triple of address,
port, and hostname, instead of the current pair of address and port.
This would allow servers to easily do virtual hosting by binding to each
hostname that they are to serve. This would allow virtual hosting with
extremely minimal changes to already deployed server applications. It
would also allow the kernel to make the decision to reject a connection
without the overhead of switching context to the server process.
http://www.iana.org/assignments/tcp-parameters
http://solutions.cit.cornell.edu/doc/TechnicalArchitecture.pdf
http://www.acme.com/software/thttpd/
http://www.zeus.com/library/articles/hosting.html