1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <gtest/gtest.h>
18 #include <private/android_filesystem_config.h>
19 
20 #define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
21 #include "DnsBpfHelper.h"
22 
23 using namespace android::bpf;  // NOLINT(google-build-using-namespace): exempted
24 
25 namespace android {
26 namespace net {
27 
28 constexpr int TEST_MAP_SIZE = 2;
29 
30 #define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
31 
32 class DnsBpfHelperTest : public ::testing::Test {
33  protected:
34   DnsBpfHelper mDnsBpfHelper;
35   BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
36   BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
37   BpfMap<uint32_t, bool> mFakeDataSaverEnabledMap;
38 
SetUp()39   void SetUp() {
40     mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE);
41     ASSERT_VALID(mFakeConfigurationMap);
42 
43     mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
44     ASSERT_VALID(mFakeUidOwnerMap);
45 
46     mFakeDataSaverEnabledMap.resetMap(BPF_MAP_TYPE_ARRAY, DATA_SAVER_ENABLED_MAP_SIZE);
47     ASSERT_VALID(mFakeDataSaverEnabledMap);
48 
49     mDnsBpfHelper.mConfigurationMap = mFakeConfigurationMap;
50     ASSERT_VALID(mDnsBpfHelper.mConfigurationMap);
51     mDnsBpfHelper.mUidOwnerMap = mFakeUidOwnerMap;
52     ASSERT_VALID(mDnsBpfHelper.mUidOwnerMap);
53     mDnsBpfHelper.mDataSaverEnabledMap = mFakeDataSaverEnabledMap;
54     ASSERT_VALID(mDnsBpfHelper.mDataSaverEnabledMap);
55   }
56 
ResetAllMaps()57   void ResetAllMaps() {
58     mDnsBpfHelper.mConfigurationMap.reset();
59     mDnsBpfHelper.mUidOwnerMap.reset();
60     mDnsBpfHelper.mDataSaverEnabledMap.reset();
61   }
62 };
63 
TEST_F(DnsBpfHelperTest,IsUidNetworkingBlocked)64 TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked) {
65   struct TestConfig {
66     const uid_t uid;
67     const uint32_t enabledRules;
68     const uint32_t uidRules;
69     const int expectedResult;
70     std::string toString() const {
71       return fmt::format(
72           "uid: {}, enabledRules: {}, uidRules: {}, expectedResult: {}",
73           uid, enabledRules, uidRules, expectedResult);
74     }
75   } testConfigs[] = {
76     // clang-format off
77     //   No rule enabled:
78     // uid,         enabledRules,                  uidRules,                      expectedResult
79     {AID_APP_START, NO_MATCH,                      NO_MATCH,                      false},
80 
81     //   An allowlist rule:
82     {AID_APP_START, NO_MATCH,                      DOZABLE_MATCH,                 false},
83     {AID_APP_START, DOZABLE_MATCH,                 NO_MATCH,                      true},
84     {AID_APP_START, DOZABLE_MATCH,                 DOZABLE_MATCH,                 false},
85     //   A denylist rule
86     {AID_APP_START, NO_MATCH,                      STANDBY_MATCH,                 false},
87     {AID_APP_START, STANDBY_MATCH,                 NO_MATCH,                      false},
88     {AID_APP_START, STANDBY_MATCH,                 STANDBY_MATCH,                 true},
89 
90     //   Multiple rules enabled:
91     //     Match only part of the enabled allowlist rules.
92     {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH,                 true},
93     {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH,               true},
94     //     Match all of the enabled allowlist rules.
95     {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false},
96     //     Match allowlist.
97     {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH,   DOZABLE_MATCH,                 false},
98     //     Match no rule.
99     {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH,   NO_MATCH,                      true},
100     {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH,                      true},
101 
102     // System UID: always unblocked.
103     {AID_SYSTEM,    NO_MATCH,                      NO_MATCH,                      false},
104     {AID_SYSTEM,    NO_MATCH,                      DOZABLE_MATCH,                 false},
105     {AID_SYSTEM,    DOZABLE_MATCH,                 NO_MATCH,                      false},
106     {AID_SYSTEM,    DOZABLE_MATCH,                 DOZABLE_MATCH,                 false},
107     {AID_SYSTEM,    NO_MATCH,                      STANDBY_MATCH,                 false},
108     {AID_SYSTEM,    STANDBY_MATCH,                 NO_MATCH,                      false},
109     {AID_SYSTEM,    STANDBY_MATCH,                 STANDBY_MATCH,                 false},
110     {AID_SYSTEM,    DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH,                 false},
111     {AID_SYSTEM,    DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH,               false},
112     {AID_SYSTEM,    DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false},
113     {AID_SYSTEM,    DOZABLE_MATCH|STANDBY_MATCH,   DOZABLE_MATCH,                 false},
114     {AID_SYSTEM,    DOZABLE_MATCH|STANDBY_MATCH,   NO_MATCH,                      false},
115     {AID_SYSTEM,    DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH,                      false},
116     // clang-format on
117   };
118 
119   for (const auto& config : testConfigs) {
120     SCOPED_TRACE(config.toString());
121 
122     // Setup maps.
123     EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY,
124                                                       config.enabledRules, BPF_EXIST));
125     EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(config.uid, {.iif = 0, .rule = config.uidRules},
126                                                  BPF_ANY));
127 
128     // Verify the function.
129     auto result = mDnsBpfHelper.isUidNetworkingBlocked(config.uid, /*metered=*/false);
130     EXPECT_TRUE(result.ok());
131     EXPECT_EQ(config.expectedResult, result.value());
132   }
133 }
134 
TEST_F(DnsBpfHelperTest,IsUidNetworkingBlocked_uninitialized)135 TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_uninitialized) {
136   ResetAllMaps();
137 
138   auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/false);
139   EXPECT_FALSE(result.ok());
140   EXPECT_EQ(EUNATCH, result.error().code());
141 
142   result = mDnsBpfHelper.isUidNetworkingBlocked(AID_SYSTEM, /*metered=*/false);
143   EXPECT_TRUE(result.ok());
144   EXPECT_FALSE(result.value());
145 }
146 
147 // Verify DataSaver on metered network.
TEST_F(DnsBpfHelperTest,IsUidNetworkingBlocked_metered)148 TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_metered) {
149   struct TestConfig {
150     const uint32_t enabledRules;     // Settings in configuration map.
151     const bool dataSaverEnabled;     // Settings in data saver enabled map.
152     const uint32_t uidRules;         // Settings in uid owner map.
153     const int blocked;               // Whether the UID is expected to be networking blocked or not.
154     std::string toString() const {
155       return fmt::format(
156           ", enabledRules: {}, dataSaverEnabled: {},  uidRules: {}, expect blocked: {}",
157           enabledRules, dataSaverEnabled, uidRules, blocked);
158     }
159   } testConfigs[]{
160     // clang-format off
161     // enabledRules, dataSaverEnabled, uidRules,                                            blocked
162     {NO_MATCH,       false,            NO_MATCH,                                             false},
163     {NO_MATCH,       false,            PENALTY_BOX_USER_MATCH,                                true},
164     {NO_MATCH,       false,            PENALTY_BOX_ADMIN_MATCH,                               true},
165     {NO_MATCH,       false,            PENALTY_BOX_USER_MATCH|PENALTY_BOX_ADMIN_MATCH,        true},
166     {NO_MATCH,       false,            HAPPY_BOX_MATCH,                                      false},
167     {NO_MATCH,       false,            PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH,                true},
168     {NO_MATCH,       false,            PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH,               true},
169     {NO_MATCH,       true,             NO_MATCH,                                              true},
170     {NO_MATCH,       true,             PENALTY_BOX_USER_MATCH,                                true},
171     {NO_MATCH,       true,             PENALTY_BOX_ADMIN_MATCH,                               true},
172     {NO_MATCH,       true,             PENALTY_BOX_USER_MATCH|PENALTY_BOX_ADMIN_MATCH,        true},
173     {NO_MATCH,       true,             HAPPY_BOX_MATCH,                                      false},
174     {NO_MATCH,       true,             PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH,                true},
175     {NO_MATCH,       true,             PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH,               true},
176     {STANDBY_MATCH,  false,            STANDBY_MATCH,                                         true},
177     {STANDBY_MATCH,  false,            STANDBY_MATCH|PENALTY_BOX_USER_MATCH,                  true},
178     {STANDBY_MATCH,  false,            STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH,                 true},
179     {STANDBY_MATCH,  false,            STANDBY_MATCH|HAPPY_BOX_MATCH,                         true},
180     {STANDBY_MATCH,  false,            STANDBY_MATCH|PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH,  true},
181     {STANDBY_MATCH,  false,            STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
182     {STANDBY_MATCH,  true,             STANDBY_MATCH,                                         true},
183     {STANDBY_MATCH,  true,             STANDBY_MATCH|PENALTY_BOX_USER_MATCH,                  true},
184     {STANDBY_MATCH,  true,             STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH,                 true},
185     {STANDBY_MATCH,  true,             STANDBY_MATCH|HAPPY_BOX_MATCH,                         true},
186     {STANDBY_MATCH,  true,             STANDBY_MATCH|PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH,  true},
187     {STANDBY_MATCH,  true,             STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
188     // clang-format on
189   };
190 
191   for (const auto& config : testConfigs) {
192     SCOPED_TRACE(config.toString());
193 
194     // Setup maps.
195     EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY,
196                                                       config.enabledRules, BPF_EXIST));
197     EXPECT_RESULT_OK(mFakeDataSaverEnabledMap.writeValue(DATA_SAVER_ENABLED_KEY,
198                                                       config.dataSaverEnabled, BPF_EXIST));
199     EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(AID_APP_START, {.iif = 0, .rule = config.uidRules},
200                                                  BPF_ANY));
201 
202     // Verify the function.
203     auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/true);
204     EXPECT_RESULT_OK(result);
205     EXPECT_EQ(config.blocked, result.value());
206   }
207 }
208 
209 }  // namespace net
210 }  // namespace android
211