Parental controls web filtering backend

In my previous post I gave an overview of the backend for the screen time limits feature of parental controls in GNOME. In this post, I’ll try and do the same for the web filtering feature.

We haven’t said much about web filtering so far, because the user interface for it isn’t finished yet. The backend is, though, and it will get plumbed up eventually. Currently we don’t have a GNOME release targeted for it yet.

(削除) When is web filterings? (削除ここまで) What is web filtering?

(Apologies to Radio 4 Friday Night Comedy.)

Firstly, what is the aim of web filtering? As with screen time limits, we’ve written a design document which (hopefully) covers everything. But the summary is that it should allow parents to filter out age-inappropriate content on the web when it’s accessed by child accounts, while not breaking the web (for example, by breaking TLS for websites) and not requiring us (as a project) to become curators of filter lists. It needs to work for all apps on the system (lots of apps other than web browsers can show web content), and needs to be able to filter things differently for different users (two different children of different ages might use the same computer, as well as the parents themselves).

After looking at various different possible ways of implementing it, the best solution seemed to be to write an NSS module to respond to name resolution (i.e. DNS) requests and potentially block them according to a per-user filter list.

A brief introduction to NSS

NSS (Name Service Switch) is a standardised name lookup API in libc. It’s used for hostname resolution, but also for user accounts and various other things. Names are resolved by various modules which are dlopen()ed into your process by libc and queried in the order given in /etc/nsswitch.conf. So for hostname resolution, a typical configuration in nsswitch.conf would cause libc to query the module which looks at /etc/hosts first, then the module which checks your machine’s hostname, then the mDNS module, then systemd-resolved.

So, we can insert our NSS module into /etc/nsswitch.conf, have it run somewhere before systemd-resolved (which in this example does the actual DNS resolution), and have it return a sinkhole address for blocked domains. Because /etc/nsswitch.conf is read by libc within your process, this means that the configuration needs to be modified for containers (flatpak) as well as on the host system.

Because the filter module is loaded into the name lookup layer, this means that content filtering (as opposed to domain name filtering) is not possible with this approach. That’s fine — content filtering is hard, I’m not sure it gives better results overall than domain name filtering, and means we can’t rely on existing domain name filter lists which are well maintained and regularly updated. We’re not planning on adding content filtering.

It also means that DNS-over-HTTPS/-TLS can be supported, as long as the app doesn’t implement it natively (i.e. by talking HTTPS over a socket itself). Some browsers do that, so the module needs to set a canary to tell them to disable it. DNS-over-HTTPS/-TLS can still be used if it’s implemented by one of the NSS modules, like systemd-resolved.

Nothing here stops apps from deliberately bypassing the filtering if they want, perhaps by talking DNS over UDP directly, or by calling secret internal glibc functions to override nsswitch.conf. In the future, we’d have to implement per-app network sandboxing to prevent bypasses. But for the moment, trusting the apps to cooperate with parental controls is fine.

Filter update daemon

So we have a way of blocking things; but how does it know what to block? There are a lot of filter lists out there on the internet, targeted at existing web filtering software. Basically, a filter list is a list of domain names to block. Some filter lists allow wildcards and regexps, others just allow plain strings. For simplicity, we’ve gone with plain strings.

We allow the parent to choose zero or more filter lists to build a web filtering policy for a child. Typically, these filter lists will correspond to categories of content, so the parent could choose a filter list for advertising, and another for violent content, for example. The web filtering policy is basically the set of these filter lists, plus some options like "do you want to enforce safe search". This policy is, like all other parental controls policies, stored against the child user in accounts-service.

Combine these filter lists, and you have the filter list to give to NSS in the child’s session, right? Not quite — because the internet unfortunately keeps changing, filter lists need to be updated regularly. So actually what we need is a system daemon which can regularly check the filter lists for updates, combine them, and make them available as a compiled file to the child’s NSS module — for each user on the system.

This daemon is malcontent-webd. It has a D-Bus interface to allow the parent to trigger compiling the filter for a child when changing the parental controls policy for that child in the UI, and to get detailed feedback on any errors. Since the filter lists come from third parties on the internet, there are various ways they could have an error.

It also has a timer unit trigger, malcontent-webd-update, which is what triggers it to periodically check the filter lists for all users for updates.

High-level diagram of the web filtering system, showing the major daemons and processes, files, and IPC calls. If it’s not clear, the awful squiggled line in the bottom left is meant to be a cloud. Maybe this representation is apt.

And that’s it! Hopefully it’ll be available in a GNOME release once we’ve implemented the user interface for it and done some more end-to-end testing, but the screen time limits work is taking priority over it.

Parental controls screen time limits backend

Ignacy blogged recently about all the parts of the user interface for screen time limits in parental controls in GNOME. He’s been doing great work pulling that all together, while I have been working on the backend side of things. We’re aiming for this screen time limits feature to appear in GNOME 50.

High level design

There’s a design document which is the canonical reference for the design of the backend, but to summarise it at a high level: there’s a stateless daemon, malcontent-timerd, which receives logs of the child user’s time usage of the computer from gnome-shell in the child’s session. For example, when the child stops using the computer, gnome-shell will send the start and end times of the most recent period of usage. The daemon deduplicates/merges and stores them. The parent has set a screen time policy for the child, which says how much time they’re allowed on the computer per day (for example, 4h at most; or only allowed to use the computer between 15:00 and 17:00). The policy is stored against the child user in accounts-service.

malcontent-timerd applies this policy to the child’s usage information to calculate an ‘estimated end time’ for the child’s current session, assuming that they continue to use the computer without taking a break. If they stop or take a break, their usage – and hence the estimated end time – is updated.

The child’s gnome-shell is notified of changes to the estimated end time and, once it’s reached, locks the child’s session (with appropriate advance warning).

Meanwhile, the parent can query the child’s computer usage via a separate API to malcontent-timerd. This returns the child’s total screen time usage per day, which allows the usage chart to be shown to the parent in the parental controls user interface (malcontent-control). The daemon imposes access controls on which users can query for usage information. Because the daemon can be accessed by the child and by the parent, and needs to be write-only for the child and read-only for the parent, it has to be a system daemon.

There’s a third API flow which allows the child to request an extension to their screen time for the day, but that’s perhaps a topic for a separate post.

IPC diagram of screen time limits support in malcontent. Screen time limit extensions are shown in dashed arrows.

So, at its core, malcontent-timerd is a time range store with some policy and a couple of D-Bus interfaces built on top.

Per-app time limits

Currently it only supports time limits for login sessions, but it is built in such a way that adding support for time limits for specific apps would be straightforward to add to malcontent-timerd in future. The main work required for that would be in gnome-shell — recording usage on a per-app basis (for apps which have limits applied), and enforcing those limits by freezing or blocking access to apps once the time runs out. There are some interesting user experience questions to think about there before anyone can implement it — how do you prevent a user from continuing to use an app without risking data loss (for example, by killing it)? How do you unambiguously remind the user they’re running out of time for a specific app? Can we reliably find all the windows associated with a certain app? Can we reliably instruct apps to save their state when they run out of time, to reduce the risk of data loss? There are a number of bits of architecture we’d need to get in place before per-app limits could happen.

Wrapping up

As it stands though, the grant funding for parental controls is coming to an end. Ignacy will be continuing to work on the UI for some more weeks, but my time on it is basically up. With the funding, we’ve managed to implement digital wellbeing (screen time limits and break reminders for adults) including a whole UI for it in gnome-control-center and a fairly complex state machine for tracking your usage in gnome-shell; a refreshed UI for parental controls; parental controls screen time limits as described above; the backend for web filtering (but more on that in a future post); and everything is structured so that the extra features we want in future should bolt on nicely.

While the features may be simple to describe, the implementation spans four projects, two buses, contains three new system daemons, two new system data stores, and three fairly unique new widgets. It’s tackled all sorts of interesting user design questions (and continues to do so). It’s fully documented, has some unit tests (but not as many as I’d like), and can be integration tested using sysexts. The new widgets are localisable, accessible, and work in dark and light mode. There are even man pages. I’m quite pleased with how it’s all come together.

It’s been a team effort from a lot of people! Code, design, input and review (in no particular order): Ignacy, Allan, Sam, Florian, Sebastian, Matthijs, Felipe, Rob. Thank you Endless for the grant and the original work on parental controls. Administratively, thank you to everyone at the GNOME Foundation for handling the grant and paperwork; and thank you to the freedesktop.org admins for providing project hosting for malcontent!

A brief parental controls update

Over the past few weeks, Ignacy and I have made good progress on the next phase of features for parental controls in GNOME: a refresh of the parental controls UI, support for screen time limits for child accounts, and basic web filtering support are all in progress. I’ve been working on the backend stuff, while Ignacy has been speedily implementing everything needed in the frontend.

Ignacy is at GUADEC, so please say hi to him! The next phase of parental controls work will involve changes to gnome-control-center and gnome-shell, so he’ll be popping up all over the stack.

I’ll try and blog more soon about the upcoming features and how they’re implemented, because there are necessarily quite a few moving parts to them.

GUADEC handbook

I was reminded today that I put together some notes last year with people’s feedback about what worked well at the last GUADEC. The idea was that this could be built on, and eventually become another part of the GNOME handbook, so that we have a good checklist to organise events from each year.

I’m not organising GUADEC, so this is about as far as I can push this proto-handbook, but if anyone wants to fork it and build on it then please feel free.

Most of the notes so far relate to A/V things, remote participation, and some climate considerations. Obviously a lot more about on-the-ground organisation would have to be added to make it a full handbook, but it’s a start.

GUADEC 2024

Goodness, it’s been a long time since I blogged. I’ve got a lot of updates to give, but perhaps let’s keep this post short, and dedicated to publishing the details of the two talks I gave at GUADEC this year, for posterity. I plan to do some more blog posts in the near future with more updates from the past year and more details of the features I’ve been working on.

An update on parental controls and digital wellbeing for GNOME 47

This was my first talk at GUADEC this year, serving as a little teaser of the work I’ve been doing recently (sponsored by Endless Network via the GNOME Foundation) to add features to parental controls and digital wellbeing.

Thank you to Allan Day for fitting in work on the design for digital wellbeing this cycle, to Florian Müllner and Felipe Borges for reviewing all the code I’ve thrown at them, and to Dylan McCall for feedback on earlier versions of break reminders.

Here’s the recording, the slides, slide notes and source.

Somewhat merging gobject-introspection into GLib

This was the second talk, and a companion to Emmanuele’s talk about changes in introspection in GNOME 46. It gives an overview of how we’ve merged half of gobject-introspection into GLib recently, and what this means for app authors (basically nothing), binding developers (something, on a timeline of your choosing) and distributions (some packaging rework, for the GLib 2.78 and 2.80 releases).

Thank you to Emmanuele for spearheading this work in GLib and doing the gobject-introspection side of it, and the many GLib and gobject-introspection contributors for helping us stabilise this (in particular with build system improvements) after it landed.

Here’s the recording, the slides, slide notes and source.

GUADEC 2023

I attended GUADEC 2023 this year in-person in Rīga. It was nice to be able to see more people in person than last year’s mini-GUADEC in Berlin, and nice to be able to travel overland/oversea to Rīga, avoiding flights and the associated huge carbon emissions. Thank you to the GNOME Foundation and my employer, Endless, for covering the travel.
And a big thank you to the local event organising team, the AV team, the volunteers and the Foundation staff for making it all happen.

The quality of the talks this year was really high. I don’t think there was a single talk slot I skipped. As a result, I didn’t get much hacking done! But there were good hallway conversations and catch ups.

I gave two talks, one on some simple improvements people can make to their apps to reduce internet data use and power use when doing so would be beneficial to the user (when on a metered network or in power-saver mode).
The aim was to remind people how easy it is to do this, and provide some examples of how different apps present these states/events in the UI, since the best way to do that can differ between apps.

You can find the slides to that talk here (notes here), and a video of it is on YouTube.

The talk has resulted in three GNOME initiatives. Each corresponds to a different state your app can pay attention to — some apps should pay attention to all of them, some apps only some of them. Please take 10 minutes to
check your app against the initiatives and make updates if necessary. I’m in the gnome-hackers and gnome-circle Matrix rooms if you have any questions about them.

My second talk was an overview of work I’ve been doing on-and-off over the past couple of years (based on work by others) to allow apps to save and restore their state when the user logs out or shuts down the computer. The idea is that the user can restore
the set of apps they were using, in the state and configuration they were left in, when next starting the computer or logging in. It’s a big project, and there’s a long way to go, but it felt like the right time to present what
we’ve got so far, let more people know how the project is structured, and get feedback from toolkit developers and app authors about the whole idea.

You can find the slides to that talk here (notes here), and a video of it is also on YouTube.

Thankfully there was 5 minutes for questions at the end of the talk, and people used them well to raise some good points and ask some good questions. I’m still in the process of factoring all that feedback into the plan, but
should hopefully have an update to give on the project in a future blog post soon.

Interesting talks I attended included Peter Hutterer’s talk about sending and capturing input in Wayland, which I think distilled his blogposts about the topic very clearly.
Allan Day’s talk about communication, which was an excellent summary of a rather long list of books on the subject, and it was very clearly presented. I feel like I could do with a cheatsheet list of his recommendations to stick next to my computer sometimes.
Evan Welsh, Philip Chimento, Nasah Kuma and Sonny Piers’s talks about JavaScript, TypeScript and the latest changes in GJS. These provided a good update on a lot that has been happening in GJS, a useful overview of TypeScript, and were really clearly presented.
Jussi Pakkanen’s talk about circles, or was it about settings? It was a good example of a lightning talk which draws you in then spins you around.

In the hallway track, I had some interesting chats with Bartłomiej about the infrastructure of ODRS, which is used to provide ratings/review data to gnome-software.
I also had some positive conversation with Cassidy about plans for GUADEC 2024 in Denver.
And at the event dinner, a really energising chat with Scott about canyoning, hiking, skiing and caving in the US and the UK.

Carbon emissions analysis of GUADEC 2022

I’ve just finished estimating the carbon emissions from GUADEC 2022, and have a few interesting highlights from the report. The report is based on data collected from the streaming servers, registration data, and the post-conference survey.

  • Having an online component to the conference increased the audience by a factor of 10: there were around 120 in-person attendees in Mexico, but there were an average of 1300 people using Big Blue Button.
  • The carbon emissions from providing the remote infrastructure were around 2.8tCO2e, or about 2kgCO2e per remote attendee.
  • Having a remote attendance party in Berlin allowed around one tenth of the attendees to attend with a factor of 10 lower transport emissions than those who attended in-person in Mexico. The average transport emissions for those who went to Berlin were 88kgCO2e, whereas they were around 1tCO2e for those going to Mexico.
  • For context, the annual emissions per person can be at most 2.3tCO2e by 2030 in order to limit global warming to 1.5C by the end of the century. That covers all food, travel, heating, purchases, etc. So travel to Mexico was 40% of the average attendee’s annual target.
  • Half of the in-person attendees travelled from within Mexico, which will have skewed the mean transport emissions downwards. The distribution is more likely bimodal between around 50kgCO2e for locals and more like 2-3tCO2e for those coming from outside Mexico.
  • As I wrote at the time, the remote attendance party was fun to attend and felt like it worked as a way to attend the conference (there were no A/V problems, we had some nice local socials, etc.). I would do it again.
  • The post-conference survey had a low response rate of about 19% of registered attendees, which made some of this analysis hard. Please always fill in the post-conference survey! It doesn’t take long, and aside from any of this analysis, it helps the organisers plan conferences better in future.
  • Many thanks to Kristi, Caroline and Bartłomiej for collecting the data needed for this analysis.
  • If anyone spots problems in the analysis please say! This is not the kind of thing I practice doing very often and I’ve had to make a number of assumptions.

Other conferences

A post-conference survey has been done for other big GNOME events, such as GNOME.Asia and LAS. Would anyone be interested in doing a similar analysis for those events? Perhaps we can get a semi-automated pipeline in place for it.

Getting from the UK to Riga for GUADEC 2023

I’ve just booked travel for getting to Riga, Latvia for GUADEC 2023, and I thought I’d quickly write up my travel plans in case it saves someone else some time in planning travel.

I am not flying, because planes are too polluting. Instead, I am taking the train to Lübeck in Germany, then an overnight ferry to Liepāja, and then a bus the following morning to Riga. It’s a bit slower, but means it’s a bit easier to get some hacking done, stretch my legs and move around, and not fund fossil fuel companies as much. I’ll get enough stopover time in Köln, Hamburg and Lübeck to quickly look round, and a night in Liepāja to see it.

Overall the travel time is just over 2 days, with half of that spent on trains, and half on a ferry. By comparison, a flight is about 7 hours (5 hours flying, 2 hours faffing in airports) plus travel time to the airport.

The carbon emissions (140kgCO2e return) are roughly a quarter of those from flying (520kgCO2e), and interestingly a significant part of those emissions (46kgCO2e) is the 3 hour bus journey to get from Liepāja to Riga, though that’s quite sensitive to the occupancy level of the bus.

The financial cost (800ドル return) is about two times that of flying (380ドル), though I have not factored in the costs of getting to/from airports and have not fully explored the hidden fees for baggage and other essentials so the ratio might be a little lower. This is quite upsetting. A disproportionate part of the cost (178ドル return) is the Eurostar, because it’s oversubscribed and I missed the early ticket releases due to waiting for grant approval. Perhaps I should not wait next time.

The journey

On 2023年07月22日:

  • Eurostar from London to Brussels-Midi, departing 07:04
  • Train from Brussels-Midi to Lübeck-Travemünde Skandinavienkai, departing 10:25 (ICE 15, ICE 200, RE 11428, RB 11528)
  • Nice walk from there to the ferry terminal for half an hour
  • Overnight ferry from Lübeck/Travemünde to Liepāja, departing 23:30

On 2023年07月23日: On the ferry all day, then stay overnight in Liepāja

On 2023年07月24日: Bus from Liepāja to Riga, departing early morning

Alternatives

I strongly looked at taking the train from Hamburg to Stockholm, and then the ferry from there to Ventspils. Unfortunately, it has limited capacity and there is track maintenance planned for around my travel dates, so I could not get suitable tickets. It would have made the timings a little more convenient overall, for about the same overall carbon emissions and cost.

Join me

If anybody else is going overland from the UK or far-western Europe, this is hopefully a sensible route for you to take, and it would be lovely if you wanted to join me. I will be arriving 2 days early for GUADEC (as we’re having an Endless OS Foundation meetup), but if you wanted to do the same journey 1 or 2 days later then it shouldn’t differ significantly. In any case, I can put you in touch with others making this journey if you want.

Tip for debugging refcounting issues: change ref calls to copy

Over the last couple of days I’ve been looking at a refcounting issue in GLib’s D-Bus implementation.

As with many things in GLib, the allocations in this code use refcounting, rather than new/free, to manage object lifecycles. This makes it quite hard to debug lifecycle issues, because the location of a bug (a ref or unref call which isn’t correct) can be quite far removed (in time and code) from where the effects of that bug become visible. This is because the effects of refcounting problems only become visible when an object’s refcount reaches zero, or when the program ends and its refcount still hasn’t reached zero.

While debugging this code, I tried an approach I haven’t before: changing some of the ref calls on the buggy object to be copy calls instead. (Specifically, changing g_object_ref() to g_dbus_message_copy().) That split up the lifecycle of the object into smaller pieces, narrowing down the sets of ref/unref calls which could be buggy. Ultimately, this allowed me to find some bugs in the code, and hopefully those are the bugs causing the refcounting issue. Since the issue is intermittent, it’s a bit hard to be sure.

This debugging approach was possible in this case because the object I was debugging is immutable, so passing around copies of it doesn’t affect the behaviour of other bits of code vs passing around the original. Hence this approach is only applicable in some situations. But it’s another good reason why using immutable objects is quite helpful when writing code, and it’s certainly an approach I’m going to be using again when I can.

Garden work

Another post which is not about software! I’ve recently, finally, finished reworking my garden, and here’s a brief writeup of what happened. It includes some ideas for low-embodied energy and ecologically friendly garden design.

The original garden

This house is on a hill. The original garden was a set of three concrete slabbed terraces going down the hill, with some wooden decking on the top terrace. There was a block paved path ramping down the garden, separating the decking from a sloping grass lawn. There were very few plants which weren’t grass or a few pots.

Problems with this included:

  • Decking was rotten
  • Lower terrace served no purpose and was devoid of life
  • There was very little biodiversity in the garden, and no space to grow anything
  • Steps in the path had been installed with uneven heights and that was surprisingly annoying

The plan

  • Get rid of the decking because it’s rotten
  • Remove the terraces because they’re just concrete, and replace them with more soil and planting area
  • Make the subdivisions of the garden less rectilinear so it feels a bit less brutal
  • Lower some areas of the terraces a bit to get a bit more privacy (the garden is overlooked)
  • Severely reduce the grass lawn area, because it requires frequent mowing and is not very ecologically diverse
  • Rebuild the path to make it curvy and add some planting area in a sunny spot by it
  • Keep the garden adaptable and don’t make anything too permanent (by cementing it in place) — I, or others, may want to rearrange things in future

Executing the plan

I started on this in 2019. Progress was slow at first, because a large part of the plan involved digging out the terraces, and there was some question about whether this would undermine the foundations of the house. That could cause the house to fall down. That would be bad.

I talked to a structural engineer, and he specified a retaining wall system I could install, which would retain the house wall and foundations, act as a raised bed, and is made out of wood so would have low embodied carbon compared to a (more standard) masonry wall (which is about 40kgCO2e/m2, see table 11 here). There was various other research and considerations about adjoining property, safety, drainage, appearance of the materials as they age, and suitability for DIY which fed into this decision. I can go into the details if anyone’s interested (get in touch if so).

What followed was about 10 months of intermittent work on it, removing the old terraces, digging a pond and sowing some wildflowers, installing the new retaining wall, fixing drainage through the clay, bringing in soil, laying a clover lawn, and rebuilding the path.

The result

I’m pretty pleased with the result. There are a few decisions in particular which I’m quite pleased worked out, as they’re not a common approach in the UK and were a bit of a gamble:

  • Clover patio. Rather than a paved patio area (as is common here), I planted clover seed on a thin bed of soil with a weed control membrane beneath. This has a significantly lower embodied carbon than paving (around 100-200kgCO2e/tonne less, if imported natural stone was used, which is the standard in the UK at the moment), and drains better, so it doesn’t contribute to flash flooding runoff. With rain becoming less frequent but more intense in the UK, runoff is going to become more of a problem. My full analysis of the options is here. I chose clover for the planting because it doesn’t require mowing, and should stay short enough to sit on. As per table 1 of this paper, I might adjust the planting in future to include other non-grass species.
  • Wooden retaining wall. I used Woodblocx, and it worked out great. It didn’t require any cementatious materials (which have high embodied carbon), just a compacted type 2 sub-base and their wooden blocks. It’s repairable, and recyclable at end of life (in about 25 years).
  • Wood chip path. This was easy to install (wood chips over a weed control membrane), doesn’t contribute to flash flooding runoff like paved paths do, and is a good environment for various insects which like damp and dark places. It will need topping up with more wood chips every few years, but no other maintenance. The path edging is made from some of the old decking planks from the original garden (the ones which weren’t rotten).
  • Water butt stands. These are all made from bits of old decking or Woodblocx offcuts, and make the water butts easier to use by bringing the tap to a more reachable level. I also made a workbench out of old decking planks.

Subjectively, now the garden’s been basically finished for a year (I finished the final few bits of it the other day), I’ve seen more insects living in it, and birds feeding on them, than I did before. Yay!