Updating TTL in a better way

Hi,

I'm experimenting with a new GL-X3000 and would like to have traffic use a TTL of 64 going out of the modem. Normal behavior is for clients to use a TTL of 64, which is decremented by 1 by the router, causing packets to leave the modem interface with TTL value 63. I'd need that to be 64.

This can quite easily be achieved by setting a TTL value of 64 on the modem interface (through the GUI), or adding an iptables rule like this:

iptables -t mangle -I POSTROUTING 1 -j TTL --ttl-set 64

This works as expected. All outgoing packets have their TTL reset/forced to a value of 64.

However. Obviously, exactly as expected, this means that the TTL is always set to a value of 64. This also applies to traffic types where the application (on the client device) actually relies on using a different TTL value to function. Most obvious example is traceroute, which uses TTL to determine the hops in between client and target. A packet with TTL=1 will cause the first router to respond, TTL=2 will cause the second one to respond, and so on. That's how traceroute determines the path that packets are taken throughout the network/internet.

By forcing the TTL to always be 64 that no longer works. The first traceroute packet sees its TTL updated from 1 to 64... causing the packet to reach the destination (rather than only the first router) and traceroute to report that the destination is directly available in the local LAN. Which, obviously, is incorrect. This is the case for traceroute, but applies to all applications that rely on a non-standard TTL.

What I'd want to do is update the outgoing TTL ONLY if the TTL equals 63 (Linux/Mac clients) or 127 (Windows clients). That's regular traffic, and only that traffic should see the TTL be set to 64. Other traffic should not be changed as it was purposefully using a non-standard TTL.

So, how can we achieve this. The most straightforward method would be by adding a filter to the mangle command. And we'll need two rules rather than just one, for Linux and Windows clients:

iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 63 --ttl-set 64 
iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 127 --ttl-set 64

Resulting in the following rules:

root@GL-X3000:~# iptables -L -t mangle | grep -i ttl
[...] TTL match TTL == 127 TTL set to 64
[...] TTL match TTL == 63 TTL set to 64
root@GL-X3000:~# 

I have tested the above and can confirm that this works. Normal traffic from clients has TTL 64, while special traffic (like traceroute) using non-standard TTL values passes through without being affected.

However from what I understand it is also bad practice to apply filters in the mangle table. Perhaps there is a better/smarter way of doing this? Any suggestions?

1 Like

On a sidenote. The GL router seems to be pinging 1.1.1.1 regularly, most likely for the reason of determining the interface/internet connection status. However, I noticed that this happens with a non-standard TTL of 60 rather than 64. Why is that?

root@GL-X3000:~# tcpdump -i rmnet_mhi0 host 1.1.1.1 -v
tcpdump: listening on rmnet_mhi0, link-type RAW (Raw IP), capture size 262144 bytes
18:11:42.618115 IP (tos 0x0, ttl 60, id 55537, offset 0, flags [DF], proto ICMP (1), length 84)
    10.X.X.X > one.one.one.one: ICMP echo request, id 1288, seq 0, length 64

When manually doing a 1.1.1.1 ping, from the router CLI, the TTL is normal (64). It seems that GL or OpenWRT software purposefully sets the TTL to 60 instead?

1 Like

If the method of ttl setting works should just keep it. I don't know if there are smarter ways but it is smart already.

Well, thats the point. It does not work. Setting a TTL value through the GUI interface breaks various applications that rely on a custom TTL. The most important one that most people would run into is traceroute which no longer works. Traceroute is crucial for being able to debug network issues.

The approach posted above resolves that issue.

There seems to be a slight problem, if there is a multi-level NAT at the lower level of the router, the rule will be invalid.

The repeater set ttl to 64 by default.

This caused the problem. We are checking how to solve this in a better way.

Good point IF you are doing multi-NAT, which is not really ideal in any case - but could happen in some specific situations when you cannot get around that.

In such case, as there would be an extra hop between client and internet, you could consider also adjusting packets going out in with a TTL of 62 and 126. The full set of rules would then be:

iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 62 --ttl-set 64 
iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 63 --ttl-set 64
iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 126 --ttl-set 64 
iptables -t mangle -I POSTROUTING 1 -j TTL --match ttl --ttl-eq 127 --ttl-set 64

Which still allows custom TTL values to be used by any client(s) as long as those are not 64, 63, 128, 127.

@Xandrios How are you persisting your smarter custom iptables ttl rules and removing the default rules? I find that after removing the default TTL rules they come back periodically.

My situation: I'd just like my GL.iNet router to not touch TTL rules at all. I don't use tethering and I'd rather just have traceroute always working. I can do this manually with some iptables commands but how do I make these persist in a clean way? After removing the ttl rules with my own iptables commands the TTL rules come back periodically a couple times a day.

Putting custom iptables commands into the Firewall section like in this screenshot I thought would work but it doesn't. From watching /etc/init.d/firewall restart I see that the /etc/firewall.user script is executed before /etc/firewall-portal.user and firewall.ethernet_ttl where ttl rules are set. So my custom iptables commands can't undo what these do because it gets run first.

I can't find the firewall-portal.user definition anywhere in the Luci interface or the GL.iNet interface.

I can just edit firewall-portal.user to remove the iptables commands that force a TTL, but that's less maintainable and I worry that GL.iNet might restore the files to their original state on reboot or during an update. It's not even clear to me why firewall-portal is being ran at all when I don't have a guest portal active.

Is there a better more maintainable way to just prevent GL.iNet from setting this iptables ttl rules completely? I really wish there was just an option in the interface to do that. Besides that, anyone had ideas on how to do this cleanly?

Okay I came up with the best solution for this I can think of:

I just modified /etc/config/firewall and moved this section:

config include
	option path '/etc/firewall.user'

From near the top of the file to the very bottom of the file. This will ensure that my custom iptables commands set in the LuCI interface get ran after all the default GL.iNet stuff, allowing my iptables rules to delete the previously set forced ttl rules.

No idea if this will persist but I'm optimistic. Would still love to hear others approaches to persist custom iptables commands.

Interesting finding. I did use the custom firewall rules set through Luci, and - at least when I did back then - it was still working after a restart. Truth is that I have not verified that these rules are still being applied after the latest update... I will have to check. Will update when I have access to the device.

Is this why I my trace route only shows 2 hops to 1.1.1.1 on a client device when in repeater mode?

For having only 2 hops to 1.1.1.1 you need to sit in their datacenter …

1 Like

If you have the regular TTL-adjustment configured, then yes. Traceroute will report incorrect results, and this is why I proposed the above method to work around this.

1 Like

I was able to verify this; and in my case I did not have to do anything special. Just configured the rules in LuCi:

And an 'iptables -t mangle -L' still shows the rules to be loaded, even after a bunch of restarts in the meantime. Odd that this did not work for you. The include rule that you mentioned is about halfway down in /etc/config/firewall for me.

I don't have any custom TTL set. Even when adding the custom firewall rules on this post, I still get a 2 hop trace route to anywhere from a client and a 1 hop trace route from the router itself to anywhere. I am double natted, if that matters at all. This wasn't happening running the same configuration on a gl-sft1200 running version 4.3.2.

Edit: More Detail

root@router:/etc# iptables -L -t mangle | grep -i ttl
TTL all -- anywhere anywhere TTL match TTL == 127 TTL set to 64
TTL all -- anywhere anywhere TTL match TTL == 126 TTL set to 64
TTL all -- anywhere anywhere TTL match TTL == 63 TTL set to 64
TTL all -- anywhere anywhere TTL match TTL == 62 TTL set to 64
PORTAL_TTL all -- anywhere anywhere
Chain PORTAL_TTL (1 references)
TTL all -- anywhere anywhere TTL set to 64

Traceroute from router:

root@router:/etc# traceroute 1.1.1.1
traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 38 byte packets
1 one.one.one.one (1.1.1.1) 58.460 ms 22.392 ms 17.445 ms

Traceroute from Client (Windows):

C:\Users\admin>tracert 1.1.1.1

Tracing route to one.one.one.one [1.1.1.1]
over a maximum of 30 hops:

1 5 ms 1 ms 3 ms console.gl-inet.com [192.168.8.1]
2 27 ms 31 ms 21 ms one.one.one.one [1.1.1.1]

Trace complete.