Nicholas Allen's Indigo Blog

Windows Communication Foundation From the Inside

April, 2006

  • Nicholas Allen's Indigo Blog

    Basics of Transport Security

    • 7 Comments

    I've gotten several questions about how transport security works. I'm putting together a list of transport security topics to talk about over the next few months to cover this topic. Before getting to that list though, I thought I'd talk a little bit about what transport security does.

    Transport security protects the transfer of a message. Security is provided on a point-to-point basis. The lifetime of the security session ends when the message arrives at an endpoint. If a message goes through multiple hops to reach its destination, the message needs to be resecured for each hop. This is different than message security, which can be used to provide end-to-end security regardless of the number of hops you're going through. No matter what method you use though, security is providing any of the following three things.

    Confidentiality. Confidentiality means that the contents of the message are kept secret from unintended listeners. An unintended listener is typically going to be someone that is trying to eavesdrop on your messages, although it's possible for the unintended listener to come from logging or other normal network monitoring. Confidentiality protects you from spying.

    Integrity. Message integrity means that you have confidence that the message you received is the same as the one that the sender sent. It's possible to have confidentiality without integrity. Someone can hand you an encrypted message, and you can start changing bits in the message without knowing what those bits mean. Similarly, it's possible to have integrity without confidentiality. You can transmit a message whose contents are clear, but provide a tamper-resistant envelope for the message. Any attempt to change the message will result in evidence of tampering.

    Authentication. Authentication means that you have confidence that you know the identity of the other party in the communication. Confidentiality and integrity are not very useful unless the message is authentic. You can have a message that was kept secret and was not tampered with, but the author of the message is not who you thought it was. Authentication can be for the client, server, or mutually for both parties of the conversation.

    Here's the list of topics I'm thinking about so far:

    • Basics of Encryption and Hashing
    • Security Key Management
    • Basics of Security Certificates
    • How Stream Ciphers Work
    • How Block Ciphers Work
    • How RSA Works
    • How Diffie-Hellman Key Exchange Works

    Next time: Configuring WCF for NATs and Firewalls

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 7: Request Channel

    • 3 Comments

    The request channel is what's actually going to send messages from the client and receive a response from the server. In the request-reply channel shape, sending and receiving messages are inextricably tied together. That means the client just has to implement this one Request operation. As I mentioned at the beginning, this sample keeps things simple by only implementing the synchronous version of the communication methods. That means we'll send a request to the server and then wait for either a response to come back or for the operation timeout to expire.

    using System;
    using System.IO;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Threading;
    
    namespace FileTransport
    {
       class FileRequestChannel : FileChannelBase, IRequestChannel
       {
          readonly Uri via;
          readonly object writeLock;
    
          public FileRequestChannel(BufferManager bufferManager, MessageEncoderFactory encoderFactory, EndpointAddress address,
             FileRequestChannelFactory parent, Uri via)
             : base(bufferManager, encoderFactory, address, parent, parent.Streamed, parent.MaxReceivedMessageSize)
          {
             this.via = via;
             this.writeLock = new object();
          }
    
          public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
          {
             throw new Exception("The method or operation is not implemented.");
          }
    
          public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
          {
             return BeginRequest(message, DefaultReceiveTimeout, callback, state);
          }
    
          public Message EndRequest(IAsyncResult result)
          {
             throw new Exception("The method or operation is not implemented.");
          }
    
          public Message Request(Message message, TimeSpan timeout)
          {
             ThrowIfDisposedOrNotOpen();
             lock (this.writeLock)
             {
                try
                {
                   File.Delete(PathToFile(Via, "reply"));
                   using (FileSystemWatcher watcher = new FileSystemWatcher(Via.AbsolutePath, "reply"))
                   {
                      ManualResetEvent replyCreated = new ManualResetEvent(false);
                      watcher.Changed += new FileSystemEventHandler(
                         delegate(object sender, FileSystemEventArgs e) { replyCreated.Set(); }
                      );
                      watcher.EnableRaisingEvents = true;
                      WriteMessage(PathToFile(via, "request"), message);
                      if (!replyCreated.WaitOne(timeout, false))
                      {
                         throw new TimeoutException(timeout.ToString());
                      }
                   }
                }
                catch (IOException exception)
                {
                   throw ConvertException(exception);
                }
                return ReadMessage(PathToFile(Via, "reply"));
             }
          }
    
          public Message Request(Message message)
          {
             return Request(message, DefaultReceiveTimeout);
          }
    
          public Uri Via
          {
             get { return this.via; }
          }
       }
    }
    

    This is a file transport so we need to use the file system to signal when messages are sent. The procedure that this transport uses is to create a file system watcher for a particular file URI so that we're notified when a message arrives. The channel waits on the file system watcher for only a limited time to implement the timeout mechanism.

    This doesn't perfectly fulfill the requirements of the timeout model because we're neglecting to measure how much time is spent sending the message. Unfortunately, we haven't shipped a good sample yet for measuring operation times to use in a situation like this. In this case, it rarely makes a difference due to the speed of file transfers, but a robust custom transport can't get away with this. The right solution is to time the entire sequence of IO operations so that the event is fired when the timeout is reached. We'd also have to have some way of cancelling the operation that was in progress when the event occurs.

    Next time: Basics of Transport Security

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 6: Channel Factory

    • 2 Comments

    We've passed the midway point for the custom file transport example. Now that we've got the underlying file code, today's article starts building out the rest of the client-side code. Here's the story so far:

    1. Building a Custom File Transport, Part 1: Planning
    2. Building a Custom File Transport, Part 2: Server
    3. Building a Custom File Transport, Part 3: Client
    4. Building a Custom File Transport, Part 4: Binding and Binding Element
    5. Building a Custom File Transport, Part 5: Channel Basics

    The next piece of the puzzle is the channel factory. The factory is rather simple because all it needs to do is configure and build out channels for us on demand. This factory implementation will take the binding element that describes the file transport, combine that with the binding element that describes the message encoding, and build an instance of the transport with all of the configured options. Once the factory is constructed from the binding, there's no sharing of configuration information. This allows you to construct many factories from a single binding, and in the future we'll be constructing many channels from a single factory.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    namespace FileTransport
    {
    class FileRequestChannelFactory : ChannelFactoryBase<IRequestChannel>
    {
    readonly BufferManager bufferManager;
    readonly MessageEncoderFactory encoderFactory;
    public readonly long MaxReceivedMessageSize;
    readonly string scheme;
    public readonly bool Streamed;

    public FileRequestChannelFactory(FileTransportBindingElement transportElement, BindingContext context)
    : base(context.Binding)
    {
    MessageEncodingBindingElement messageEncodingElement = context.UnhandledBindingElements.Remove<MessageEncodingBindingElement>();
    this.bufferManager = BufferManager.CreateBufferManager(transportElement.MaxBufferPoolSize, int.MaxValue);
    this.encoderFactory = messageEncodingElement.CreateMessageEncoderFactory();
    MaxReceivedMessageSize = transportElement.MaxReceivedMessageSize;
    this.scheme = transportElement.Scheme;
    Streamed = transportElement.Streamed;
    }

    protected override IRequestChannel OnCreateChannel(EndpointAddress address, Uri via)
    {
    return new FileRequestChannel(this.bufferManager, this.encoderFactory, address, this, via);
    }

    public override MessageVersion MessageVersion
    {
    get { return MessageVersion.Default; }
    }

    public override string Scheme
    {
    get { return this.scheme; }
    }
    }
    }

    Next time: Building a Custom File Transport, Part 7: Request Channel

  • Nicholas Allen's Indigo Blog

    Networking with NATs and Firewalls

    • 1 Comments

    Two weeks ago I talked a little bit about how Network Address Translators (NATs) work, but only hinted at the broader picture. There's a two step process for getting from those technical details to how you should deal with NATs and firewalls in WCF. Today's article covers a more general view of what NATs and firewalls are. Next week, I'll present the rollup article that incorporates all of these details so far and gives advice for using WCF. You'll have a head start on the article next week because it will include the material you're reading today as an introduction.

    How NATs Affect Communication

    Network address translation was created to enable several machines to share a single external IP address. A port-remapping NAT maps an internal IP address and port for a connection to an external IP address with a new port number. The new port number allows the NAT to correlate return traffic with the original communication. Many home users now have an IP address that is only privately routable and rely on a NAT to provide global routing of packets.

    A NAT does not provide a security boundary. However, common NAT configurations prevent the internal machines from being directly addressed. This both protects the internal machines from some unwanted connections and makes it difficult to write server applications that need to asynchronously send data back to the client. The NAT rewrites the addresses in packets to make it seem like connections are originating at the NAT machine. This causes the server to fail when it attempts to open a connection back to the client. If the server uses the client's perceived address, it will fail because the client address is not publicly routable. If the server uses the NAT's address, it will fail to connect because no application is listening on that machine.

    Some NATs support the configuration of forwarding rules to allow external machines to connect to a particular internal machine. The instructions for configuring forwarding rules varies between different NATs and asking end users to change their NAT configuration is not recommended for most applications. Many end users either cannot or do not want to change their NAT configuration for a particular application.

    How Firewalls Affect Communication

    A firewall is a software or hardware device that applies rules to the traffic passing through to decide whether to allow or deny passage. Firewalls can be configured to examine incoming, outgoing, or both streams of traffic. The firewall provides a security boundary for the network at either the edge of the network or on the endpoint host. Business users have traditionally kept their servers behind a firewall to prevent malicious attacks. Since the introduction of the personal firewall in Windows XP Service Pack 2, the number of home users behind a firewall has greatly increased as well. This makes it very likely that one or both ends of a connection will have a firewall examining packets.

    Firewalls vary greatly in terms of their complexity and capability for examining packets. Simple firewalls apply rules based on the source and destination addresses and ports in packets. Intelligent firewalls can also examine the contents of packets to make decisions. These firewalls come in many different configuration and are often used for specialized applications.

    A common configuration for a home user firewall is to prohibit incoming connections unless an outgoing connection was made to that machine previously. A common configuration for a business user firewall is to prohibit incoming connections on all ports except a group specifically identified. An example is a firewall that prohibits connections on all ports except for ports 80 and 443 to provide HTTP and HTTPS service. Managed firewalls exist for both home and business users that permit a trusted user or process on the machine to change the firewall configuration. Managed firewalls are more common for home users where there is not a corporate policy controlling network usage.

    Next time: Building a Custom File Transport, Part 6: Channel Factory

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 5: Channel Basics

    • 3 Comments

    I've pulled out the actual mechanics of the file transport into a separate class in this example. Both the client and server sides of the channel are going to use this code. There's more than the average amount of code in this class so I've created a few parts. The first part has all of the supporting methods in the class. The second part has the methods for buffered transfers. The third part has the methods for streamed transfers. Streamed transfers in this example are a bit contrived because I didn't want the code for handling IO to overwhelm the code for the channel. I'll talk a little more about that with part three.

    using System;
    using System.IO;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    namespace FileTransport
    {
    abstract class FileChannelBase : ChannelBase
    {
    const int MaxBufferSize = 64 * 1024;
    const int MaxSizeOfHeaders = 4 * 1024;

    readonly EndpointAddress address;
    readonly BufferManager bufferManager;
    readonly MessageEncoder encoder;
    readonly long maxReceivedMessageSize;
    readonly bool streamed;

    public FileChannelBase(BufferManager bufferManager, MessageEncoderFactory encoderFactory, EndpointAddress address, ChannelManagerBase parent,
    bool streamed, long maxReceivedMessageSize)
    : base(parent)
    {
    this.address = address;
    this.bufferManager = bufferManager;
    this.encoder = encoderFactory.CreateSessionEncoder();
    this.maxReceivedMessageSize = maxReceivedMessageSize;
    this.streamed = streamed;
    }

    protected Message ReadMessage(string path)
    {
    if (this.streamed)
    {
    return StreamedReadMessage(path);
    }
    return BufferedReadMessage(path);
    }

    protected void WriteMessage(string path, Message message)
    {
    if (this.streamed)
    {
    StreamedWriteMessage(path, message);
    }
    else { BufferedWriteMessage(path, message); } } public EndpointAddress RemoteAddress
    {
    get { return this.address; }
    }

    protected static Exception ConvertException(Exception exception)
    {
    Type exceptionType = exception.GetType();
    if (exceptionType == typeof(System.IO.DirectoryNotFoundException) ||
    exceptionType == typeof(System.IO.FileNotFoundException) ||
    exceptionType == typeof(System.IO.PathTooLongException))
    {
    return new EndpointNotFoundException(exception.Message, exception);
    }
    return new CommunicationException(exception.Message, exception);
    }

    protected static string PathToFile(Uri path, String name)
    {
    UriBuilder address = new UriBuilder(path);
    address.Scheme = "file";
    address.Path = Path.Combine(path.AbsolutePath, name);
    return address.Uri.AbsolutePath;
    }
    }
    }

    The first half of this block sets up the options for the channel. We'll support a Streamed option for controlling the transfer mode and quotas for MaxReceivedMessageSize and MaxBufferPoolSize. You'd want to use the TransferMode enumeration if you were really doing this kind of streaming control, but this is trying to keep the example as simple as possible. The second half of this block defines some helper methods. It's bad to throw exceptions that don't descend from CommunicationException. The exception to this exception rule is design time exceptions, terminally fatal exceptions, or where you're indicating that there's a bug in the calling code.

    Message BufferedReadMessage(string path)
    {
    byte[] data;
    long bytesTotal;
    try { using (FileStream stream = new FileStream(path, FileMode.Open))
    {
    bytesTotal = stream.Length;
    if (bytesTotal > int.MaxValue)
    {
    throw new CommunicationException(
    String.Format("Message of size {0} bytes is too large to buffer. Use a streamed transfer instead.", bytesTotal)
    );
    }
    if (bytesTotal > this.maxReceivedMessageSize)
    {
    throw new CommunicationException(String.Format("Message exceeds maximum size: {0} > {1}.", bytesTotal, maxReceivedMessageSize));
    }
    data = this.bufferManager.TakeBuffer((int)bytesTotal);
    int bytesRead = 0;
    while (bytesRead < bytesTotal)
    {
    int count = stream.Read(data, bytesRead, (int)bytesTotal - bytesRead);
    if (count == 0)
    {
    throw new CommunicationException(String.Format("Unexpected end of message after {0} of {1} bytes.", bytesRead, bytesTotal));
    }
    bytesRead += count;
    }
    }
    }
    catch (IOException exception)
    {
    throw ConvertException(exception);
    }
    ArraySegment<byte> buffer = new ArraySegment<byte>(data, 0, (int)bytesTotal);
    return this.encoder.ReadMessage(buffer, this.bufferManager);
    }

    void BufferedWriteMessage(string path, Message message)
    {
    ArraySegment<byte> buffer;
    using (message)
    {
    this.address.ApplyTo(message);
    buffer = this.encoder.WriteMessage(message, MaxBufferSize, this.bufferManager);
    }
    try { using (FileStream stream = new FileStream(path, FileMode.Create))
    {
    stream.Write(buffer.Array, buffer.Offset, buffer.Count);
    }
    }
    catch (IOException exception)
    {
    throw ConvertException(exception);
    }
    }

    Buffered transfers show off the use of our BufferManager class to pass data around. The buffer manager takes care of sharing and allocating byte array buffers, which sometimes have noticeable collection costs when there's a lot of traffic. If you reuse buffers, I would recommend using our BufferManager class rather than writing your own. Manual addressing is ignored for the file transport. It really doesn't make sense to support specifying a custom address because the destination of the message is an inherent part of the channel setup.

    Message StreamedReadMessage(string path)
    {
    try { Stream stream = File.Open(path, FileMode.Open); long bytesTotal = stream.Length;
    if (bytesTotal > maxReceivedMessageSize)
    {
    throw new CommunicationException(String.Format("Message exceeds maximum size: {0} > {1}.", bytesTotal, maxReceivedMessageSize));
    }
    return this.encoder.ReadMessage(stream, MaxSizeOfHeaders);
    }
    catch (IOException exception)
    {
    throw ConvertException(exception);
    }
    }

    void StreamedWriteMessage(string path, Message message)
    {
    using (message)
    {
    this.address.ApplyTo(message);
    try { using (Stream stream = File.Open(path, FileMode.Create))
    {
    this.encoder.WriteMessage(message, stream);
    }
    }
    catch (IOException exception)
    {
    throw ConvertException(exception);
    }
    }
    }

    Streamed transfers are simpler for two reasons in this example. The first reason is that the native file APIs are stream-oriented rather than byte-array oriented making the coupling with WCF easier. The second reason is that this is an extremely cheesy stream implementation. We're taking advantage of a lot of implicit buffering, especially the buffering of the file by the file system. To make this a real streaming example, here's what we'd need to do:

    1. Add some framing so that we can tell whether the stream is expected to continue or end after we pull each chunk of data.
    2. Create a message class to wrap the file stream so that the reader is pulling directly off of the file. Note that the reader will block if the file stream runs out of data but the framing indicates that more data is expected in the future.
    3. Handle the overlapped reads and writes going on in the file system to keep our view of the stream consistent.

    This would have made the simple example much more complicated without really adding anything to the discussion about channels.

    Next time: Networking with NATs and Firewalls

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 4: Binding and Binding Element

    • 3 Comments

    Creating a binding and binding element for your transport is entirely optional if you're just using the channel model. It is possible to do everything you need through the channel factory and listener as long as you make those public. This stops being true at higher levels of abstraction in WCF. If you want to use the service model, then having at least a binding element is required. Including a binding that shows off your binding element is polite because it removes the need for users to create their own custom binding for your transport. Due to this, it's customary to just always create both a binding and binding element, and hide the channel factory and listener inside the assembly.

    The binding for this example is extremely simple. We just have the transport element and a message encoder element. Remember, this transport does not have a default encoder so supplying a message encoder is mandatory. I've used the text message encoder so that you can look inside the last message sent if something goes wrong.

    using System.ServiceModel.Channels;
    
    namespace FileTransport
    {
       public class FileTransportBinding : Binding
       {
          readonly MessageEncodingBindingElement messageElement;
          readonly FileTransportBindingElement transportElement;
    
          public FileTransportBinding()
          {
             this.messageElement = new TextMessageEncodingBindingElement();
             this.transportElement = new FileTransportBindingElement();
          }
    
          public override BindingElementCollection CreateBindingElements()
          {
             BindingElementCollection elements = new BindingElementCollection();
             elements.Add(this.messageElement);
             elements.Add(this.transportElement);
             return elements.Clone();
          }
    
          public override string Scheme
          {
             get { return this.transportElement.Scheme; }
          }
       }
    }
    

    The transport binding element provides three things in this example. The first is that it provides the scheme that this transport will use. I've picked the "my.file" scheme in this case. The second is that it provides the constraints on creating channel factories and listeners. This example only supports the request-reply message exchange pattern, so the binding element checks that you have the correct channel shape. The third is that it provides configurable settings for the transport. I've included a setting to control whether the transport uses streamed transfers. Additionally, you inherit the quota settings for the maximum message size and maximum buffer pool size from the abstract base class. We'll make sure to implement those quotas when we get there.

    using System;
    using System.ServiceModel.Channels;
    
    namespace FileTransport
    {
       public class FileTransportBindingElement : TransportBindingElement
       {
          public bool Streamed;
    
          public FileTransportBindingElement()
          {
             Streamed = false;
          }
    
          FileTransportBindingElement(FileTransportBindingElement other)
             : base(other)
          {
             Streamed = other.Streamed;
          }
    
          public override string Scheme
          {
             get { return "my.file"; }
          }
    
          public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
          {
             return typeof(TChannel) == typeof(IRequestChannel);
          }
    
          public override bool CanBuildChannelListener<TChannel>(BindingContext context)
          {
             return typeof(TChannel) == typeof(IReplyChannel);
          }
    
          public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
          {
             if (context == null)
             {
                throw new ArgumentNullException("context");
             }
             if (!CanBuildChannelFactory<TChannel>(context))
             {
                throw new ArgumentException(String.Format("Unsupported channel type: {0}.", typeof(TChannel).Name));
             }
             return (IChannelFactory<TChannel>)(object)new FileRequestChannelFactory(this, context);
          }
    
          public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
          {
             if (context == null)
             {
                throw new ArgumentNullException("context");
             }
             if (!CanBuildChannelListener<TChannel>(context))
             {
                throw new ArgumentException(String.Format("Unsupported channel type: {0}.", typeof(TChannel).Name));
             }
             return (IChannelListener<TChannel>)(object)new FileReplyChannelListener(this, context);
          }
    
          public override BindingElement Clone()
          {
             return new FileTransportBindingElement(this);
          }
       }
    }
    

    Next time: Building a Custom File Transport, Part 5: Channel Basics

  • Nicholas Allen's Indigo Blog

    Counting Down to TechEd 2006

    • 1 Comments

    I'm devoting today's post to setting up some connections for TechEd. TechEd 2006 is in Boston from June 11th to 16th, just eight weeks away now. I'll be there, along with quite a few other Indigo'ers, talking about what we've brought to the table with Version 1 of the Windows Communication Foundation. You can even take the time to tell us what we need to do in Version 2. We're always on the lookout for new ideas.

    I'm looking for any suggestions that you've got for informal talk topics. We'll be manning the booths and ready to chat in between the big talks, but it's a lot more fun to have some semi-organized sessions. Since these talks aren't on the master agenda, we can be flexible with the time and have short or irregular length talks depending on the topic. Even if I'm not the right person for the talk, I'll try to find someone who is. You do not have to be attending TechEd 2006 to make suggestions. I'll turn any content that gets used in the talks I do into blog entries after the show is over.

    Back to building our custom transport!

    Next time: Building a Custom File Transport, Part 4: Binding and Binding Element

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 3: Client

    • 3 Comments

    I once again used the trick of taking the code from the previous example for the client. The File Transport client uses the same binding class as the server. This isn't actually a hard requirement in WCF. We never force you to share code between the client and server if you don't want to. It's sufficient for the client and server bindings to merely be compatible. However, I didn't have a good reason to demonstrate this in the example so I reused the existing binding.

    The timeouts I'm using for the client and server are somewhat arbitrary. File operations of this size shouldn't take five seconds unless you've got a very slow device or you're trying to access files on a network share. The server has a one minute timeout waiting for incoming connections. That should be plenty of time to go start a client for testing but still short enough that you can sit and watch the server terminate gracefully when the timeout expires.

    The description of the message protocol is in yesterday's post about the server.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using FileTransport;
    
    namespace FileRequestChannelClient
    {
       class FileRequestChannelClient
       {
          static void Main(string[] args)
          {
             Console.Write("Creating factory...");
             Binding binding = new FileTransportBinding();
             IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>();
             factory.Open(TimeSpan.FromSeconds(5));
             Console.WriteLine(" done.");
             Console.Write("Creating channel...");
             using (factory)
             {
                Uri uri = new Uri("my.file://localhost/x");
                IRequestChannel channel = factory.CreateChannel(new EndpointAddress(uri));
                Console.WriteLine(" done.");
                Console.Write("Opening channel...");
                channel.Open(TimeSpan.FromSeconds(5));
                Console.WriteLine(" done.");
                while (true)
                {
                   Console.Write("Enter some text (Ctrl-Z to quit): ");
                   String text = Console.ReadLine();
                   if (text == null)
                   {
                      break;
                   }
                   Console.Write("Sending request...");
                   Message requestMessage = Message.CreateMessage(MessageVersion.Default, "http://reflect", text);
                   Message replyMessage = channel.Request(requestMessage, TimeSpan.FromSeconds(5));
                   Console.WriteLine(" done.");
                   using (replyMessage)
                   {
                      Console.WriteLine("Processing reply: {0}", replyMessage.Headers.Action);
                      Console.WriteLine("Reply: {0}", replyMessage.GetBody<string>());
                   }
                }
                channel.Close(TimeSpan.FromSeconds(5));
             }
          }
       }
    }
    

    Next time: Counting Down to TechEd 2006

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 2: Server

    • 4 Comments

    For the server, I took the example code I posted a few weeks ago and started making modifications. I've replaced the binding with a new one for the File Transport (we'll cover that code later this week) and added some reasonable timeouts for all of the operations. I also added some debug messages for tracing if you want to play with this once the series is over and experiment.

    The action of the server is a very simple one for demonstration. The server will take a message with action equal to "http://reflect" and reverse the contents of the message body. This reply gets sent back in a message with action equal to "http://reflection". Why http in the action? Just to show that actions are simply a description of how the message should be processed and don't have any inherent meaning until you give them one.

    using System;
    using System.ServiceModel.Channels;
    using FileTransport;
    
    namespace FileReplyChannelServer
    {
       class FileReplyChannelServer
       {
          static void Main(string[] args)
          {
             Console.Write("Creating listener...");
             Binding binding = new FileTransportBinding();
             Uri uri = new Uri("my.file://localhost/x");
             IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(uri, new BindingParameterCollection());
             listener.Open(TimeSpan.FromSeconds(5));
             Console.WriteLine(" done.");
             Console.Write("Creating channel...");
             IReplyChannel channel = listener.AcceptChannel(TimeSpan.FromSeconds(5));
             channel.Open(TimeSpan.FromSeconds(5));
             Console.WriteLine(" done.");
             Console.Write("Waiting for request...");
             while (channel.WaitForRequest(TimeSpan.FromMinutes(1)))
             {
                using (IRequestContext context = channel.ReceiveRequest(TimeSpan.FromSeconds(5)))
                {
                   Console.WriteLine(" done.");
                   using (Message message = context.RequestMessage)
                   {
                      Console.WriteLine("Processing request: {0}", message.Headers.Action);
                      if (message.Headers.Action == "http://reflect")
                      {
                         string response = ProcessReflectRequest(message.GetBody<string>());
                         Console.Write("Sending reply...");
                         Message replyMessage = Message.CreateMessage(MessageVersion.Default, "http://reflection", response);
                         context.Reply(replyMessage, TimeSpan.FromSeconds(5));
                         Console.WriteLine(" done.");
                      }
                   }
                }
                Console.Write("Waiting for request...");
             }
             Console.WriteLine(" terminated.");
             channel.Close(TimeSpan.FromSeconds(5));
          }
    
          static string ProcessReflectRequest(string request)
          {
             char[] output = new char[request.Length];
             for (int index = 0; index < request.Length; index++)
             {
                output[index] = request[request.Length - index - 1];
             }
             return new string(output);
          }
       }
    }
    

    This code works with the recent February CTP bits. If you don't have a WCF project template set up, you'll need to add references to the System.ServiceModel and System.Runtime.Serialization dlls. In Visual Studio, use Add Reference on the project to do this.

    Next time: Building a Custom File Transport, Part 3: Client

  • Nicholas Allen's Indigo Blog

    Building a Custom File Transport, Part 1: Planning

    • 5 Comments

    During the last few weeks I've been going over various parts of the channel model and giving some commentary about using those classes. Looking back at the list of topics, we've already covered everything you need to build a custom transport. Today I'm starting a series that ties everything together by going step-by-step through building a custom transport over a file protocol.

    Here are the basics of the protocol. We're creating a new scheme called "my.file" that ignores the hostname and port and just uses the path of the URI. The path indicates a directory on the file system that we'll use to exchange messages. The transport will expose a request-reply interface. The request channel writes its message to a file in the directory called "request". The reply channel then reads the message and writes its response message to a file in the same directory called "reply". You can use any encoding you want with this protocol, but there is no default encoding.

    To keep the example simple, I've cut a few features out of the transport. You won't want to use this transport in a production system!

    • Only synchronous operations are supported. All of the asynchronous methods will throw if you call them. We don't call any methods behind your back at the channel model level, so as long as the client and server don't explicitly call an asynchronous method you'll never hit these code paths.
    • There is no additional security checking in the transport. The channel will try to write messages to wherever someone tells it to and the only thing that will stop it is any permission settings you have on your file system.
    • All exceptions are treated as fatal. This is a good way to demonstrate that the expected case doesn't throw any exceptions.
    • The transport is not optimized for performance. Even though the transport supports both buffered and streamed transfers, there are some inefficient data copies that take place. This transport uses the standard Message class, which is not going to be as optimized as a transport-specific message would be. You also aren't going to get top performance using synchronous operations.
    • There is only support for configuration through code. I haven't covered file-based configuration yet. When I write that series, I'll go back and extend this sample as a demonstration.

    I'm not going to call out specific past articles most of the time during this series. Almost everything has been talked about at one point in time if you look at the archive. Here's a master list of classes that get extended by the sample. You'll have to follow the links in the articles to get to parent classes and interfaces where applicable.

    1. BindingElement
    2. Binding
    3. IRequestChannel
    4. IReplyChannel
    5. IRequestContext
    6. ChannelBase
    7. ChannelFactoryBase and ChannelListenerBase

    There's actually a total of ten classes in the sample. The two classes that don't appear in the above list are the client and server.

    Next time: Building a Custom File Transport, Part 2: Server

  • Nicholas Allen's Indigo Blog

    Configuring HTTP

    • 4 Comments

    This article is now a part of the Windows SDK.

    Although most people think of web servers when they hear HTTP, it's entirely possible to self-host WCF services over HTTP. You do need to perform some configuration magic to make this happen, especially if you want to peacefully coexist with an existing server like IIS. You also need to deal with the stronger security for services on Vista. This article explains how.


    Configuring HTTP

    Using Windows Communication Foundation (WCF) over HTTP either requires the use of a host, such as IIS, or manual configuration of the HTTP settings through the HTTP Server API. This document describes configuring WCF when using HTTP and HTTPS.

    The instructions in this document use the httpcfg.exe tool to configure HTTP settings. This tool is a part of the Windows Support Tools download. With Windows Vista, it is now possible to configure these settings through the netsh tool. These settings are part of the http context of netsh.

    Configuring SSL Certificates

    Secure Sockets Layer (SSL) uses certificates on the client and server to store encryption keys. The server provides its SSL certificate when a connection is made so that the client can verify the identity of the server. The server can also request a certificate from the client to provide mutual authentication of both sides of the connection.

    Certificates are stored in a centralized store according to the IP address and port number of the connection. The special IP address 0.0.0.0 matches any IP address for the local machine. Note that the certificate store does not distinguish URLs based on the path. Services with the same IP address and port combination must share certificates even if the path in the URL for the services is different. Modifying the certificates stored on the computer requires administrative privileges.

    Use the "httpcfg.exe set ssl" command to register SSL certificates. The Windows Support Tools documentation explains the syntax for the httpcfg.exe tool. Client certificates require passing an additional "/f 2" flag to httpcfg.exe to indicate that the certificate may be used for the client side of mutual authentication.

    Configuring Namespace Reservations

    Namespace reservation assigns the rights for a portion of the HTTP URL namespace to a particular group of users. A reservation gives those users the right to create services that listen on that portion of the namespace. Reservations are URL prefixes, meaning that the reservation covers all subpaths of the reservation path. Namespace reservations permit various types of wildcarding. The HTTP Server API documentation describes the order of resolution between namespace claims that involve wildcards.

    Modifying the reservation rights for a portion of the namespace requires either administrative privileges or ownership of that portion of the namespace. Initially, the entire HTTP namespace belongs to the local administrator. Use the "httpcfg.exe set urlacl" command to change namespace reservations. The Windows Support Tools documentation explains the syntax for the httpcfg.exe tool.

    Configuring the IP Listen List

    The HTTP Server API only binds to an IP address and port once a user registers an URL. By default, the HTTP Server API binds to the port in the URL for all of the IP addresses of the machine. A conflict arises if an application that doesn't use the HTTP Server API has previously bound to that combination of IP address and port. The IP Listen List allows WCF services to coexist with applications that use a port for some of the IP addresses of the machine. If the IP Listen List contains any entries, the HTTP Server API will only bind to those IP addresses that the list specifies.

    Use the "httpcfg.exe set iplisten" command to modify the IP Listen List. The Windows Support Tools documentation explains the syntax for the httpcfg.exe tool. Modifying the IP Listen List requires administrative privileges. Issues Specific to Windows XP

    • HTTP addressing is not integrated into the personal firewall on Windows XP. An exception must be added to the firewall configuration to allow inbound connections using a particular URL.
    • IIS does not support port sharing on Windows XP. If IIS is running and a WCF service attempts to use a namespace with the same port, the WCF service will fail to start. IIS and WCF both default to using port 80. Either change the port assignment for one of the services or use the IP Listen List to assign the WCF service to a network adapter not used by IIS.

    Other Configuration Settings

    The HTTP Server API has some advanced configuration settings that are not available through httpcfg. These settings are maintained in the registry and apply to all applications running on the system that use the HTTP Server APIs. Microsoft Knowledge Base article 820129 describes these settings in detail. Most users should not need to change these settings.


    Next time: Building a Custom File Transport, Part 1: Planning

  • Nicholas Allen's Indigo Blog

    How Network Address Translation (NAT) Works

    • 6 Comments

    Statistics indicate that you likely would not be able to read this post without the help of network address translation. NATs are a mechanism for several machines to share a single external IP address. A port-remapping NAT maps an internal IP address and port for a connection to an external IP address with a new port number. The new port number allows the NAT to identify return traffic and route that back to the original machine. Many machines now have an IP address that is only privately routable and rely on a NAT to map them to a globally routable IP address.

    Going through a NAT doesn't actually provide a security boundary for your computer although people sometimes speak of NATs as if they do. There are many NAT configurations that explicitly prevent unwanted connections to your internal machines, but that isn't an inherent requirement of being a NAT. Let's talk about the NATs that do remove direct addressability of the internal machines though, as that configuration is generally what gives networking applications the most grief.

    When you remove direct addressability, it is no longer easy for a server to open a connection back to the client or for multiple clients to talk with each other in a peer-to-peer configuration. Here's a look at a basic NAT scenario:

    The NAT is going to be rewriting the source and destination ports and addresses in the IP and TCP headers. This makes it look to the remote machine like the connection is actually coming from the NAT. If the server then tries to open a connection back to the NAT, the NAT has no idea what to do with the connection since it's not actually running the application that the server is trying to connect to. Some NATs allow you to configure a static port forwarding rule to solve this problem. When an external machine attempts to connect to the NAT on a particular port, the NAT can route this connection to the specified internal machine.

    WCF has a variety of methods on both the client and server for working through NATs. I'll write up a post in a few weeks detailing what your options are. In the mean time, I've got a lot of WCF channel extensibility articles that I want to get out.

    Next time: Configuring HTTP

  • Nicholas Allen's Indigo Blog

    How TCPIP Works, Part 4 Demultiplexing Connections

    • 4 Comments

    Yesterday, we looked at the tax you pay for a typical network stack. Where does all that tax go? Well, some part of it is dedicated to actually delivering messages to you. That's kind of an essential tax to pay. Let's look at the same picture of data framing and try to identify some of the costs of delivering messages.

    Starting with the Ethernet header, we're going to be looking at parts of the message that help refine the message destination. Suppose a message has just arrived on your network cable. We now need to decide how to deliver the message. This process is called demultiplexing.

    1. Look at the 48-bit destination address in the Ethernet header. This tells us which of the network cards hooked up to the cable should receive the message. Notice that IP addresses are totally irrelevant at this layer even though that's probably how the message got to your network in the first place. We used ARP to figure out the address conversion so that the message will go to the right machine.
    2. Look at the frame type in the Ethernet header. This tells us whether the message is IP, ARP, RARP, or some other low-level protocol. ARP and RARP messages are going to be handled by the network driver and not your application. You will only see the IP messages coming up to you. Assume that we see a message using IP.
    3. Look at the protocol in the IP header. This tells us whether the message is ICMP, IGMP, TCP, UDP, or some other mid-level protocol. Depending on the framework that you're using, you might see none or all of these protocols in your application. When using WCF, you're very rarely going to be directly dealing with this protocol layer. Assume that we see a message using TCP.
    4. Look at the destination port number in the TCP header. This tells us which socket to send the message to that some application will be listening on. Some scenarios use the destination IP address or information about the message source to do additional processing. A firewall that doesn't inspect the contents of messages is probably filtering at this level.
    5. Look inside the message to perform high-level protocol filtering. For HTTP, the server might demultiplex based on the URL to handle virtual hosts using the domain and path. For SOAP, the server might demultiplex based on the To address or some other header. There is a lot of flexibility at this level because more processor power is generally available to inspect messages.
    6. Eventually, a message arrives at your application from across the Internet despite the fact that you didn't have to program any logic to make that happen.

    We're at the end of the TCP/IP series, but this information is going to continue to show up in the future. A couple of the posts in the next few weeks will be dedicated to talking about taking WCF through NATs and firewalls. You can't really understand how that happens unless you understand the lower-level protocols. Most firewalls are at a level below WCF.

    Next time: How Network Address Translation (NAT) Works

  • Nicholas Allen's Indigo Blog

    How TCPIP Works, Part 3: Framing Data

    • 6 Comments

    As someone that just uses WCF to get a job done, you may be wondering why it's important to know about all of those network protocols that I talked about yesterday. Well, in exchange for helping move your data around, every protocol layer you go through in the network stack charges you a tax.

    Here's what happens to your data getting to the network for even a lightweight protocol like TCP:

    This picture does not show the costs of securing your transmissions. Using technologies like SSL or NTLM requires not only additional space in messages, but also the security negotiations lead to more messages being exchanged.

    For those of you using SOAP, your application data gets wrapped with an envelope, body tags, and message headers. The size of the SOAP tax depends on the number of message features you want to take advantage of that require headers. This can range from about 20 bytes on the low end to as large as you want. Each additional message-level feature tends to require about 20-40 bytes. Then, you get wrapped in TCP and IP, which require 20 bytes each for their headers. On an Ethernet network, add another 14 bytes for headers and a four-byte checksum at the end. Tomorrow, we'll look at the addressing information that accounts for most of these header bytes.

    The cost for Ethernet is actually much higher than pictured. First, Ethernet requires a preamble and interframe gap to separate messages on the wire. This is going to reduce the maximum bandwidth you can get across a connection. Second, for electrical reasons, there is a whole host of strange encodings used to actually send bits around, with names like "Manchester", "4B5B", and "8B10B". The stated speed of Ethernet is with these encodings taken into account so you can ignore this part of the cost. A standard 100 megabits per second network card actually sends 125 megabits per second, but the encoding consumes a 25% overhead to improve signal quality.

    You can work out the actual efficiency your application has to compute your maximum effective bandwidth from the theoretical bandwidth of the connection. Let's say you're transmitting over a 10 megabits per second network with no packet loss and roughly 40 bytes of SOAP overhead per message. The maximum size of a standard Ethernet frame is 1500 bytes. You can see with this overhead that your efficiency is only going to be around 90%, so your maximum effective bandwidth is only 9 megabits per second. If you fail to stuff every transmission with the maximum amount of data, the efficiency will drop even further. The efficiency number when you're transmitting lots of very small messages is quite scary.

    Next time: How TCP/IP Works, Part 4: Demultiplexing Connections

  • Nicholas Allen's Indigo Blog

    How TCP/IP Works, Part 2: Protocols

    • 4 Comments

    Inside each layer of the TCP/IP networking stack, there are many protocols to transmit and route information at that level. Today's post has some of the more popular protocols that play a role in helping your WCF applications work, as well as a few protocols that were originally useful but have fallen out of style in the last few years.

    Link layer protocols

    Address Resolution Protocol (ARP): ARP is used to translate the Internet address of a computer to its address on an Ethernet network. Internet addresses are actually totally independent of the adapter address used on the local network. This was more useful in a time when TCP/IP was not as prevalent and you had machines using TCP/IP and machines using some other network stack on the same physical cable, but using ARP is still a basic part of doing networking. The specification for ARP is RFC 826.

    Reverse Address Resolution Protocol (RARP): RARP is the complement of ARP. It translates from the hardware interface address to the Internet address of the computer. RARP was originally used for getting the address of a computer when booting off the network, but there have been several successive technologies that solve this problem better. The specification for RARP is RFC 903.

    Network layer protocols

    Internet Protocol (IP): IP is the primary means of moving data around. The remaining protocols I'll talk about today are all transmitted using IP. When using IP, there's actually no guarantee that your data will be delivered in the right order or even at all. IP will just throw away data it doesn't understand and might send back an ICMP error message if you're lucky. Connection state and reliability are services that other protocols provide on top of IP. The current popular specification for IP (IPv4) is RFC 791. The IP specification is where Internet addresses come from. IPv6 is the successor to IPv4 although adoption has been slow.

    Internet Control Message Protocol (ICMP): ICMP is the protocol that machines use to give feedback about the IP network. ICMP is used for error-reporting and supplying point-to-point routing information. You can't have IP without ICMP helping. The specification for ICMP is RFC 777.

    Internet Group Management Protocol (IGMP): IGMP is a much less frequently used protocol that controls IP multicasting. Hosts use IGMP to control membership of multicast groups. There have been several versions of IGMP used over the years. The current specification for IGMP is RFC 3376.

    Transport layer protocols

    Transmission Control Protocol (TCP): TCP is built on top of IP and supplies both reliable delivery and a continuous connection session. TCP used to be considered an expensive protocol because it does take a lot of work to make these guarantees from the fundamentally unreliable IP. Today, most application level protocols are built on top of TCP because user expectations for reliability have increased. Using TCP allows you to avoid reimplementing these services yourself. TCP is one of the standard transports in WCF. The specification for TCP is RFC 793.

    User Datagram Protocol (UDP): UDP is the unreliable cousin of TCP. Using UDP is a lot more like using IP directly than TCP. UDP is not one of the standard transports in WCF although we have it available as a sample. The specification for UDP is RFC 768.

    Next time: How TCP/IP Works, Part 3: Framing Data

  • Nicholas Allen's Indigo Blog

    How TCP/IP Works, Part 1: Layers

    • 5 Comments

    When you use WCF, you take for granted a lot of the infrastructure that actually moves data from place to place.  TCP/IP is the backbone of the Internet, allowing computers with different hardware running different software to communicate with each other.  TCP/IP is more than just a single protocol though; it's actually a suite of protocols stacked on top of each other in layers.  There are various ways to describe the protocol layers of a networking stack.  The OSI model is a 7-layer stack that many people think is unnecessarily complicated.  I'm already going to unnecessarily complicate things by including WCF in the picture, so I'll use a slightly simpler traditional networking stack underneath.

    1. The media layer is what conveys the actual stream of bits through the network. For Ethernet, this is the cable that carries electrical signals from place to place. Try not to think too deeply about what the media layer is for a wireless network. It will make your head hurt.
    2. On top of the media layer is the link layer. The link layer consists of the network card and device drivers that handle the hardware details of interfacing with the media.
    3. On top of the link layer is the network layer. The network layer organizes messages into packets and routes those packets around different networks. This is the first layer that exhibits real organization for moving data. Below the network layer it's basically just people shouting at each other. The Internet Protocol (IP) part of TCP/IP is at this layer.
    4. On top of the network layer is the transport layer (not the WCF kind of transport). The transport layer handles connections, flow control, and end-to-end delivery of messages. A very popular transport is Transmission Control Protocol (TCP). In a normal networking stack, this is the end of the line and you just have your application from this point. In WCF, we keep going onward!
    5. The other transport layer (the WCF kind of transport) is what moves data into and out of a WCF application. You can have a TCP transport, which essentially means you've collapsed this layer with the previous layer. Or, you can have something like the HTTP transport, which traditionally was considered an application level protocol. Now, it's just a basic building block.
    6. You can add any number of additional layers by inserting channels into your channel stack. Sessions, security, message encoding, reliable messaging, and any other protocol you can think of, are layers that you can include in almost any order. I avoid using the OSI model because it really breaks down here for WCF.

    Eventually though, you should reach the end of the channel stack and ascend upward into your application. There's no telling what layer number your application is going to have.

    TCP and IP are only two of the protocols that make up the TCP/IP protocol suite. You'll have to wait until Monday to find out about the other protocols that keep the Internet working.

    Next time: How TCP/IP Works, Part 2: Protocols

  • Nicholas Allen's Indigo Blog

    Using the Base Classes to Build Things that Build Channels

    • 3 Comments

    After seeing the ChannelBase class yesterday for implementing a channel, today's post is about the base classes for implementing the things that build channels.  There is a common ChannelManagerBase class, and then the two ends of the channel split into the ChannelFactoryBase class and ChannelListenerBase class.  Both the factory and listener have a generic and non-generic part, although like I mentioned before, just think about the final generic version you get in the end.  This is exactly mirroring the IChannelManager, IChannelFactory, and IChannelListener interfaces.  Each of these base classes only has a few interesting points so I'm doing them all together rather than my normal approach of spreading them out across multiple days.

    public abstract class ChannelManagerBase : CommunicationObject, IChannelManager, ICommunicationObject, IDisposable, IDefaultCommunicationTimeouts
    {
    protected ChannelManagerBase();

    protected abstract TimeSpan DefaultReceiveTimeout { get; }
    protected abstract TimeSpan DefaultSendTimeout { get; }
    public abstract MessageVersion MessageVersion { get; }
    public abstract string Scheme { get; }

    protected void AbortChannels();
    protected IAsyncResult BeginCloseChannels(TimeSpan timeout, AsyncCallback callback, object state);
    protected void CloseChannels(TimeSpan timeout);
    protected void EndCloseChannels(IAsyncResult result);
    public virtual T GetProperty<T>() where T : class;
    }

    The ChannelManagerBase class represents the shared code between the client and server sides.  The bonus functionality you get here is the ability to shut down all of the channels that came from this factory or listener.  In yesterday's post, I mentioned that channels notify their manager when they're created or destroyed.  This is what the manager does with that information.  The timeout for the close operations is the total time for closing all of the channels.  If time expires or an error occurs, these methods stop immediately without trying to process any more channels.  This seems like a silly observation, but Abort runs from newest to oldest through the list while Close runs from oldest to newest.  I would not rely on that factoid for making any kind of decision.  Pretend like these operations will take the channels in a random order.

    public abstract class ChannelFactoryBase : ChannelManagerBase, IChannelFactory, IChannelManager, ICommunicationObject, IDisposable
    {
    protected ChannelFactoryBase();
    protected ChannelFactoryBase(IDefaultCommunicationTimeouts timeouts);

    protected override TimeSpan DefaultCloseTimeout {get; }
    protected override TimeSpan DefaultOpenTimeout {get; }
    protected override TimeSpan DefaultReceiveTimeout {get; }
    protected override TimeSpan DefaultSendTimeout {get; }

    public override T GetProperty<T>() where T : class;
    protected override void OnAbort();
    protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state);
    protected override void OnClose(TimeSpan timeout);
    protected override void OnEndClose(IAsyncResult result);
    }

    public abstract class ChannelFactoryBase<TChannel> : ChannelFactoryBase, IChannelFactory<TChannel>, IChannelFactory, IChannelManager, ICommunicationObject, IDisposable
    {
    public ChannelFactoryBase();
    public ChannelFactoryBase(IDefaultCommunicationTimeouts timeouts);

    public TChannel CreateChannel(EndpointAddress address);
    public TChannel CreateChannel(EndpointAddress address, Uri via);
    protected abstract TChannel OnCreateChannel(EndpointAddress address, Uri via);
    protected void ValidateCreateChannel();
    }

    Most of these methods are skeletons that either implement closing the factory or consolidate methods on the interface.  Two of the methods actually do something.  One is GetProperty, which will respond to people looking for channel factories by returning this instance.  The other is the ValidateCreateChannel method, which checks that you're in the right state.  Your factory must be in the Opened state to create channels.  You'll get a different error message depending on the wrongness of your state according to the standard rules.  Note that if you create a channel while at the same time disposing the factory, you run some risk of either getting an error message after channel creation or not getting any error message at all depending on the order of events.

    public abstract class ChannelListenerBase : ChannelManagerBase, IChannelListener, IChannelManager, ICommunicationObject, IDisposable
    {
    protected ChannelListenerBase();
    protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);

    public abstract Type ChannelType {get; }
    protected override TimeSpan DefaultCloseTimeout {get; }
    protected override TimeSpan DefaultOpenTimeout {get; }
    protected override TimeSpan DefaultReceiveTimeout {get; }
    protected override TimeSpan DefaultSendTimeout {get; }
    public virtual Identity Identity {get; }
    public virtual IChannelListener InnerChannelListener {get; set; }
    public abstract Uri Uri {get; }

    public IAsyncResult BeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state);
    public bool EndWaitForChannel(IAsyncResult result);
    public override T GetProperty<T>() where T : class;
    protected override void OnAbort();
    protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state);
    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
    protected abstract IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state);
    protected override void OnClose(TimeSpan timeout);
    protected override void OnEndClose(IAsyncResult result);
    protected override void OnEndOpen(IAsyncResult result);
    protected abstract bool OnEndWaitForChannel(IAsyncResult result);
    protected override void OnOpen(TimeSpan timeout);
    protected abstract bool OnWaitForChannel(TimeSpan timeout);
    public bool WaitForChannel(TimeSpan timeout);
    }

    public abstract class ChannelListenerBase<TChannel> : ChannelListenerBase, IChannelListener<TChannel>, IChannelListener, IChannelManager, ICommunicationObject, IDisposable where TChannel : class, System.ServiceModel.Channels.IChannel
    {
    protected ChannelListenerBase();
    protected ChannelListenerBase(bool sharedInnerListener);
    protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);
    protected ChannelListenerBase(bool sharedInnerListener, IDefaultCommunicationTimeouts timeouts);

    public override Type ChannelType { get; }

    public TChannel AcceptChannel();
    public TChannel AcceptChannel(TimeSpan timeout);
    public IAsyncResult BeginAcceptChannel(AsyncCallback callback, object state);
    public IAsyncResult BeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state);
    public TChannel EndAcceptChannel(IAsyncResult result);
    protected abstract TChannel OnAcceptChannel(TimeSpan timeout);
    protected abstract IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state);
    protected abstract TChannel OnEndAcceptChannel(IAsyncResult result);
    }

    Whew, there are even more skeleton methods on the listener.  Like the factory, your listener must be in the Opened state to open new channels.  The other statements about GetProperty, error messages, and opening channels while closing the listener all apply as well.  Unlike the factory, the listener is actively doing something even when you're not calling methods.  The next time you try to get a channel from the listener, you'll get any exceptions acquired in the meantime.

    A point that I want to make here is that you absolutely should not use InnerChannelListener.  It should stand out as something really strange because you have a setter for that property, which can't possibly result in good things if you try to use it.  The correct way to find one of your inner channels is through the GetProperty method, and it's never correct to try to alter the channel stack like that after creation.

    Next time: How TCP/IP Works, Part 1: Layers

  • Nicholas Allen's Indigo Blog

    Using the Base Classes to Build Channels

    • 5 Comments

    After a bit of a diversion, let's spend some time again looking at the parts necessary to build a custom channel.  I hope to use the second half of this month going over a custom transport I've put together as a quick sample.  We've seen most of the important interfaces in the channel model by this point.  Today and tomorrow I'll go over base classes that make implementing those interfaces easier.  Today's post is about the ChannelBase class, which is the abstract base class for IChannel.

    public abstract class ChannelBase : CommunicationObject, IChannel, ICommunicationObject, IDisposable, IDefaultCommunicationTimeouts
    {
    protected ChannelBase(ChannelManagerBase channelManager);

    protected override TimeSpan DefaultCloseTimeout { get; }
    protected override TimeSpan DefaultOpenTimeout { get; }
    protected TimeSpan DefaultReceiveTimeout { get; }
    protected TimeSpan DefaultSendTimeout { get; }
    public IChannelManager Manager { get; }

    public virtual T GetProperty<T>() where T : class;
    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
    protected override void OnClosed();
    protected override void OnEndOpen(IAsyncResult result);
    protected override void OnOpen(TimeSpan timeout);
    }

    ChannelBase can only be used in conjunction with the base classes for channel factories and listeners.  Right now, it's an all-or-nothing package.  There are a couple of things you get if you accept the package.  One is that the timeouts on the manager get used as the default timeouts for the channel.  Another is that ChannelBase takes care of managing the manager for you.  In addition to exposing a reference to the manager, the base class also notifies the manager when channels are created and destroyed.  The reference to the manager is only good until the channel is destroyed, so be mindful of the state your channel is in.

    Even if you do use the base class, you'll still need to provide some sensible behavior for opening and closing the channel.  The default implementations of these methods are not going to do anything useful for you.  You'll also want to get into the habit of overriding GetProperty whenever you implement a channel.  GetProperty is an excellent way to communicate information along the channel stack without having to know what channels are below you, or what channels above you are interested in performing queries.  Define interfaces for people to query on for any interesting information you have.  Whenever you don't recognize the type argument for GetProperty, delegate down to the next channel in the stack or return null if you're creating a new transport channel at the bottom of the stack.

    Next time: Using the Base Classes to Build Things that Build Channels

  • Nicholas Allen's Indigo Blog

    Five Pitfalls of the Channel Model, Part 2

    • 1 Comments
    In yesterday's post, we looked at the first three subtle rules, aka pitfalls, of the channel model.  Today's post covers pitfalls four and five.  A pitfall is something that has significant negative consequences and no clear indication in the code that you're doing something wrong.  I wish we had fewer pitfalls because then anyone could pick up and use the channel model with confidence.  However, a lot of these pitfalls are the result of providing you with power and flexibility in other areas.  We try very hard to hide the pitfalls from you when you're using contracts and the Service Framework.  Using the channel model directly is a tradeoff, like using assembly language.  There are times when it's the right solution to the problem, but you're giving up safety in exchange for more freedom.  At least with managed code though, you don't have to give up that much safety to get things done.

    Pitfall #4: Actual maximum may be smaller than it appears

    We let you specify a lot of different types of limits to restrict resource consumption, but we can never guarantee that you can actually hit those limits.  Setting the limits low protects you from Denial of Service (DOS) attacks at the cost of sometimes turning away legitimate traffic.  We've been very conservative with the default settings to keep you safe out of the box.  There are real world scenarios where you have to relax the limits to get your work done.

    Some people are a little too relaxed with the limits.  You have to be realistic about the computational resources you can afford to spend.  Even if you set a very long timeout for an operation, we may give up before that if things are failing.  Even if you permit thousands of connections, you may not have enough memory, CPU, or bandwidth to talk with all of those clients before they die off.  Once your server hits full load, there are situations where it can begin to degrade very rapidly as you try to add more load.  And, as I pointed out in an earlier post, even if you permit ridiculously large message sizes, there are a lot of factors preventing you from successfully sending messages that big.  The moral of this story is that while the default settings leave a lot of headroom in production environments, there are limits to how far you can push your limits.

    Pitfall #5: Complain if you don't want to do something

    The last pitfall that I want to point out in this edition is that you always have to remember that we're all in this together.  Your service limits what clients can do by providing a restricted set of verbs to act on.  A service can validate client requests before taking action.  The code running behind your service is inside this trust boundary.  The channel model has rules you must follow, such as the object state machine.  There are ways to indicate that a problem has occurred and then there's also ways to simply misbehave.  At the channel model level, we can't do much to protect a misbehaving service from itself.  If you're lucky, your service gets caught breaking the rules without much penalty.  Sometimes, the service just dies and gets restarted.  Don't rely on this.  Make sure you follow the rules of the game, and we'll do our best to make sure that the rules actually work like we claim they do.

    Next time: Using the Base Classes to Build Channels
  • Nicholas Allen's Indigo Blog

    Five Pitfalls of the Channel Model, Part 1

    • 1 Comments
    Although the WCF channel model has a relatively simple set of APIs, it has a very subtle set of rules for using those APIs.  I've picked out a small collection of rules to talk about for the next two posts.  You might have seen some of these before if you're a regular reader, but everything here is worth saying at least twice.  I may run follow-ups in the future to collect more rules as we come across them.  There is no shortage of wrong ways to use the channel model.

    Pitfall #1: There's always a limit, even if no one tells you what it is

    This is the best pitfall to employ when you need a mysteriously bad experience at random intervals.  Every operation has some threshold for timeliness, above which you'll start incurring user unhappiness.  Sometimes you know what that threshold is, although frequently you'll just have to guess.  The timeout model in WCF is that the threshold you set for an operation bubbles down to its suboperations so that they know how much time is remaining.  For various reasons, not every method allows you to specify a timeout.  That doesn't mean you have an unlimited amount of time.  Since you don't know how much time you can afford to spend, that means you must never take an appreciable amount of time to complete an operation without a timeout.

    Pitfall #2: Clean up your toys when you're done

    Most of the channel model has an explicit cleanup arrangement through the Close method.  An IDisposable implementation is often provided as a synonym for Close so that you can take advantage of the using syntax in C# to define a containment block.  It is a bad idea to not close an object after you're done.  At the level of the channel model, we don't promise to clean up after you.  Failing to call Close can lead to leaks of both memory and network resources, such as ports, namespaces, and available connections, for significant periods of time.  Don't let this happen to you.

    Pitfall #3: If you break a message, you've bought it

    A message is one of those objects that have an explicit cleanup requirement.  The difference between messages and things like channels is that messages are far more likely to be passed around.  There is one, and only one, owner for each message.  When a message is passed through a function, there should be some indication of whether ownership of the message has also been handed off.  Typically, giving away a message is equivalent to giving away its ownership.  The person that received the message will then either pass the message on to someone else or break the message open to read its contents.  Once you break open a message, you're pretty much stuck cleaning it up because you can't pass it on.

    Next time: Five Pitfalls of the Channel Model, Part 2
Page 1 of 1 (20 items)