Στο Android 10 και μετά, μια εφαρμογή που ανοίγει το /proc/net/tcp παίρνει άρνηση πρόσβασης αντί για πίνακα sockets. Η διεπαφή VPN δεν ενοχλείται: τα πακέτα φτάνουν ακόμη, γιατί το τούνελ είναι απόφαση δρομολόγησης, όχι άδεια αρχείου. Αυτό που άλλαξε είναι η απάντηση σε μια στενότερη ερώτηση: «ποια εφαρμογή κατέχει αυτή τη σύνδεση;» Το υποκατάστατο είναι το ConnectivityManager.getConnectionOwnerUid, το υποστηριζόμενο ερώτημα ανά σύνδεση, και η επιλογή ανάμεσα στην παλιά συνήθεια και το νέο API εξαρτάται από το ποια από τρεις δουλειές έχετε πραγματικά: λήψη πακέτων, απόδοση ροής ή άθροιση χρήσης.

Δύο τρόποι να απαντήσετε στην ερώτηση του κατόχου

Πριν από το API 29, μια προνομιούχα διαδικασία ή root μπορούσε να διαβάσει τους πίνακες sockets του πυρήνα, να ταιριάξει την τετράδα και να μάθει το UID του κατόχου. Αυτό έδινε ένα απόθεμα για όλη τη συσκευή σε ένα αρχείο. Από το API 29 η προβολή του συστήματος αρχείων είναι περιορισμένη, και η υποστηριζόμενη διαδρομή είναι ένα ερώτημα ανά σύνδεση με το πρωτόκολλο, την τοπική διεύθυνση και την απομακρυσμένη διεύθυνση.

QuestionLegacy socket tablesgetConnectionOwnerUid
Who owns this flow?Scan and match the tupleAsk once per tuple
Device-wide inventory?Yes, when readableNo, one flow at a time
Historical usage?NoNo; that is NetworkStatsManager
Works without root?No on modern releasesYes, for authorized callers

Η σύγκριση δεν είναι «παλιό εναντίον νέου». Είναι τρεις διαφορετικές δουλειές που παλιά τις ικανοποιούσε ένα αναγνώσιμο αρχείο και τώρα τις ικανοποιούν τρεις διαφορετικές διεπαφές με τρία διαφορετικά συμβόλαια.

Η τοπική διεύθυνση είναι το άκρο της εφαρμογής

Η υπογραφή της μεθόδου δέχεται ένα πρωτόκολλο, μια τοπική διεύθυνση socket και μια απομακρυσμένη διεύθυνση socket. «Τοπική» σημαίνει την πλευρά της εφαρμογής στη σύνδεση, όχι το άκρο που τυχαίνει να είναι η πηγή του πακέτου τη στιγμή της επιθεώρησης. Ένα πακέτο που ταξιδεύει από τον απομακρυσμένο διακομιστή πίσω στην εφαρμογή έχει πηγή και προορισμό αντεστραμμένα σε σχέση με αυτόν τον ορισμό.

ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
    OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);

Αυτό το λάθος κατεύθυνσης είναι ο συνηθέστερος λόγος που ένα λειτουργικό ερώτημα επιστρέφει λάθος απάντηση ή καμία. Καταγράψτε και τα δύο άκρα πριν την κλήση και επιβεβαιώστε το ζεύγος με τη σύνδεση που άνοιξε πραγματικά η εφαρμογή. Η οικογένεια διευθύνσεων μετράει επίσης: μια διεύθυνση αντιστοιχισμένη σε IPv4 και μια εγγενής διεύθυνση IPv6 είναι διαφορετικά tuple, όπως και οι ροές TCP και UDP στις ίδιες θύρες.

Η εξουσιοδότηση και τα άγνωστα αποτελέσματα είναι μέρος του συμβολαίου

Το ερώτημα περιορίζεται σε εξουσιοδοτημένους καλούντες, συμπεριλαμβανομένης της ενεργής εφαρμογής VPN για τον σχετικό χρήστη. Μια άσχετη εφαρμογή παρακολούθησης δεν μπορεί να υποθέσει ότι θα απαριθμήσει τις συνδέσεις άλλου προφίλ, και μια μη εξουσιοδοτημένη κλήση μπορεί να πετάξει εξαίρεση ασφαλείας αντί να επιστρέψει φιλικό σφάλμα. Χειριστείτε και τα δύο αποτελέσματα ρητά.

Ένας απόν κάτοχος δεν είναι όνομα πακέτου. Η ροή μπορεί να έκλεισε ανάμεσα στο πακέτο και το ερώτημα, το tuple μπορεί να είναι ελλιπές, ή ο καλών μπορεί απλώς να μην επιτρέπεται να ξέρει. Η προσωρινή αποθήκευση της πρώτης επιτυχημένης απάντησης και η επαναχρησιμοποίησή της αργότερα αποδίδει μια νέα σύνδεση σε μια παλιά εφαρμογή, που είναι χειρότερο από το να αναφέρεις άγνωστο.

Όταν η απόδοση αποτύχει, η κίνηση χρειάζεται ακόμη μια απόφαση: μπλοκάρετέ την όπου το απαιτεί η πολιτική, επιτρέψτε μόνο ένα εύρος που καταλαβαίνετε, ή δείξτε το κενό. Αυτό το εφεδρικό ανήκει στον σχεδιασμό, όχι σε ένα σχόλιο.

Η λογιστική είναι άλλη ερώτηση

Το NetworkStatsManager αναφέρει χρήση με τους δικούς του κανόνες πρόσβασης. Μπορεί να σας πει πόσα μετέφερε ένα UID σε μια περίοδο· δεν μπορεί να σας πει τον κάτοχο ενός αυθαίρετου ζωντανού πακέτου. Οι δυνατότητες δικτύου περιγράφουν ιδιότητες δικτύου, όχι κατάλογο sockets. Η ανάμειξη των τριών παράγει πίνακες που φαίνονται συνεπείς και δεν είναι.

Για την απλή περίπτωση του να αποφασίσετε ποιες εφαρμογές χρησιμοποιούν το τούνελ, οι υποστηριζόμενες λίστες επιτρεπόμενων και απαγορευμένων εφαρμογών του Android εκφράζουν ήδη την πολιτική χωρίς κανένα scraping. Κρατήστε την απόδοση ανά ροή για τις περιπτώσεις που την χρειάζονται πραγματικά, και προϋπολογίστε μια συντηρούμενη υλοποίηση που χειρίζεται κατεύθυνση, δικαιώματα και άγνωστες καταστάσεις μαζί.

Διαλέξτε το εργαλείο κατά δουλειά και μετά δοκιμάστε τα άκρα

Αν η απαίτηση είναι επιλογή τούνελ, χρησιμοποιήστε τη λίστα της πλατφόρμας. Αν είναι ζωντανή απόδοση, χρησιμοποιήστε το ερώτημα κατόχου και θεωρήστε κάθε αναπάντητο tuple άγνωστο. Αν είναι ιστορικά σύνολα, χρησιμοποιήστε το stats API. Έπειτα δοκιμάστε τα τρία άκρα που σπάνε τις αφελείς υλοποιήσεις: ένα πακέτο αντίστροφης κατεύθυνσης, μια ροή που κλείνει πριν τη ρωτήσετε, και έναν καλών που δεν είναι το ενεργό VPN.

Τίποτα από αυτά δεν απαιτεί απενεργοποίηση του SELinux, root στη συσκευή ή παγκοσμίως αναγνώσιμους πίνακες πυρήνα. Ο περιορισμός είναι το νόημα: η πλατφόρμα μετακίνησε την απόδοση πίσω από ένα συμβόλαιο ώστε η απάντηση να είναι διαθέσιμη στο συστατικό που τη χρειάζεται νόμιμα, και σε κανέναν άλλο.

Σχετικά άρθρα