Varnish: Casting a string to an IP address

The Varnish VCL match operator (~) will currently only match an ACL (access control list) to an IP address data type. Since there's no way to cast a string to an IP in VCL (although it is planned), you can't match a forwarded-for header against an ACL without using inline C. You can only match client.ip, which can be a problem depending on the cluster configuration.

The following hack works by using VCL's inline C functionality to do the cast. Varnish compiles the ACL into a C function that you can call directly: match_acl_named_paybarrier_bypass(). If there is a match, it sets a header for the backend.

It's useful to play around with the command varnishd -C which prints out the compiled VCL code.

C{
  #include <netinet/in.h>
  #include <string.h>
}C
// We use inline C to do the equivalent of this string to IP cast:
//   if (IP(req.http.X-Forwarded-For) ~ paybarrier_bypass)
// This syntax should be supported in Varnish 3.0.
C{
  struct sockaddr xff_ip;

  // Make a complete copy of client.ip.
  xff_ip = *(VRT_r_client_ip(sp));

  // Need to cast a pointer to sockaddr_in which is the struct required by
  // inet_pton.
  struct sockaddr_in *xff_ip_in = (struct sockaddr_in *) &xff_ip;

  // X-Real-Forwarded-For may contain two IP addresses, so grab the first.
  char *rxff_ip_str = strtok(VRT_GetHdr(sp, HDR_REQ, "\025X-Real-Forwarded-For:"), ",");
  if (rxff_ip_str == NULL)
    rxff_ip_str = VRT_GetHdr(sp, HDR_REQ, "\025X-Real-Forwarded-For:");

  // Copy the ip address into the struct's sin_addr.
  inet_pton(AF_INET, rxff_ip_str, &((*xff_ip_in).sin_addr));

  // Run the match.
  if (match_acl_named_paybarrier_bypass(sp, &(xff_ip))) {
    VRT_SetHdr(sp, HDR_REQ, "\025X-Our-Header:", "X", vrt_magic_string_end);
    VRT_done(sp, VCL_RET_PASS);
  }
}C