Apache mod_substitute on HTTP requests to reverse-proxied host

apache-2.2mod-rewritereverse-proxy

I have the following setup:

  • Printer #1 on LAN
  • Printer #2 on LAN
  • Internet facing Debian Apache 2.2 web server at server-external-ip that I want to use as an IPP gateway to the two printers

The two printers are reachable (from LAN and from the Apache server) at the following IPP URLs:

  • http://printer-1-local-ip/printer
  • http://printer-2-local-ip/printer

(The printers are not physically attached to the web server.)

I want them to be reachable from the Internet at the following URLs:

  1. http://server-external-ip/prn1
  2. http://server-external-ip/prn2

IPP works exclusively through HTTP requests to the printer address (i.e., the whole printing process happens through POST requests at the http://printer-X-local-ip/printer URLs), so I only need to redirect (i.e., reverse proxy with Apache) URLs 1 and 2 above.

Apache is serving other content, so I cannot replace it with a custom program (e.g., netcat or netsed). Also, I cannot run a custom program on a different port since the printer clients will only be able to reach the server at port 80.

Then I tried the following Apache configuration:

RewriteRule ^/prn1$ http://printer-1-local-ip:80/printer [P]
ProxyPassReverse /prn1 http://printer-1-local-ip

Connecting a Windows client to the http://server-external-ip/prn1 URL, the reverse proxy works. But the IPP protocol also sends to the printer (inside the POST-ed data) the full device URL.

This means that the printer receives an explicit IPP request for a http://server-external-ip/prn1 printer, and not for its correct address (http://printer-1-local-ip/printer). So it refuses the connection.

I added this entry into the HOST file at the Windows client:

server-external-ip    printer-dns-name

But it still doesn't work since the printer receives an IPP request for http://printer-dns-name/prn1 which still has the wrong service name (i.e., prn1 instead of printer).

I cannot change the reverse proxy url from http://server-external-ip/prn1 to http://server‑external‑ip/printer since I have to provide access to both printers (and I can't change the printer service name in the printer configuration).

What I want to do is to mangle the IPP data HTTP POST-ed to the printer to substitute http://server‑external‑ip/prnX with http://printer-X-local-ip/printer (there are no checksums in the IPP protocol and from the packets I captured this should work).

The problem is that all the Apache modules I can google for won't help you in mangling HTTP request bodies sent to the reverse-proxied printer. mod_rewrite works only on headers, mod_substitute works on response bodies, mod_headers works on request and response headers, mod_replace works on everything but request bodies, etc.

With mod_substitute I tried with the following:

<Location /> 
    AddOutputFilterByType SUBSTITUTE application/ipp
    Substitute "s|server-external-ip/prn1|printer-1-local-ip/printer|"
</Location>

But, as expected, it works perfectly on response bodies but not on proxied requests (I checked proxying to another server). Also note that IPP requests are of the application/ipp MIME type, so the filtering won't (significantly) impact normal traffic.

Any idea on how to solve this mess? I feel like there should be an easy solution and I'm not looking at things the right way. That's why I'm asking this always-awesome community (I have no posts here yet, but I'm a longtime fan).

I'd like to stay on this "redirection approach", so workarounds will be useful only if no direct solution exists. And yes, I could modify an Apache module for the purpose, but I don't really feel like it… 🙂

In the meantime I'll try some netsed magic… 🙂

Best Answer

Why not do the following: Define two DNS names for your external printer, eg.

printer-1-external.mydomain.com CNAME external-server.mydomain.com
printer-2-external.mydomain.com CNAME external-server.mydomain.com

You could even use the same names as you use internally. Have the internal DNS resolve printer names directly to the printer, and the external DNS to your external IP...

Add two name based virtual hosts on your server:

<VirtualHost *:80>
    ServerName printer-1-external.mydomain.com
    ProxyPass / http://printer-1-local-ip/
</VirtualHost>
<VirtualHost *:80>
    ServerName printer-2-external.mydomain.com
    ProxyPass / http://printer-2-local-ip/
</VirtualHost>

That way your IPP request for http://printer-2-external.mydomain.com/printer will have the correct service name in its post parameters.