pi-hole/api

Shared Memory Versioning

AzureMarker opened this issue · 6 comments

Shared memory requires that both sides use identical structures, otherwise the shared memory could get corrupt or be read incorrectly. To prevent these issues, there should be a field in shared memory which records the version it is at, like a database version. Then everyone who connects to the shared memory can validate that they are using the correct version of structures.

This will require changes in both FTL and API, but should be trivial. FTL should expose a new shared memory file which will contain configuration details. The only configuration detail at first will be the version, but it should have room to grow if necessary. To allow this growth, the version must be the first item in the shared config structure, and it must have a fixed size (eg. can't change from u8 to u32 when a new version is released).

DL6ER commented

Idea 1

Most fields (queries, clients, domains, forwarded, overTime) have the magic byte at the beginning. FTL does not care about the content as long as it is always the same.

It is currently hard-coded to 0x57 but this was just a random choice. The magic byte is guaranteed to be the first byte of the structs.

An alternative idea to your is

  • Change the magic byte when any of the structs change inside FTL
  • Have the API first connect to only the very first byte of one of the above structs
  • Read this byte and decide from this which format to use
  • Disconnect from this single byte
  • Connect to the structs using the format we found out

We could guarantee that there is a guaranteed first entry (empty) in the queries or clients struct so that even if there is no data, there would at least be the magic byte set.

Idea 2

Following your ideas, it would be interesting if it would be possible to tell the API how large a struct is right now. We would then tell the API through the new shmem object that the client struct is now, e.g., 36 bytes and the API would discard the last 6 bytes are it only knows how to process the former 30 bytes (these are arbitrary numbers). Although this would not allow us to remove fields, it would allow for the FTL structs to grow per entry (i.e., add more variables) without side effects. Adding more fields is much more likely than removing fields.

Do you have any objections against the original idea? Those other ideas seem like workarounds.

DL6ER commented

No, my idea 2 is just the logical extension of your original idea. Otherwise, changes in FTL could not be made without having made the changes already before in the API (which has to know the new format). In my idea, the API could still read the known fields in case the FTL implementation is newer than the API. This would mostly be a benefit for those users (resp. mostly me :-) ) who want to experiment around with FTL without already knowing beforehand how the new elements of the structs will finally look like.

The SHM API client will not allow a connection to shared memory if the shared memory size is not a multiple of the size of the struct it is expected to contain (for array shared memory). Therefore I don't think the dynamic size idea will work.

DL6ER commented

Following scenario: The latest version the API knows has a size of 30 bytes. The version FTL uses now is 36 bytes long. The API could read this information from the new shmem object (specific to this purpose) and know that at the end of each entry, it has to insert something like char neverused[6];

If a version of FTL is released with SHM changes, the API should also be updated. Otherwise, how would the API know if it can use the new version safely, even with the assumption that it ignores the extra data? Some preexisting struct data could have been changed, such as switching out an int for a char[4], and the API would have no way to know this.