gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skynetblocklist/skynetblocklist.go (about) 1 package skynetblocklist 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "sync" 8 "time" 9 10 "gitlab.com/NebulousLabs/encoding" 11 "gitlab.com/NebulousLabs/errors" 12 "gitlab.com/SkynetLabs/skyd/build" 13 "gitlab.com/SkynetLabs/skyd/skymodules" 14 "go.sia.tech/siad/crypto" 15 "go.sia.tech/siad/persist" 16 "go.sia.tech/siad/types" 17 ) 18 19 const ( 20 // persistFile is the name of the persist file 21 persistFile string = "skynetblocklist.dat" 22 23 // persistSize is the size of a persisted merkleroot in the blocklist. It is 24 // the length of `merkleroot` plus the int64 probationary period plus 25 // the `listed` flag (32 + 8 + 1). 26 persistSize uint64 = 41 27 ) 28 29 var ( 30 // DefaultProbationaryPeriod is the default length in seconds of the 31 // blocklist probationary period. During this time, skylinks will be 32 // blocked but not deleted 33 // 34 // NOTE: The default for production is 90 days as this is a legal 35 // requirement in the USA for potentially illegal content to be 36 // immediately blocked, but retained for 90 days to help with 37 // investigations. 38 DefaultProbationaryPeriod = build.Select(build.Var{ 39 Standard: int64(90 * 24 * 60 * 60), // 90 days 40 Dev: int64(24 * 60 * 60), // 1 day 41 Testing: int64(60), 42 }).(int64) 43 44 // metadataHeader is the header of the metadata for the persist file 45 metadataHeader = types.NewSpecifier("SkynetBlocklist\n") 46 47 // metadataVersion is the version of the persistence file 48 metadataVersion = metadataVersionV1510 49 ) 50 51 type ( 52 // SkynetBlocklist manages a set of blocked skylinks by tracking the 53 // merkleroots and persists the list to disk. 54 SkynetBlocklist struct { 55 staticAop *persist.AppendOnlyPersist 56 57 // hashes is a map of hashed blocked merkleroots to the uinx 58 // timestamp of the end of their probationary period. During the 59 // probationary period, the skylinks are blocked but the 60 // underlying content is not deleted. This allows portal 61 // operations time to validate blocklist requests to protect 62 // against malicious blocklisting. 63 hashes map[crypto.Hash]int64 64 65 mu sync.Mutex 66 } 67 68 // persistEntry contains a hash and whether it should be listed as being in 69 // the current blocklist. 70 persistEntry struct { 71 Hash crypto.Hash 72 ProbationaryPeriodEnd int64 73 Listed bool 74 } 75 ) 76 77 // New returns an initialized SkynetBlocklist. 78 func New(persistDir string) (*SkynetBlocklist, error) { 79 // Load the persistence of the blocklist. 80 aop, reader, err := loadPersist(persistDir) 81 if err != nil { 82 return nil, errors.AddContext(err, "unable to load the skynet blocklist persistence") 83 } 84 85 sb := &SkynetBlocklist{ 86 staticAop: aop, 87 } 88 hashes, err := unmarshalObjects(reader, metadataVersion) 89 if err != nil { 90 err = errors.Compose(err, aop.Close()) 91 return nil, errors.AddContext(err, "unable to unmarshal persist objects") 92 } 93 sb.hashes = hashes 94 95 return sb, nil 96 } 97 98 // Blocklist returns the hashes of the merkleroots that are blocked 99 func (sb *SkynetBlocklist) Blocklist() []crypto.Hash { 100 sb.mu.Lock() 101 defer sb.mu.Unlock() 102 103 var blocklist []crypto.Hash 104 for hash := range sb.hashes { 105 blocklist = append(blocklist, hash) 106 } 107 return blocklist 108 } 109 110 // Close closes and frees associated resources. 111 func (sb *SkynetBlocklist) Close() error { 112 return sb.staticAop.Close() 113 } 114 115 // IsBlocked indicates if a skylink is currently blocked and if it should be 116 // deleted. 117 func (sb *SkynetBlocklist) IsBlocked(skylink skymodules.Skylink) (shouldDelete, isBlocked bool) { 118 if !skylink.IsSkylinkV1() { 119 build.Critical("IsBlocked requires V1 skylink") 120 return false, false 121 } 122 hash := crypto.HashObject(skylink.MerkleRoot()) 123 return sb.IsHashBlocked(hash) 124 } 125 126 // IsHashBlocked indicates if a hash is currently blocked and if it should be 127 // deleted. 128 func (sb *SkynetBlocklist) IsHashBlocked(hash crypto.Hash) (shouldDelete, isBlocked bool) { 129 sb.mu.Lock() 130 defer sb.mu.Unlock() 131 probationaryPeriodEnd, ok := sb.hashes[hash] 132 // If the hash exists it is blocked, and if the probationaryPeriod is in 133 // the past we should delete the data. 134 return time.Now().Unix() >= probationaryPeriodEnd, ok 135 } 136 137 // UpdateBlocklist updates the list of skylinks that are blocked. 138 func (sb *SkynetBlocklist) UpdateBlocklist(additions, removals []crypto.Hash, probationaryPeriod int64) error { 139 sb.mu.Lock() 140 defer sb.mu.Unlock() 141 142 buf, err := sb.marshalObjects(additions, removals, probationaryPeriod) 143 if err != nil { 144 return errors.AddContext(err, fmt.Sprintf("unable to update skynet blocklist persistence at '%v'", sb.staticAop.FilePath())) 145 } 146 _, err = sb.staticAop.Write(buf.Bytes()) 147 return errors.AddContext(err, fmt.Sprintf("unable to update skynet blocklist persistence at '%v'", sb.staticAop.FilePath())) 148 } 149 150 // marshalObjects marshals the given objects into a byte buffer. 151 func (sb *SkynetBlocklist) marshalObjects(additions, removals []crypto.Hash, probationaryPeriod int64) (bytes.Buffer, error) { 152 // Create buffer for encoder 153 var buf bytes.Buffer 154 // Create and encode the persist links 155 listed := true 156 for _, hash := range additions { 157 probationaryPeriodEnd := time.Now().Unix() + probationaryPeriod 158 // Check if the hash is already blocked 159 if _, ok := sb.hashes[hash]; ok { 160 // Update the probationaryPeriod 161 sb.hashes[hash] = probationaryPeriodEnd 162 continue 163 } 164 165 // Add hash to map 166 sb.hashes[hash] = probationaryPeriodEnd 167 168 // Marshal the update 169 pe := persistEntry{hash, probationaryPeriodEnd, listed} 170 data := encoding.Marshal(pe) 171 _, err := buf.Write(data) 172 if err != nil { 173 return bytes.Buffer{}, errors.AddContext(err, "unable to write addition to the buffer") 174 } 175 } 176 listed = false 177 for _, hash := range removals { 178 // Check if the hash is already removed 179 if _, ok := sb.hashes[hash]; !ok { 180 continue 181 } 182 183 // Remove hash from map 184 delete(sb.hashes, hash) 185 186 // Marshal the update 187 pe := persistEntry{hash, 0, listed} 188 data := encoding.Marshal(pe) 189 _, err := buf.Write(data) 190 if err != nil { 191 return bytes.Buffer{}, errors.AddContext(err, "unable to write removal to the buffer") 192 } 193 } 194 195 return buf, nil 196 } 197 198 // unmarshalObjects unmarshals the sia encoded objects. 199 func unmarshalObjects(reader io.Reader, version types.Specifier) (map[crypto.Hash]int64, error) { 200 // Version Check 201 switch version { 202 case metadataVersionV1510: 203 default: 204 return nil, errors.New("bad version") 205 } 206 blocklist := make(map[crypto.Hash]int64) 207 // Unmarshal blocked links one by one until EOF. 208 var offset uint64 209 for { 210 buf := make([]byte, persistSize) 211 _, err := io.ReadFull(reader, buf) 212 if errors.Contains(err, io.EOF) { 213 break 214 } 215 if err != nil { 216 return nil, err 217 } 218 var pe persistEntry 219 err = encoding.Unmarshal(buf, &pe) 220 if err != nil { 221 return nil, err 222 } 223 offset += persistSize 224 225 if !pe.Listed { 226 delete(blocklist, pe.Hash) 227 continue 228 } 229 blocklist[pe.Hash] = pe.ProbationaryPeriodEnd 230 } 231 return blocklist, nil 232 }