Skip to content

Improve passive mode FQDN rule creation with domain name prioritization and smart port display#782

Merged
objective-see merged 1 commit intoobjective-see:masterfrom
haroondilshad:features/passive-mode-fqdn-rule
Aug 17, 2025
Merged

Improve passive mode FQDN rule creation with domain name prioritization and smart port display#782
objective-see merged 1 commit intoobjective-see:masterfrom
haroondilshad:features/passive-mode-fqdn-rule

Conversation

@haroondilshad
Copy link
Copy Markdown
Contributor

Summary

This PR enhances passive mode rule creation to prioritize domain names over IP addresses and implements smart port display for cleaner UI. When passive mode creates rules automatically, they now show meaningful names like github.com instead of 140.82.112.3:443.

Changes Made

🎯 Domain Name Prioritization

  • Added getBestHostnameFromFlow: method to FilterDataProvider that prioritizes hostname sources:
    1. flow.URL.host (best for domain names)
    2. flow.remoteHostname (macOS 11+ fallback)
    3. remoteEndpoint.hostname (IP address fallback)
  • Updated passive mode rule creation to use the new hostname prioritization logic
  • Maintains backward compatibility with IP addresses when domain names aren't available

🎨 Smart Port Display

  • Hide common ports (80, 443) from rule display for cleaner UI
  • Show uncommon ports (8080, 3000, etc.) to highlight important information
  • Preserve full data internally for precise rule matching
  • No impact on filtering - display logic only affects UI presentation

Test Results

9/9 tests passed covering:

  • Domain name prioritization over IP addresses
  • Port hiding for common ports (80, 443)
  • Port showing for uncommon ports
  • End-to-end functionality validation
  • Edge case handling

📝 Testing Note

The included tests use standalone test files for quick validation and demonstration. For long-term maintainability, these could be converted to the standard XCTest framework and integrated as test targets in the Xcode project.

Before/After Examples

Before (IP-based) After (Domain-based)
140.82.112.3:443 github.com
52.36.184.210:443 api.slack.com
127.0.0.1:8080 localhost:8080

Benefits

  • Better UX: Rules display meaningful domain names instead of IP addresses
  • Cleaner interface: Common ports hidden, uncommon ports highlighted
  • Consistency: Passive mode now creates similar rule types as active mode
  • No regressions: All existing functionality preserved
  • Backward compatible: Falls back to IP addresses when needed

Technical Details

  • Files modified: FilterDataProvider.m/.h, RulesWindowController.m
  • New method: getBestHostnameFromFlow: using same logic as active mode
  • Display logic: Smart port hiding in createConnectionCell method
  • Test coverage: Comprehensive test suite validates all scenarios (LuLu/Tests/)

…ently and add method to FilterDataProvider for retrieving the best hostname from flow, prioritizing domain names over IP addresses.
@haroondilshad
Copy link
Copy Markdown
Contributor Author

Since I don't have a paid Apple developer account I can't test the network extension myself. It will be great if you could test this.

//init addr/port with smart port display (hide common ports 80, 443)
if (([rule.endpointPort isEqualToString:@"80"] || [rule.endpointPort isEqualToString:@"443"]) &&
NO == [rule.endpointAddr isEqualToString:VALUE_ANY] &&
NO == [rule.endpointPort isEqualToString:VALUE_ANY]) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the NO == [self.endpointPort isEqualToString:VALUE_ANY] check is needed?

The first condition explicitly checks if endpointPort equals "80" OR "443"
If the port is literally the string "80" or "443", it cannot simultaneously be VALUE_ANY (which is "*")

As such, I think this would suffice:

if (([self.endpointPort isEqualToString:@"80"] || [self.endpointPort isEqualToString:@"443"]) && 
    NO == [self.endpointAddr isEqualToString:VALUE_ANY]) {
    return address;
}

...right?

remoteEndpoint = (NWHostEndpoint*)flow.remoteEndpoint;

//priority 1: try flow.URL.host (best for domain names)
if(nil != flow.URL.host && 0 != flow.URL.host.length)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I probably do checks like this elsewhere, this could be simplified to just: if(flow.URL.host.length)

}

//priority 3: fallback to remoteEndpoint.hostname (may be IP address)
if(nil != remoteEndpoint.hostname && 0 != remoteEndpoint.hostname.length)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here: if(remoteEndpoint.hostname.length) should suffice

// Smart port display (hide common ports 80, 443)
if (([self.endpointPort isEqualToString:@"80"] || [self.endpointPort isEqualToString:@"443"]) &&
NO == [self.endpointAddr isEqualToString:VALUE_ANY] &&
NO == [self.endpointPort isEqualToString:VALUE_ANY]) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above, but this check NO == [self.endpointPort isEqualToString:VALUE_ANY]) is superfluous?

Copy link
Copy Markdown
Owner

@objective-see objective-see left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR! It's great - and I love the improvements!
Left a few comments for you to review/address, and then I'll merge it in! Mahalo again

@objective-see objective-see merged commit dc3cc55 into objective-see:master Aug 17, 2025
@objective-see
Copy link
Copy Markdown
Owner

Thanks for the PR, went ahead and merged it! I'll work on the tweaks I commented on earlier (as they are super minor)

@haroondilshad
Copy link
Copy Markdown
Contributor Author

haroondilshad commented Aug 26, 2025

Hey, sorry for the late response and thank you for the merge. Maybe I can take care of these in an upcoming PR :)

I just joined your discord as well. I plan on contributing to this in the future as well. Is there somewhere I can volunteer in your organization or join as a member. For now my motivation would be to get a contributor(least privileges needed to test network extensions, no malicious intentions, promise!) role in your org Apple developer team so that I can locally test network extensions which is necessary for Lulu. Who knows I could find more time in the future. Would love to play my part. I just went through your website and that's what inspired my decision just now. @objective-see

@tophertopaz
Copy link
Copy Markdown

Could this please be made optionally conditional upon an "Always show all port numbers" type of setting?

I make a large number of rules that explicitly distinguish between ports 80 and 443 (e.g. I want a process to only be able to communicate over 443 and not 80) so no longer seeing the port numbers is far more cumbersome than convenient for my use case.

objective-see added a commit that referenced this pull request Aug 26, 2025
preference for showing rule endpoint details (#782)
improved logging for matching on events (#777)
@objective-see
Copy link
Copy Markdown
Owner

@tophertopaz - yes. Will publish new version soon with the ability to toggle this as a setting

image

@objective-see
Copy link
Copy Markdown
Owner

@haroondilshad no worries - I implemented my suggested fixes 😁

@tophertopaz
Copy link
Copy Markdown

Very much appreciated! Thank you both for the exceptional work you do!

objective-see added a commit that referenced this pull request Sep 1, 2025
Too confusing, as it could be (mis)interpreted as the rule covered *all* traffic to an endpoints when port(s) weren't shown (#782)
@objective-see
Copy link
Copy Markdown
Owner

objective-see commented Sep 1, 2025

Decided to revert back to always showing ports, as it didn't clean up the UI too much, and I think it's a bit confusing, as it could be (mis)interpreted as the rule covered all traffic to an endpoints when port(s) weren't shown.

And yes, originally made a setting to toggle this, but really trying to keep the app simple, hence decided was easier just to revert to as was.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants