HS can repick an expired intro points or one that we've already picked

Let's assume here we are about to cycle our intro points in rend_services_introduce(), we do a dance and at the end we choose 3 new ones. To do that, we use router_choose_random_node() that picks a random node using the torrc ExcludeNodes list but also an exclude_nodes list that we've populated before which contains expired intro points.

After that, we happily launch an intro circuit by calling rend_service_launch_establish_intro() and then circuit_launch_by_extend_info(). In there, a circuit can be cannibalized and if so, the exit node is NOT extended to our intro points that we picked earlier because of our purpose:

  case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
    /* it's ready right now */

The IP is then changed to the one we just cannibalized once we have our circuit. This is not desirable for two reasons:

  1. We ignore the exclude nodes list thus we can end up picking an expired IP.
  2. We can use to the same IP multiple times for a single descriptor.

All of the above, I was able to reproduce in a chutney network that is having a set of IPs that expired in the previous period and a set of IPs that are all the same.

It's not that terrible in the real network because the probability of that happening is roughly 1/<relay count> which is < 1% currently, still worth fixing imo.

Possible solution for this: i) Use a 4th hop for the IP circuit meaning we allow cannibalization but we extend the circuit to the intro point.

ii) Disable cannibalization for intro point thus always creating a fresh circuit ending up at the right place.

My pick here would be i) because I think a 4th hop is cheaper than a creating a new circuit.

Comments welcome!