1

Possible Duplicate:
WCF: Data contract being converted to message contract

I have a generic WCF proxy, for those unfamiliar with that it is a class with a method with the below OperationContract.

[OperationContract(Action = "*", ReplyAction = "*")]
void Proxy(Message requestMessage);

One of the requirements that has recently emerged, is I need to replace all properties in the Message that are of type IPAddress, to the IPAddress value of those that supplied the request message.

I.e. something like,

public void Proxy(Message requestMessage)
{
    try
    {
        // Client IP address, not currently used!
        IPAddress clientIP = IPAddress.Parse((requestMessage
            .Properties[RemoteEndpointMessageProperty.Name] 
            as RemoteEndpointMessageProperty).Address);
        var factory = new ChannelFactory<IDmzProxy>("client");
        IDmzProxy dmzProxy = factory.CreateChannel();
        dmzProxy.Proxy(requestMessage);
        factory.Close();
    }

    // No leakage of data!  Any exceptions still return void!
    catch (Exception exception)
    {
        Log.Fatal(
            "Exception occurred on proxying the request",
            exception);
        return;
    }
}

The question now is how to set the element in requestMessage of type IPAddress to the clientIP that I have retrieved?

Edit 1

Things I have tried and failed,

requestMessage.GetBodyAttribute("ipAddress", "http://schemas.datacontract.org/2004/07/System.Net")

Edit 2

One method seems to be replacing the XML of the MessageBody. It seems like overkill to me (what's the point of WCF then?).

It also isn't particularly easy as the MessageBody needs to match an element by its attribute names rather than the element name.

  <ipAddress xmlns:a="http://schemas.datacontract.org/2004/07/System.Net" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <a:m_Address>3772007081</a:m_Address>
    <a:m_Family>InterNetwork</a:m_Family>
    <a:m_HashCode>0</a:m_HashCode>
    <a:m_Numbers xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
      <b:unsignedShort>0</b:unsignedShort>
    </a:m_Numbers>
    <a:m_ScopeId>0</a:m_ScopeId>
  </ipAddress>

Edit 3

Certainly not a duplicate, here is something that's roughly working, needs some work still to replace the node I am after,

    public void Proxy(Message requestMessage)
    {
        try
        {
            Log.Info("Received request");
            requestMessage = SourceNATMessage(requestMessage);

            // Check if there is an accepted action we have to catch
            // If the Accepted Action is set, and the action is not the same
            // then just return);)
            if (!String.IsNullOrEmpty(AcceptedAction) &&
                !requestMessage.Headers.Action.EndsWith(AcceptedAction))
            {
                Log.WarnFormat(
                    "Invalid request received with the following action {0}\n" +
                    "expected action ending with {1}",
                    requestMessage.Headers.Action,
                    AcceptedAction);
                return;
            }

            // otherwise, let's proxy the request
            Log.Debug("Proceeding with forwarding the request");
            var factory = new ChannelFactory<IDmzProxy>("client");
            IDmzProxy dmzProxy = factory.CreateChannel();
            dmzProxy.Proxy(requestMessage);
            factory.Close();
        }

        // No leakage of data!  Any exceptions still return void!
        catch (Exception exception)
        {
            Log.Fatal(
                "Exception occurred on proxying the request",
                exception);
            return;
        }
    }

    private static Message SourceNATMessage(Message message)
    {
        IPAddress clientIp =
                IPAddress.Parse(
                    ((RemoteEndpointMessageProperty)
                     message.Properties[
                         RemoteEndpointMessageProperty.Name]).Address);

        Log.DebugFormat("Retrieved client IP address {0}", clientIp);

        var stringBuilder = new StringBuilder();
        XDocument document;

        using (XmlWriter writer = XmlWriter.Create(stringBuilder))
        {
            message.WriteBody(writer);
            writer.Flush();
            document = XDocument.Parse(stringBuilder.ToString());
        }

        var deserializer = new DataContractSerializer(typeof(IPAddress));

        foreach (XElement element in
            from element in document.DescendantNodes().OfType<XElement>()
            let aNameSpace = element.GetNamespaceOfPrefix("a")
            let iNameSpace = element.GetNamespaceOfPrefix("i")
            where
                aNameSpace != null &&
                aNameSpace.NamespaceName.Equals(SystemNetNameSpace) &&
                iNameSpace != null &&
                iNameSpace.NamespaceName.Equals(XmlSchemaNameSpace) &&
                deserializer.ReadObject(element.CreateReader(), false) is IPAddress
            select element)
        {
            element.ReplaceWith(new XElement(element.Name, deserializer.WriteObject());
        }

        return Message.CreateMessage(message.Version,
                                     message.Headers.Action,
                                     document.CreateReader());
    }

Edit 4

Working code for those interested, can't post as an answer as the question is closed.

private static Message SourceNatMessage(Message message)
{
    IPAddress clientIp =
            IPAddress.Parse(
                ((RemoteEndpointMessageProperty)
                    message.Properties[
                        RemoteEndpointMessageProperty.Name]).Address);

    Log.DebugFormat("Retrieved client IP address {0}", clientIp);

    var stringBuilder = new StringBuilder();
    XDocument document;

    using (XmlWriter writer = XmlWriter.Create(stringBuilder))
    using (XmlDictionaryWriter dictionaryWriter =
        XmlDictionaryWriter.CreateDictionaryWriter(writer))
    {
        message.WriteBodyContents(dictionaryWriter);
        dictionaryWriter.Flush();
        document = XDocument.Parse(stringBuilder.ToString());
    }

    var deserializer = new DataContractSerializer(typeof(IPAddress));
    var clientIpXml = new StringBuilder();
    using (var xmlWriter = XmlWriter.Create(clientIpXml))
    {
        deserializer.WriteObject(xmlWriter, clientIp);
        xmlWriter.Flush();
    }

    var clientElement = XElement.Parse(clientIpXml.ToString());

    foreach (XElement element in
        from element in document.DescendantNodes().OfType<XElement>()
        let aNameSpace = element.GetNamespaceOfPrefix("a")
        let iNameSpace = element.GetNamespaceOfPrefix("i")
        where
            aNameSpace != null &&
            aNameSpace.NamespaceName.Equals(SystemNetNameSpace) &&
            iNameSpace != null &&
            iNameSpace.NamespaceName.Equals(XmlSchemaNameSpace) &&
            element.NodeType == XmlNodeType.Element
        select element)
    {
        try
        {
            deserializer.ReadObject(element.CreateReader(), false);
            element.ReplaceNodes(clientElement);
        }
        catch (SerializationException) { }
    }

    Message sourceNatMessage = Message.CreateMessage(message.Version,
                                    null,
                                    document.CreateReader());
    sourceNatMessage.Headers.CopyHeadersFrom(message);
    sourceNatMessage.Properties.CopyProperties(message.Properties);

    return sourceNatMessage;
}
Community
  • 1
  • 1
M Afifi
  • 4,645
  • 2
  • 28
  • 48
  • Use `requestMessage.properties.OfType()` instead of your LINQ query. – abatishchev Jun 28 '12 at 10:41
  • @abatishchev Properties on requestMessage is not actually the message properties btw. The above as I said is purely made up... – M Afifi Jun 28 '12 at 10:47
  • Replacing retrieved values from client, in a service method, where that i know dispatcher job is almost finished! why?, do you need message inspector? – Beygi Jun 28 '12 at 10:59
  • @Beygi its some software that also gathers IP addresses, for a certain class of machines we can't totally trust their message and need to replace parts of it with values that we know. – M Afifi Jun 28 '12 at 11:06
  • Why you are not using (prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty).Address instead of unknown DataContract IPAddress property? – Beygi Jun 28 '12 at 11:27
  • My comment was not an answer but a way to rewrite your large LINQ query in foreach source. I still don't see a reason why don't use OfType. Please elaborate. – abatishchev Jun 28 '12 at 11:39
  • 1
    @abatishchev OfType would work just fine thansk. – M Afifi Jun 28 '12 at 11:45

2 Answers2

0

Just annotate your SOAP message with a custom addressing header.

Mihai H
  • 3,291
  • 4
  • 25
  • 34
  • Can this be completed server side exclusively? Without the DataContract being updated, or the client generating the message being updated? The message has all the information we need after all. – M Afifi Jun 28 '12 at 11:48
  • Can be done on the service side suing IDispatchMessageInspector. – Mihai H Jun 28 '12 at 11:50
  • Not relevant, I already have the Message and I can easily overrite it as it is a BufferedMessage. If need I can create my own as well. – M Afifi Jun 28 '12 at 12:04
  • So if you can do it then what else do you need? – Mihai H Jun 28 '12 at 12:28
  • How do I set the property ipAddress in requestMessage to the client IP address? Is Xml editing the only option? – M Afifi Jun 28 '12 at 12:34
  • If the above answer does not satisfy your request then let me ask you this: how do you know the client IP address on your service level in order to do the appropriate replacement? If you care only about replacing things on the incoming message, reformulating your question is the most appropriate thing I assume. – Mihai H Jun 28 '12 at 13:18
0

You don't have to do anything like that in normal circumstances. Your message already contains the information you are looking for.

OperationContext context = OperationContext.Current;
MessageProperties messageProperties = context.IncomingMessageProperties;
RemoteEndpointMessageProperty endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

Where the client IP address is found on endpointProperty.Address property and the port is found on endpointProperty.Port property.

Mihai H
  • 3,291
  • 4
  • 25
  • 34
  • i mentioned that, but he's really serious to modify the retrieved message... – Beygi Jun 28 '12 at 13:14
  • But that's one of the things proxies do. I have a "security" WCF service that sits in the DMZ. The client IP address that has been injected by the client could be a number of things, 1. Incorrect (its a NAT'ed IP address that's nonsense), 2. Spoofed, its someone sending in bad data. This DMZ service knows the IP address and replaces it. On the inside of our network perimeter, I can't use the MessageProperty, between load balancers, proxies, and other ifnrastructure, that value is also nonsense internally, the difference is I trust waht the client sends within our network. – M Afifi Jun 28 '12 at 14:35
  • So in that case why the DMZ service which knows the IP address and should replace it can't add on the message an appropriate addressing header based on the DMZ security policy? Maybe I am missing something... – Mihai H Jun 28 '12 at 14:43