When attempting to assign a link-local IP to a jail one of the following things happens:
* it either does not work at all and the jail refuses to start for some reason claiming the jail does not exist
* Jail starts, but ifconfig in the jail shows there is no IP on the interfaces. Trying to bind to it with nc(1) to test fails.
* Adding the index to the IPv6 address causes the jail to reject the address as invalid and not start:
jail: v6test: ip6.addr: not an IPv6 address: fe80::216:3eff:fe72:eadd%xn0
I'm not surprised such a thing wouldn't work. The IPv6 jail-related kernel code knows only IP addresses, and doesn't touch scope. These scoped link-local addresses are a little beyond me I must admit*, but I can see at least one essential jail concept breaking down in that paradigm: any IP communication of the jail with itself is forcibly rerouted to localhost - which in the IPv6 case kind of assumes the global scope where ::1 lives. I'm sure there are many other ways that are a good less simple that the whole scope concept just doesn't make it into the part of the kernel that knows/cares about jails.
I imagine it would take a lot of support deep in the IPv6 code to make jails work on link-local addresses, and frankly I just don't see than happening. And I wonder if it would be a good idea anyway - shunting a link-local address off to a jail sounds it like could break things that depend on such addresses existing in a regular non-jailed way.
(* It's also beyond me why a committee of people who know networking much better than I do ever though such an abomination as this out-of-band special "scope" address should even exist, but I digress).
Just to add to this:
It _is_ possible to assign a link-local IPv6 address to a jail. But you have to use the interface's scope identifier to do so, i.e. instead of writing something like
ip6.addr = "lo1|fe80::dead:beef/64";
you need to use
ip6.addr = "lo1|fe80:X::dead:beef/64";
where X is the scope-id of your interface. This number does depend - as far as I can tell - on the order in which the kernel creates the interfaces, so you either have to take a wild guess or take a look at the ouput of ifconfig, which prints the scopeid of any IPv6-address assigned to it:
inet6 fe80::dead:beef%lo1 prefixlen 64 scopeid 0x5
It would be really nice if this hack wasn't necessary, i.e. if the system would do this either by itself, or if there was a reliable way to get the scope-id of an interface without having to guess. As it stands right now, using scope-ids to assign link-local addresses works, but might break on reboot if a new interface is added.
It wouldn't be too hard to automate the transition between fe80:ifnum::dead:beef and fe80::dead:beef%ifname. Jail uses inet_pton and inet_ntop which aren't aware of this, but it looks like it could use getaddrinfo and getameinfo which use the scope ID, or if_nametoindex and if_indextoname which give me the number directly.
I would have to move this scope ID to/from the second and third octets in the inet6 address, which is rather non-standard but I assume a long-standing FreeBSD way of doing things.
That would be a great and at least avoid the issue of having to specify the (slightly volatile) interface number explicitly in jail.conf.
However, since the interface that an address is meant for is always specified in jail.conf anyway (either via the interface parameter or the if|address format in ip4.addr and ip6.addr) it would be even nicer if we could generate the interface index directly from the interface information in jail.conf, i.e.
ip6.addr = 'lo1|fe80::dead:beef'
interface = 'lo1'
ip6.addr = 'fe80::dead:beef'
should suffice for the sake of convenience. I guess one could map these forms to fe80::dead:beef%if and then map this to the index form like you suggested.